web-dev-qa-db-fra.com

Besoin d'un nombre de lignes après l'instruction SELECT: quelle est l'approche SQL optimale?

J'essaie de sélectionner une colonne dans une seule table (pas de jointures) et j'ai besoin du nombre de lignes, idéalement avant de commencer à récupérer les lignes. Je suis arrivé à deux approches qui fournissent les informations dont j'ai besoin.

Approche 1:

SELECT COUNT( my_table.my_col ) AS row_count
  FROM my_table
 WHERE my_table.foo = 'bar'

Ensuite

SELECT my_table.my_col
  FROM my_table
 WHERE my_table.foo = 'bar'

Ou approche 2

SELECT my_table.my_col, ( SELECT COUNT ( my_table.my_col )
                            FROM my_table
                           WHERE my_table.foo = 'bar' ) AS row_count
  FROM my_table
 WHERE my_table.foo = 'bar'

Je le fais parce que mon pilote SQL (SQL Native Client 9.0) ne me permet pas d'utiliser SQLRowCount sur une instruction SELECT mais j'ai besoin de connaître le nombre de lignes dans mon résultat afin d'allouer un tableau avant de lui affecter des informations. L'utilisation d'un conteneur alloué dynamiquement n'est malheureusement pas une option dans ce domaine de mon programme.

Je crains que le scénario suivant ne se produise:

  • SELECT pour le décompte se produit
  • Une autre instruction se produit, ajoutant ou supprimant une ligne
  • SELECT pour les données se produit et soudain, le tableau a la mauvaise taille.
    - Dans le pire des cas, cela tentera d'écrire des données au-delà des limites des tableaux et de planter mon programme.

L'approche 2 interdit-elle ce problème?

De plus, l'une des deux approches sera-t-elle plus rapide? Si oui, lequel?

