Frage

Dies ist eine sehr einfache Abfrage, die ich nicht herausfinden kann ....

Lassen Sie uns sagen, ich habe eine zweispaltige Tabelle wie folgt aus:

userid  |  roleid
--------|--------
   1    |    1
   1    |    2
   1    |    3
   2    |    1

ich all unterschiedlichen Benutzer-IDs zu bekommen, die roleids haben 1, 2 und 3 das obige Beispiel verwenden, das einzige Ergebnis, das ich zurückgegeben werden soll ist userid 1. Wie mache ich das?

War es hilfreich?

Lösung

SELECT userid
FROM UserRole
WHERE roleid IN (1, 2, 3)
GROUP BY userid
HAVING COUNT(DISTINCT roleid) = 3;

Für jeden der Lektüre dieses: meine Antwort ist einfach und unkompliziert, und bekam die ‚akzeptiert‘ Status, aber bitte die Antwort von @cletus gegeben. Es hat eine deutlich bessere Leistung.


Justing laut gedacht, ein anderer Weg, um die Selbstverknüpfung schreiben von @cletus beschrieben ist:

SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid
JOIN userrole t3 ON t2.userid = t3.userid
WHERE (t1.roleid, t2.roleid, t3.roleid) = (1, 2, 3);

Dies könnte einfacher sein, für Sie zu lesen, und MySQL unterstützt Vergleiche von Tupeln ähnlich. MySQL weiß auch, wie Indizes zu nutzen abdeckt intelligent für diese Abfrage. Führen Sie einfach es durch EXPLAIN und finden Sie unter „Index“ im Anhang für alle drei Tabellen, die es liest den Index bedeutet, und haben nicht einmal die Datenzeilen zu berühren.

lief ich diese Abfrage über 2,1 Millionen Zeilen (der Stack-Überlauf Juli Datendump für PostTags) mit MySQL 5.1.48 auf meinem MacBook, und es ergab das Ergebnis in 1,08 sec. Auf einem anständigen Server mit genügend Speicher innodb_buffer_pool_size zugewiesen, sollte es noch schneller sein.

Andere Tipps

Ok, ich habe auf dieser Downvoted so habe ich beschlossen, es zu testen:

CREATE TABLE userrole (
  userid INT,
  roleid INT,
  PRIMARY KEY (userid, roleid)
);

CREATE INDEX ON userrole (roleid);

Führen Sie diese:

<?php
ini_set('max_execution_time', 120); // takes over a minute to insert 500k+ records 

$start = microtime(true);

echo "<pre>\n";
mysql_connect('localhost', 'scratch', 'scratch');
if (mysql_error()) {
    echo "Connect error: " . mysql_error() . "\n";
}
mysql_select_db('scratch');
if (mysql_error()) {
    echo "Selct DB error: " . mysql_error() . "\n";
}

$users = 200000;
$count = 0;
for ($i=1; $i<=$users; $i++) {
    $roles = rand(1, 4);
    $available = range(1, 5);
    for ($j=0; $j<$roles; $j++) {
        $extract = array_splice($available, rand(0, sizeof($available)-1), 1);
        $id = $extract[0];
        query("INSERT INTO userrole (userid, roleid) VALUES ($i, $id)");
        $count++;
    }
}

$stop = microtime(true);
$duration = $stop - $start;
$insert = $duration / $count;

echo "$count users added.\n";
echo "Program ran for $duration seconds.\n";
echo "Insert time $insert seconds.\n";
echo "</pre>\n";

function query($str) {
    mysql_query($str);
    if (mysql_error()) {
        echo "$str: " . mysql_error() . "\n";
    }
}
?>

Ausgabe:

499872 users added.
Program ran for 56.5513510704 seconds.
Insert time 0.000113131663847 seconds.

Das macht 500.000 zufälligen Benutzer-Rollen-Kombinationen und gibt es etwa 25.000, dass die gewählten Kriterien entsprechen.

Erste Abfrage:

SELECT userid
FROM userrole
WHERE roleid IN (1, 2, 3)
GROUP by userid
HAVING COUNT(1) = 3

Abfragezeit: 0.312s

SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid AND t2.roleid = 2
JOIN userrole t3 ON t2.userid = t3.userid AND t3.roleid = 3
AND t1.roleid = 1

Abfragezeit: 0.016s

Das ist richtig. Die Join-Version, die ich vorgeschlagen ist zwanzig Mal schneller als die Gesamt Version.

