SELECT avec colonne calculée qui dépend d'une corrélation
-
18-09-2019 - |
Question
Je ne fais pas beaucoup de SQL, et la plupart du temps, je fais des opérations CRUD. De temps en temps, je vais quelque chose d'un peu plus compliqué. Donc, cette question peut être une question de débutant, mais je suis prêt. Je viens juste essayé de comprendre cela pendant des heures, et il a été inutile.
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 gamme de 0 - 8, et il contient le nombre de correspondances à la requête. Je tiens également à limiter le jeu de résultats pour inclure uniquement les lignes qui ont un certain nombre de correspondances.
Ainsi, à partir de cet échantillon de données:
> | 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 importe, et je ne peux pas changer le schéma DB.
Je préfère également garder comme une requête autonome et ne pas avoir à créer une procédure stockée ou d'une table temporaire.
La solution
Cette réponse travaillera en collaboration 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
Autres conseils
Voici 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 un sous-requête parce que vous pouvez normalement pas utiliser un alias de colonne dans une clause de WHERE
de la même requête.
Il peut sembler que vous devez également utiliser une clause de WHERE
dans la sous-requête pour limiter ses lignes, mais selon toute vraisemblance, vous allez finir avec un balayage table complète de toute façon il est donc probablement pas une grande victoire. D'autre part, si vous vous attendez qu'une telle restriction serait très réduire le nombre de lignes dans le résultat de sous-requête, il serait utile.
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;
Cette requête est plus convivial index:
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 une grande cardinalité (beaucoup de valeurs distinctes).
Voir cet article dans mon blog pour les détails de la performance: