2012-10-03 8 views
5

Ich verstehe, Unterabfragen sind notorisch schlecht für die Leistung, wenn sie falsch verwendet werden. Ich habe ein sehr spezifisches Szenario, in dem der Benutzer eine gefilterte Menge von Datensätzen aus einer Tabelle abrufen muss. Eine große Auswahl an Filtern wird verfügbar sein und sie müssen die Zusammensetzung unterstützen. Darüber hinaus werden regelmäßig neue Filter von einer Gruppe von Entwicklern erstellt.SQL-Unterabfragen in FROM-Klausel

Ich mag nicht die Idee einer wachsenden, monolithischen SQL-Abfrage mit einer großen Anzahl von Parametern. Ich mag die Idee einer Reihe von autonomen SQL-Abfragen mit identischen SELECT-Anweisungen und unterschiedlichen WHERE-Klauseln nicht. Ich mag die Idee einer dynamischen SQL-Abfrage, aber ich bin mir nicht sicher, welche Art von Struktur ich verwenden soll. Ich kann mich vier grundlegende Optionen: (wenn es mehr ist, dass ich fehle, dann zögern Sie bitte nicht, sie vorzuschlagen)

  1. „INNER JOIN“: verketten Filter über innere Joins Ergebnisse zu filtern.
  2. "FROM Unterabfragen": Stack-Filter über Unterabfragen in der FROM-Anweisung.
  3. "WHERE Unterabfragen": Contatenate Filter über Unterabfragen in der WHERE-Klausel.
  4. "INNER JOIN Unterabfragen": Ein seltsamer Hybrid.

Ich habe eine SQL Geige erstellt demonstrieren (und Profil) sie:

http://sqlfiddle.com/#!3/4e17b/9

Die unten ist ein Auszug aus der Geige eine Vorstellung davon zu geben, was ich m spricht:

------------------------------------------------------------------------ 
--THIS IS AN EXCERPT FROM THE SQL FIDDLE -- IT IS NOT MEANT TO COMPILE-- 
------------------------------------------------------------------------ 

-- 
--"INNER JOIN" test 
     SELECT COUNT(*) 
     FROM 
      @TestTable Test0 
      INNER JOIN @TestTable Test1 ON Test1.ID=Test0.ID AND Test1.ID % @i = 0 
      INNER JOIN @TestTable Test2 ON Test2.ID=Test0.ID AND Test2.ID % @j = 0 
      INNER JOIN @TestTable Test3 ON Test3.ID=Test0.ID AND Test3.ID % @k = 0 

-- 
--"FROM subqueries" test 
     SELECT COUNT(*) FROM (
      SELECT * FROM (
        SELECT * FROM (
         SELECT * FROM @TestTable Test3 WHERE Test3.ID % @k = 0 
       ) Test2 WHERE Test2.ID % @j = 0 
      ) Test1 WHERE Test1.ID % @i = 0 
    ) Test0 

-- 
--"WHERE subqueries" test 
     SELECT COUNT(*) 
     FROM @TestTable Test0 
     WHERE 
      Test0.ID IN (SELECT ID FROM @TestTable Test1 WHERE Test1.ID % @i = 0) 
      AND Test0.ID IN (SELECT ID FROM @TestTable Test2 WHERE Test2.ID % @j = 0) 
      AND Test0.ID IN (SELECT ID FROM @TestTable Test3 WHERE Test3.ID % @k = 0) 

-- 
--"INNER JOIN subqueries" test 
    SELECT COUNT(*) 
    FROM 
     TestTable Test0 
     INNER JOIN (SELECT ID FROM TestTable WHERE ID % @i = 0) Test1 ON Test1.ID=Test0.ID 
     INNER JOIN (SELECT ID FROM TestTable WHERE ID % @j = 0) Test2 ON Test2.ID=Test0.ID 
     INNER JOIN (SELECT ID FROM TestTable WHERE ID % @k = 0) Test3 ON Test3.ID=Test0.ID 

-- 
--"EXISTS subqueries" test 
    SELECT COUNT(*) 
    FROM TestTable Test0 
    WHERE 
     EXISTS (SELECT 1 FROM TestTable Test1 WHERE Test1.ID = Test0.ID AND Test1.ID % @i = 0) 
     AND EXISTS (SELECT 1 FROM TestTable Test2 WHERE Test2.ID = Test0.ID AND Test2.ID % @j = 0) 
     AND EXISTS (SELECT 1 FROM TestTable Test3 WHERE Test3.ID = Test0.ID AND Test3.ID % @k = 0) 

