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

StackOverflow https://stackoverflow.com/questions/243782

  •  04-07-2019
  •  | 
  •  

Question

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 à extraire 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'

Alors

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 de mon résultat pour pouvoir allouer un tableau avant d'affecter des informations à il. 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 compte se produit
  • Une autre instruction survient, ajouter ou supprimer une ligne
  • SELECT pour que les données se produisent et, tout à coup, la taille du tableau est incorrecte.
      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?

Aussi, 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 l'ont demandé, j'utilise Native C ++ avec le pilote SQL susmentionné (fourni par Microsoft).

Était-ce utile?

La solution

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

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

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

Autres conseils

Si vous utilisez SQL Server, vous pouvez sélectionner, après votre requête, le @@ RowCount (ou si votre jeu de résultats peut comporter plus de 2 milliards de lignes), utilisez la RowCount_Big () ). 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 insert / update / delete.

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

SELECT @@Rowcount

Ou si vous souhaitez que le nombre de lignes inclus dans le résultat envoyé soit similaire à l'Approche n ° 2, vous pouvez utiliser le 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 donnera 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 j’ai donné donne le nombre de lignes de 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 que my_table.foo. Comme votre requête exemple contenait la clause "WHERE my_table.foo = 'bar'", toutes les lignes du jeu de résultats auront la même valeur que my_table.foo et, par conséquent, la valeur de la colonne sera la même pour toutes les lignes et égale à (dans ce cas), cela correspond au nombre de lignes de la requête.

Voici un exemple meilleur / plus simple d'inclusion d'une colonne dans chaque ligne représentant le nombre total de lignes dans le jeu de résultats. Supprimez simplement la clause optionnelle Partition By.

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

L’approche 2 renvoie toujours un nombre qui correspond à votre jeu de résultats.

Je vous suggère toutefois de lier la sous-requête à votre requête externe afin de 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';

Si vous êtes préoccupé par le fait que le nombre de lignes répondant à la condition peut changer en quelques millisecondes depuis l'exécution de la requête et la récupération des résultats, vous pouvez / devriez exécuter les requêtes dans 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

Ceci renverrait 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 le résultat de la requête real vers une table temporaire ou une table. variable, vous pouvez donc 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

Voici quelques idées:

  • Adoptez l’approche n ° 1 et redimensionnez le tableau afin de conserver des résultats supplémentaires ou utilisez un type qui redimensionne automatiquement au besoin (vous ne précisez pas la langue que vous utilisez, je ne peux donc pas être plus spécifique).
  • Vous pouvez exécuter les deux instructions de l'approche n ° 1 au sein d'une transaction pour garantir que les comptages sont identiques à la fois si votre base de données le permet.
  • Je ne sais pas ce que vous faites avec les données, mais s’il est possible de traiter les résultats sans les stocker au préalable, cela pourrait être la meilleure méthode.

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 sélectionner d'abord vos lignes dans une table temporaire? De cette façon, vous savez que vous serez synchronisé.

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

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

Aucun pilote SQL à prévision automatique ne vous indiquera le nombre de lignes que votre requête retournera avant de les renvoyer, car la réponse pourrait changer (sauf si vous utilisez une transaction, ce qui crée des problèmes qui lui sont propres.)

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

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

Juste pour ajouter ceci car il s'agit du premier résultat de Google pour cette question. Dans sqlite, je l’ai utilisé 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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top