Enfin, existe-t-il une meilleure approche que je devrais envisager (peut-être un moyen de demander au pilote de renvoyer le nombre de lignes dans un résultat SELECT à l'aide de SQLRowCount?)

Pour ceux qui ont demandé, j'utilise Native C++ avec le pilote SQL susmentionné (fourni par Microsoft.)

31
antik

Il n'y a que deux façons d'être certain à 100% que la COUNT(*) et la requête réelle donneront des résultats cohérents:

  • Combiné la COUNT(*) avec la requête, comme dans votre approche 2. Je recommande le formulaire que vous montrez dans votre exemple, pas le formulaire de sous-requête corrélée montré dans le commentaire de kogus.
  • Utilisez deux requêtes, comme dans votre approche 1, après avoir démarré une transaction au niveau d'isolation SNAPSHOT ou SERIALIZABLE.

L'utilisation de l'un de ces niveaux d'isolement est importante car tout autre niveau d'isolement permet aux nouvelles lignes créées par d'autres clients de devenir visibles dans votre transaction actuelle. Lisez la documentation MSDN sur SET TRANSACTION ISOLATION pour plus de détails.

16
Bill Karwin

Si vous utilisez SQL Server, après votre requête, vous pouvez sélectionner la fonction @@ RowCount (ou si votre jeu de résultats peut contenir plus de 2 milliards de lignes, utilisez RowCount_Big () = fonction). Cela renverra le nombre de lignes sélectionnées par l'instruction précédente ou le nombre de lignes affectées par une instruction d'insertion/mise à jour/suppression.

SELECT my_table.my_col
  FROM my_table
 WHERE my_table.foo = 'bar'

SELECT @@Rowcount

Ou si vous voulez que le nombre de lignes inclus dans le résultat envoyé soit similaire à l'approche n ° 2, vous pouvez utiliser la clause OVER .

SELECT my_table.my_col,
    count(*) OVER(PARTITION BY my_table.foo) AS 'Count'
  FROM my_table
 WHERE my_table.foo = 'bar'

L'utilisation de la clause OVER aura de bien meilleures performances que l'utilisation d'une sous-requête pour obtenir le nombre de lignes. L'utilisation de @@ RowCount aura les meilleures performances car il n'y aura aucun coût de requête pour l'instruction select @@ RowCount

Mise à jour en réponse au commentaire: L'exemple que je donne donnerait le # de lignes dans la partition - défini dans ce cas par "PARTITION BY my_table.foo". La valeur de la colonne dans chaque ligne est le # de lignes avec la même valeur de my_table.foo. Étant donné que votre exemple de requête avait la clause "WHERE my_table.foo = 'bar'", toutes les lignes de l'ensemble de résultats auront la même valeur de my_table.foo et par conséquent, la valeur dans la colonne sera la même pour toutes les lignes et égale (dans ce cas) c'est le # de lignes dans la requête.

Voici un exemple meilleur/plus simple de la façon d'inclure une colonne dans chaque ligne qui est le nombre total de lignes dans le jeu de résultats. Supprimez simplement la clause facultative Partition By.

SELECT my_table.my_col, count(*) OVER() AS 'Count'
  FROM my_table
 WHERE my_table.foo = 'bar'
29
Adam Porad

Si vous êtes préoccupé par le nombre de lignes qui remplissent la condition peut changer dans les quelques millisecondes depuis l'exécution de la requête et la récupération des résultats, vous pouvez/devez exécuter les requêtes à l'intérieur d'une transaction:

BEGIN TRAN bogus

SELECT COUNT( my_table.my_col ) AS row_count
FROM my_table
WHERE my_table.foo = 'bar'

SELECT my_table.my_col
FROM my_table
WHERE my_table.foo = 'bar'
ROLLBACK TRAN bogus

Cela retournerait toujours les valeurs correctes.

De plus, si vous utilisez SQL Server, vous pouvez utiliser @@ ROWCOUNT pour obtenir le nombre de lignes affectées par la dernière instruction, et rediriger la sortie de la requête real vers une table temporaire ou une variable de table, donc vous pouvez tout renvoyer, sans avoir besoin d'une transaction:

DECLARE @dummy INT

SELECT my_table.my_col
INTO #temp_table
FROM my_table
WHERE my_table.foo = 'bar'

SET @dummy=@@ROWCOUNT
SELECT @dummy, * FROM #temp_table
3
Joe Pineda

L'approche 2 renverra toujours un nombre qui correspond à votre ensemble de résultats.

Je vous suggère cependant de lier la sous-requête à votre requête externe, pour garantir que la condition de votre compte correspond à la condition de l'ensemble de données.

SELECT 
  mt.my_row,
 (SELECT COUNT(mt2.my_row) FROM my_table mt2 WHERE mt2.foo = mt.foo) as cnt
FROM my_table mt
WHERE mt.foo = 'bar';
3
JosephStyons

Voici quelques idées:

  • Allez avec l'approche n ° 1 et redimensionnez le tableau pour contenir des résultats supplémentaires ou utilisez un type qui se redimensionne automatiquement comme nécessaire (vous ne mentionnez pas la langue que vous utilisez, donc je ne peux pas être plus précis).
  • Vous pouvez exécuter les deux instructions dans l'approche n ° 1 au sein d'une transaction pour garantir que les nombres sont les deux fois identiques si votre base de données le prend en charge.
  • Je ne suis pas sûr de ce que vous faites avec les données, mais s'il est possible de traiter les résultats sans les stocker tous au préalable, cela pourrait être la meilleure méthode.
1
Robert Gamble

Si vous êtes vraiment préoccupé par le fait que votre nombre de lignes changera entre le nombre sélectionné et l'instruction select, pourquoi ne pas d'abord sélectionner vos lignes dans une table temporaire? De cette façon, vous savez que vous serez synchronisé.

1
BoltBait
IF (@@ROWCOUNT > 0)
BEGIN
SELECT my_table.my_col
  FROM my_table
 WHERE my_table.foo = 'bar'
END
0
Deepfreezed

Pourquoi ne mettez-vous pas vos résultats dans un vecteur? De cette façon, vous n'avez pas besoin de connaître la taille à l'avance.

0
jonnii

Juste pour ajouter ceci car c'est le meilleur résultat dans google pour cette question. Dans sqlite, j'ai utilisé cela pour obtenir le nombre de lignes.

WITH temptable AS
  (SELECT one,two
   FROM
     (SELECT one, two
      FROM table3
      WHERE dimension=0
      UNION ALL SELECT one, two
      FROM table2
      WHERE dimension=0
      UNION ALL SELECT one, two
      FROM table1
      WHERE dimension=0)
   ORDER BY date DESC)
SELECT *
FROM temptable
LEFT JOIN
  (SELECT count(*)/7 AS cnt,
                        0 AS bonus
   FROM temptable) counter
WHERE 0 = counter.bonus
0
Tschallacka

Vous voudrez peut-être réfléchir à un meilleur modèle pour traiter les données de ce type.

Aucun pilote SQL auto-détecté ne vous dira combien de lignes votre requête renverra avant de renvoyer les lignes, car la réponse peut changer (sauf si vous utilisez une transaction, ce qui crée ses propres problèmes).

Le nombre de lignes ne changera pas - google pour ACID et SQL.

0
dkretz