2009-08-17 9 views
3

Je ne fais pas beaucoup de SQL, et la plupart du temps, je fais des opérations CRUD. De temps en temps, je vais obtenir quelque chose d'un peu plus compliqué. Donc, cette question peut être une question de débutant, mais je suis prêt. Je viens d'essayer de comprendre cela pendant des heures, et ça n'a servi à rien.SELECT avec une colonne calculée qui dépend d'une corrélation

Alors, imaginez la structure de tableau suivant:

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

Je veux sélectionner ID et une colonne calculée. La colonne calculée a une plage de 0 à 8 et contient le nombre de correspondances à la requête. Je souhaite également limiter l'ensemble de résultats pour inclure uniquement les lignes ayant un certain nombre de correspondances.

Ainsi, à partir de ces données d'échantillon:

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

Je veux interroger sur Col1 = 'a' OU Col2 = 'c' OU Col3 = 1 OU Col4 = 5 où le résultat calculé> 1 et ont le jeu de résultats ressembler à:

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

J'utilise T-SQL et SQL Server 2005, si elle compte, et je ne peux pas changer le schéma DB.

Je préfère également le garder comme une requête autonome et ne pas avoir à créer une procédure stockée ou une table temporaire.

+0

Essayez ceci: SELECT * FROM table WHERE (Col1 = 'a' OU Col2 = 'c' OU Col3 = 1 OU Col4 = 5) ET (COUNT (Col1)> 1 OU COUNT (Col2)> 1 OU COUNT (Col3)> 1 ou COUNT (Col4)> 1) GROUPE PAR Col1, Col2, Col3, Col4 Non testé ... – Havenard

+0

Pouvez-vous expliquer votre table de résultats? – pjp

+1

Pourquoi, dans votre exemple, votre ensemble de résultats pour Id = 3 a-t-il un total de colonnes calculé de 3? On dirait que cela devrait être 2. – jro

Répondre

4

Cette réponse fonctionnera avec SQL 2005, en utilisant un CTE pour nettoyer la table dérivée un peu.

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

Cela fonctionne très bien! Merci. La solution de Quassnoi a également fonctionné. –

2

est ici une solution qui tire parti du fait qu'une comparaison booléenne renvoie les entiers 1 ou 0:

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

Notez que vous devez parenthésée les comparaisons booléennes parce + a une priorité supérieure =. En outre, vous devez tout mettre dans une sous-requête car vous ne pouvez normalement pas utiliser un alias de colonne dans une clause WHERE de la même requête.

Il peut sembler que vous devriez également utiliser une clause WHERE dans la sous-requête pour restreindre ses lignes, mais selon toute vraisemblance vous allez finir avec un scan de table complet, donc ce n'est probablement pas une grande victoire. D'un autre côté, si vous pensez qu'une telle restriction réduirait considérablement le nombre de lignes dans le résultat de la sous-requête, cela en valait la peine.


Commentaire de Re Quassnoi, si vous ne pouvez pas traiter les expressions booléennes comme des valeurs entières, il devrait y avoir un moyen de cartographier les conditions booléennes aux entiers, même si elle est un peu bavard. Par exemple:

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

Sinon, vous pourriez éviter la sous-requête au prix de répéter le même ' (Col1 = 'a') + (Col2 = 'c') + ... 'dans la clause' WHERE', que SQL Server devrait optimiser (ce qui rend la requête plus longue le seul coût) – VoteyDisciple

+0

Cela fonctionnera dans 'MySQL' mais pas dans 'SQL Server', il ne peut pas convertir les booléens en entiers. – Quassnoi

+0

@VoteyDisciple: Oui, je pensais au même point et j'ajoutais le dernier paragraphe de la réponse ci-dessus pendant que vous écriviez votre commentaire. –

1

Cette requête est plus index convivial:

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 

Ce ne sera efficace que si tous les colonnes que vous recherchez sont, d'abord, indexés et, d'autre part, ont élevé cardinalité (plusieurs valeurs distinctes).

Voir cet article dans mon blog pour plus de détails de performance:

+0

Le problème avec ceci est que cela signifie que vous devez faire des recherches d'index pour chaque élément sur lequel vous filtrez et qu'il suppose que l'ID est un PK en cluster. Si l'un de ceux-ci n'est pas indexé, vous finissez par faire un scan de table plus l'index cherche (dans le pire des cas 4 balayages de table). Cela va être sous-optimal, où une seule analyse de table ferait l'affaire. – Anon246

+0

'@ Strommy': vous avez raison, et c'est exactement ce que le poste dit. Mais si les colonnes sont sélectives, cette solution peut être optimale. – Quassnoi

+0

@Quassnoi: Je suis d'accord avec vous. Le travail et le vôtre a la possibilité d'être beaucoup plus rapide en supposant une couverture d'index. Il y a plus d'une façon de peler un chat, je suppose ... – Anon246

Questions connexes