2009-08-17 11 views
3

Ich mache nicht viel SQL, und die meiste Zeit mache ich CRUD-Operationen. Gelegentlich bekomme ich etwas komplizierter. Also, diese Frage kann eine neue Frage sein, aber ich bin bereit. Ich habe gerade versucht, das stundenlang herauszufinden, und es hat keinen Sinn gemacht.SELECT mit berechneter Spalte, die von einer Korrelation abhängig ist

So Stellen Sie sich die folgende Tabelle Struktur:

> | ID | Col1 | Col2 | Col3 | .. | Col8 | 

Ich möchte ID und eine berechnete Spalte wählen. Die berechnete Spalte hat einen Bereich von 0 - 8 und enthält die Anzahl der Übereinstimmungen mit der Abfrage. Ich möchte auch die Ergebnismenge auf Zeilen beschränken, die eine bestimmte Anzahl von Übereinstimmungen haben.

Also, aus diesen Beispieldaten:

> | 1 | 'a' | 'b' | 1 | 2 | 
> | 2 | 'b' | 'c' | 1 | 2 | 
> | 3 | 'b' | 'c' | 4 | 5 | 
> | 4 | 'x' | 'x' | 9 | 9 | 

Ich mag auf Col1 = 'a' OR Col2 = 'c' abzufragen OR Col3 = 1 OR Col4 = 5, wobei das berechnete Ergebnis> 1 und habe die Ergebnismenge wie folgt aussehen:

> | ID | Cal | 
> | 1 | 2 | 
> | 2 | 2 | 
> | 3 | 2 | 

ich bin mit T-SQL und SQL Server 2005, wenn es darauf ankommt, und ich kann nicht das DB-Schema ändern.

Ich würde es auch bevorzugen, es als eine in sich geschlossene Abfrage zu halten und nicht eine gespeicherte Prozedur oder temporäre Tabelle erstellen müssen.

+0

Versuchen Sie dies: SELECT * FROM Tabelle WHERE (Col1 = 'a' OR Col2 = 'c' OR Col3 = 1 OR Col4 = 5) und (COUNT (Col1)> 1 OR COUNT (Col2)> 1 ODER COUNT (Col3)> 1 oder COUNT (Col4)> 1) GROUP BY Col1, Col2, Col3, Col4 Nicht getestet ... – Havenard

+0

Können Sie Ihre Ergebnistabelle erklären? – pjp

+1

Warum hat Ihr Resultset für Id = 3 in Ihrem Beispiel eine berechnete Spaltensumme von 3? Sieht so aus, als sollte es 2 sein. – jro

Antwort

4

Diese Antwort mit SQL 2005 funktionieren wird, einen CTE mit ein wenig die abgeleitete Tabelle zu bereinigen.

WITH Matches AS 
(
    SELECT ID, CASE WHEN Col1 = 'a' THEN 1 ELSE 0 END + 
       CASE WHEN Col2 = 'c' THEN 1 ELSE 0 END + 
       CASE WHEN Col3 = 1 THEN 1 ELSE 0 END + 
       CASE WHEN Col4 = 5 THEN 1 ELSE 0 END AS Result 
    FROM Table1 
    WHERE Col1 = 'a' OR Col2 = 'c' OR Col3 = 1 OR Col4 = 5 
) 
SELECT ID, Result 
FROM Matches 
WHERE Result > 1 
+0

Das funktioniert großartig! Vielen Dank. Quassnois Lösung hat auch funktioniert. –

2

Hier ist eine Lösung, die die Tatsache, dass ein boolean Vergleich gibt die ganzen Zahlen 1 oder 0 nutzt:

SELECT * FROM (
    SELECT ID, (Col1='a') + (Col2='c') + (Col3=1) + (Col4=5) AS calculated 
    FROM MyTable 
) q 
WHERE calculated > 1; 

Beachten Sie, dass die Booleschen Vergleiche müssen klammern, weil + höhere Priorität hat als =. Außerdem müssen Sie alles in eine Unterabfrage stellen, da Sie normalerweise keinen Spaltenalias in einer WHERE-Klausel derselben Abfrage verwenden können.

Es könnte scheinen, dass Sie auch eine WHERE Klausel in der Unterabfrage verwenden sollten, um ihre Zeilen zu beschränken, aber mit großer Wahrscheinlichkeit werden Sie am Ende mit einem vollständigen Tabellenscan sowieso, also ist es wahrscheinlich kein großer Gewinn. Auf der anderen Seite, wenn Sie erwarten, dass eine solche Einschränkung stark die Anzahl der Zeilen in der Unterabfrage Ergebnis reduzieren würde, dann würde es sich lohnen.


Re Quassnoi Kommentar, wenn Sie nicht Booleschen Ausdrücken als Integer-Werte behandeln können, sollte es eine Möglichkeit geben, boolean Bedingungen auf ganze Zahlen auf der Karte, auch wenn es ein bisschen ausführlicher ist. Zum Beispiel:

SELECT * FROM (
    SELECT ID, 
     CASE WHEN Col1='a' THEN 1 ELSE 0 END 
    + CASE WHEN Col2='c' THEN 1 ELSE 0 END 
    + CASE WHEN Col3=1 THEN 1 ELSE 0 END 
    + CASE WHEN Col4=5 THEN 1 ELSE 0 END AS calculated 
    FROM MyTable 
) q 
WHERE calculated > 1; 
+0

Alternativ könnten Sie die Unterabfrage auf Kosten der Wiederholung der gleichen zu vermeiden (Col1 = 'a') + (Col2 = 'c') + ... 'in der 'WHERE'-Klausel, die SQL Server sollte weg optimieren (wodurch die längere Abfrage die einzigen Kosten) – VoteyDisciple

+0

Dies wird in' MySQL' funktionieren aber nicht in SQL Server kann es Booleans nicht in ganze Zahlen umwandeln. – Quassnoi

+0

@VoteyDisciple: Ja, ich dachte an den gleichen Punkt und ich fügte den letzten Absatz in der obigen Antwort hinzu, als Sie Ihren Kommentar verfassten. –

1

Diese Abfrage ist mehr Index freundlich:

SELECT id, SUM(match) 
FROM (
     SELECT id, 1 AS match 
     FROM mytable 
     WHERE col1 = 'a' 
     UNION ALL 
     SELECT id, 1 AS match 
     FROM mytable 
     WHERE col2 = 'c' 
     UNION ALL 
     SELECT id, 1 AS match 
     FROM mytable 
     WHERE col3 = 1 
     UNION ALL 
     SELECT id, 1 AS match 
     FROM mytable 
     WHERE col4 = 5 
     ) q 
GROUP BY 
     id 
HAVING SUM(match) > 1 

Dies wird nur dann effizient sein, wenn alle die Spalten Sie suchen, sind erstens, indiziert und zweitens haben hohe Kardinalität (viele verschiedene Werte).

Lesen Sie diesen Artikel in meinem Blog für Leistungsdetails:

+0

Das Problem dabei ist, dass es bedeutet, dass Sie für jedes Element, nach dem Sie filtern, Index-Suchvorgänge ausführen müssen. Dabei wird davon ausgegangen, dass es sich bei der ID um eine gruppierte PK handelt. Wenn einer davon nicht indiziert ist, führt dies zu einem Tabellen-Scan plus Index-Suchen (Worst-Case-4-Tabellen-Scans). Dies wird suboptimal sein, wo ein einzelner Tabellenscan ausreichen würde. – Anon246

+0

'@ Strommy': Du hast Recht, und genau das sagt der Beitrag. Wenn die Spalten jedoch selektiv sind, kann diese Lösung optimal sein. – Quassnoi

+0

@Quassnoi: Ich stimme dir zu. Sowohl Ihre Arbeit als auch Ihre Arbeit haben die Möglichkeit, bei der Annahme einer Indexabdeckung viel schneller zu sein. Es gibt mehr als eine Möglichkeit, eine Katze zu häuten, denke ich ... – Anon246