Dans Sqlite 3, j'essaie de comprendre comment sélectionner des lignes en fonction d'une valeur minimale. Je pense que je suis limité en ne connaissant pas suffisamment la terminologie associée pour rechercher efficacement sur Google.
Le tableau ressemble à:
num text num2
---------- ---------- ----------
0 a 1
0 a 2
1 a 3
1 b 4
Je veux obtenir les lignes où num2
est 1, 2
, et 4
. Je veux faire la sélection en fonction de la valeur minimale de num pour chaque valeur unique de la colonne de texte.
Donc pour text = 'a'
, la valeur minimale de num
is 0
, donc je veux les lignes 1 et 2. Pour text = 'b'
, la valeur minimale de num
est 1
, donc je veux la ligne 4
.
En utilisant diverses combinaisons de group by, je peux obtenir les deux lignes 1
et 2
ou lignes 1
et 4
. J'ai l'impression qu'il me manque un composant SQL qui ferait ce que je veux, mais je n'ai pas réussi à comprendre ce que cela pourrait être.
Quelle est la bonne façon de faire ce type de requête?
J'ai trouvé a moyen de le faire. Je ne suis pas assez réputé pour répondre à ma propre question, alors je fais la mise à jour ici. Je ne sais pas si c'est toujours correct ou à quoi ressemble l'efficacité. Tout commentaire est le bienvenu.
J'ai utilisé une instruction select composée, où une requête trouve la valeur minimale de num pour chaque valeur unique de texte:
sqlite> select num, text from t group by text having num = min( num );
num text
---------- ----------
0 a
1 b
Ensuite, je l'ai joint à la table complète pour obtenir toutes les lignes correspondant à ces deux colonnes.
sqlite> with u as
( select num, text from t group by text having num = min( num ) )
select t.* from t join u on t.num = u.num and t.text = u.text;
num text num2
---------- ---------- ----------
0 a 1
0 a 2
1 b 4
Comme vous l'avez vu, un simple GROUP BY ne fonctionnera pas car il ne retournerait qu'un seul enregistrement par groupe.
Votre inscription fonctionne bien. Pour une grande table, elle ne sera efficace que s'il existe un index sur les colonnes de jointure (num
et text
).
Vous pouvez également utiliser une sous-requête corrélée:
SELECT *
FROM t
WHERE num = (SELECT MIN(num)
FROM t AS t2
WHERE t2.text = t.text);
Lors de son exécution, cette requête ne nécessite pas de table temporaire (votre requête le fait pour le résultat de u
), mais exécutera la sous-requête pour chaque enregistrement dans t
, donc text
doit être indexé. (Ou utilisez un index à la fois sur text
et num
pour obtenir un indice de couverture .)
J'ai tendance à faire ce genre de chose avec une auto-jointure externe:
SELECT
M1.Num,
M1.Text,
M1.Num2
FROM
MyDb M1
LEFT OUTER JOIN
MyDB M2
ON
M1.text = M2.text
AND
M1.num > m2.num
WHERE
M2.num is null
C'est essentiellement dire; donnez-moi tous les enregistrements qui n'ont pas une valeur plus élevée, c'est-à-dire null.
Alors, comment pouvez-vous trouver vous-même la réponse à votre question la prochaine fois? À mon avis, c'est en décomposant et en suivant la logique. Et vous avez raison:
Je veux faire la sélection en fonction de la valeur minimale de num pour chaque valeur unique de la colonne de texte
Cela se traduit par:
select text, min(num) from t group by text;
(Cela devrait être équivalent à votre requête having
. Il pourrait être intéressant de jeter un œil aux lignes où num
est égal à NULL. Soyez plus précis: jetez un œil aux effets des lignes avec des valeurs nulles , que vous voudrez peut-être filtrer en premier avec un where num is not null
)
De là, vous pouvez obtenir le résultat souhaité en:
select * from t where (num, text) in ( *insert query above* )
Ou en utilisant une jointure:
select t1.* from t t1,
(select text, min(num) as n from t group by text) t2
where t1.num = t2.n and t1.text = t2.text.
Et lorsque les performances ne sont pas suffisantes pour vos tables, commencez à examiner des instructions plus complexes.