Es tut uns Leid, aber ich tue dies für ein Leben und Arbeiten in der realen Welt und in der realen Welt, die wir testen, SQL und die Ergebnisse sprechen für sich.

Der Grund hierfür sollte ziemlich klar sein. Das Aggregat Abfrage Kosten, die mit der Größe der Tabelle skalieren. Jede Reihe wird verarbeitet, aggregiert und (oder nicht) durch die Klausel HAVING filtriert. Die Join-Version wird (unter Verwendung eines Index) wählen Sie eine Teilmenge der auf einer bestimmten Rolle basierte Benutzer, überprüfen Sie, dass die Teilmenge gegen die zweite Rolle und schließlich dieser Teilmenge gegen die dritte Rolle. Jede Auswahl (in funktioniert relationale Algebra ) auf einem zunehmend kleine Teilmenge. Daraus können Sie schließen:

Die Leistung der Version kommen wird noch besser mit einer geringeren Inzidenz von Übereinstimmungen.

Wenn es nur 500 Nutzer waren (aus der 500k Probe oben), die die drei genannten Rollen hatte, die Join-Version deutlich schneller. Die Aggregat Version wird nicht (und jede Performance-Verbesserung ist eine Folge des Transports von 500 Benutzern statt 25k, die die Join-Version natürlich auch bekommt).

Ich war auch neugierig, wie eine echte Datenbank (zB Oracle) damit umgehen würde. So wiederholte ich im Grunde die gleiche Übung auf Oracle XE (auf demselben Windows XP Desktop-Computern wie der MySQL aus dem vorherigen Beispiel), und die Ergebnisse sind fast identisch.

Joins scheint verpönt zu sein, aber wie ich gezeigt habe, können Aggregatabfragen eine Größenordnung langsamer sein.

Update: Nach einigem umfangreiche Tests , ist das Bild komplizierter und wird die Antwort auf Ihre Daten, Ihre Datenbank und anderen Faktoren abhängen. Die Moral der Geschichte ist, Test, Test, Test.

Der klassische Weg, dies zu tun, ist es als ein relationale Division Problem zu behandeln.

In Englisch:. Wählen Sie die Benutzer, für die keine der gewünschten RoleID Werte fehlt

Ich nehme an, Sie eine Tabelle Benutzer haben, auf die die Userrole Tabelle bezieht, und ich werde die gewünschten RoleID Werte sind in einer Tabelle übernehmen:

create table RoleGroup(
  roleid int not null,
  primary key(roleid)
)
insert into RoleGroup values (1);
insert into RoleGroup values (2);
insert into RoleGroup values (3);

Ich nehme an, auch alle relevanten Spalten keine Nullwerte enthalten sind, so gibt es keine Überraschungen mit IN oder NICHT VORHANDEN. Hier ist eine SQL-Abfrage, die die englischen oben zum Ausdruck bringt:

select userid from Users as U
where not exists (
  select * from RoleGroup as G
  where not exists (
    select R.roleid from UserRole as R
    where R.roleid = G.roleid
    and R.userid = U.userid
  )
);

Eine andere Möglichkeit, es zu schreiben, ist dies

select userid from Users as U
where not exists (
  select * from RoleGroup as G
  where G.roleid not in (
    select R.roleid from UserRole as R
    where R.userid = U.userid
  )
);

Dies kann oder auch nicht am Ende als effizient, in Abhängigkeit von Indizes, Plattform, Daten etc. Web durchsuchen nach „relationaler Division“ und Sie werden eine Menge finden.

Unter der Annahme, Benutzer-ID, RoleID ist in einem eindeutigen Index enthalten ist (dh es gibt nicht zwei Datensätze sein kann, wo Benutzer-ID = x und RoleID = 1

select count(*), userid from t
where roleid in (1,2,3)
group by userid
having count(*) = 3
select userid from userrole where userid = 1
intersect
select userid from userrole where userid = 2
intersect
select userid from userrole where userid = 3

Wird dies das Problem nicht lösen? Wie gut eine Lösung ist, diese auf typische Relationale DBs? Wird diese Abfrage-Optimierer automatisch optimieren?

Wenn Sie jede Art von Allgemeinheit brauchen hier (verschiedene 3-Rollenkombinationen oder verschiedene n-Rollenkombinationen) ... Ich würde vorschlagen, dass Sie ein wenig Maskierungssystem für Ihre Rollen verwenden und die Bit-Operatoren Ihre Abfragen auszuführen. ..

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top