Rankings (Zeit, um die Tests auszuführen)

SQL Fiddle:

|INNER JOIN|FROM SUBQUERIES|WHERE SUBQUERIES|INNER JOIN SUBQUERIES|EXISTS SUBQUERIES| 
------------------------------------------------------------------------------------- 
|  5174 |   777 |   7240 |    5478 |   7359 | 

Lokale Umwelt: (ohne Cache: Clearing-Puffer vor jedem Test)

|INNER JOIN|FROM SUBQUERIES|WHERE SUBQUERIES|INNER JOIN SUBQUERIES|EXISTS SUBQUERIES| 
------------------------------------------------------------------------------------- 
|  3281 |   2851 |   2964 |    3148 |   3071 | 

Lokale Umwelt: (mit Cache: Ausführen von Abfragen zweimal in Folge und notieren Sie die Zeit des 2. Laufs)

|INNER JOIN|FROM SUBQUERIES|WHERE SUBQUERIES|INNER JOIN SUBQUERIES|EXISTS SUBQUERIES| 
------------------------------------------------------------------------------------- 
|  284 |   50 |   3334 |     278 |    408 | 

Es gibt Vorteile/Nachteile mit jeder Lösung. Die Unterabfragen in der WHERE-Klausel haben ziemlich schreckliche Leistung. Die Unterabfragen in der FROM-Klausel haben eine recht gute Leistung (tatsächlich erbringen sie normalerweise die besten Ergebnisse) (Anmerkung: Ich glaube, diese Methode würde die Vorteile von Indizes zunichte machen). Die INNER JOINs haben eine ziemlich gute Performance, obwohl sie einige interessante Scoping-Probleme mit sich bringt, da die INNER JOINs im Gegensatz zu den Unterabfragen im selben Kontext arbeiten (es müsste ein intermediäres System geben, um die Kollision von Tabellenaliasen zu vermeiden).

Insgesamt denke ich, die sauberste Lösung ist Unterabfragen in der FROM-Klausel. Die Filter wären einfach zu schreiben und zu testen (weil sie im Gegensatz zu den INNER JOINs nicht mit einer Kontext/Basis-Abfrage versehen werden müssten).

Gedanken? Ist dies eine gültige Verwendung von Unterabfragen oder eine Katastrophe, die auf Sie wartet?

UPDATE (2012.10.04):

  • aktualisiert SQL Fiddle einen Test umfassen für "exists" Methode
  • Added Performance-Test von SQL Fiddle und lokale Umgebung

Antwort

0

Wenn Sie werden immer "und" Logik anwenden die innere Verbindung ist wahrscheinlich ein guter Ansatz (ich verallgemeinere, aber es wird durch eine Menge von Faktoren einschließlich Ihrer Tabelle Größe und Indizes usw.) variieren. Sie müssen eine der anderen Lösungen verwenden, wenn Sie "und" oder "oder" filtern können.

Außerdem sollten Sie die Performance testen, indem existiert Klauseln:

SELECT COUNT(*) 
     FROM @TestTable Test0 
     WHERE 
      EXISTS (SELECT 1 FROM @TestTable Test1 WHERE Test0.ID = Test1.ID AND Test1.ID % @i = 0) 
      EXISTS (SELECT 1 FROM @TestTable Test2 WHERE Test0.ID = Test2.ID AND Test2.ID % @j = 0) 
      EXISTS (SELECT 1 FROM @TestTable Test3 WHERE Test0.ID = Test3.ID AND Test3.ID % @k = 0) 
+0

Hinzugefügt Tests für die "exists" Methode. Es rangiert in der Nähe des hinteren Endes der Packung, obwohl ich überrascht war, einen Unterschied zwischen "SELECT *" und "SELECT 1" zu sehen (sicherlich nicht die am besten kontrollierten Tests, aber es gab einige deutliche Unterschiede). Die allgemeine Methodik wird auf eine Vielzahl von Tabellen angewendet, so dass Größe und Indizes im Voraus schwer vorherzusagen sind. Ich suche im Grunde nach der "freundlichsten Sandbox". – makerplays