346 votes

Sélectionner n lignes aléatoires dans une table SQL Server

J'ai une table SQL Server qui contient environ 50 000 lignes. Je veux sélectionner environ 5 000 de ces lignes de manière aléatoire. J'ai pensé à une méthode compliquée, en créant une table temporaire avec une colonne "nombre aléatoire", en copiant ma table dans cette dernière, en parcourant en boucle la table temporaire et en mettant à jour chaque ligne avec la commande RAND() puis de sélectionner dans ce tableau les cas où la colonne de nombres aléatoires est < 0,1. Je cherche un moyen plus simple de le faire, en une seule instruction si possible.

Cet article suggère d'utiliser le NEWID() fonction. Cela semble prometteur, mais je ne vois pas comment je pourrais sélectionner de manière fiable un certain pourcentage de lignes.

Quelqu'un a déjà fait ça avant ? Des idées ?

3 votes

MSDN propose un bon article qui couvre une grande partie de ces questions : Sélection aléatoire de rangs dans un grand tableau

0 votes

438voto

Ralph Shillington Points 8016
select top 10 percent * from [yourtable] order by newid()

En réponse au commentaire "pure poubelle" concernant les grandes tables : vous pouvez procéder de cette manière pour améliorer les performances.

select  * from [yourtable] where [yourPk] in 
(select top 10 percent [yourPk] from [yourtable] order by newid())

Le coût de cette opération correspondra au balayage clé des valeurs plus le coût de la jointure, ce qui, sur une grande table avec un petit pourcentage de sélection, devrait être raisonnable.

19 votes

Il est toujours bon de garder à l'esprit que newid() n'est pas un très bon générateur de nombres pseudo-aléatoires, du moins pas aussi bon que rand(). Mais si vous avez juste besoin d'échantillons vaguement aléatoires et que vous ne vous souciez pas des qualités mathématiques et autres, ce sera suffisant. Sinon, vous en avez besoin : stackoverflow.com/questions/249301/

2 votes

Um, désolé si c'est évident mais qu'est-ce qui fait que [yourPk] font-ils référence ? EDIT : Nvm, j'ai trouvé... Primary Key. Durrr

4 votes

Newid - guid est conçu pour être unique mais pas aléatoire approche incorrecte

96voto

Patrick Taylor Points 416

En fonction de vos besoins, TABLESAMPLE vous obtiendrez des résultats presque aussi aléatoires et de meilleures performances. Ceci est disponible sur MS SQL server 2005 et plus.

TABLESAMPLE renverra des données provenant de pages aléatoires plutôt que de lignes aléatoires et ne récupérera donc même pas les données qu'il ne retournera pas.

Sur une très grande table, j'ai testé

select top 1 percent * from [tablename] order by newid()

a pris plus de 20 minutes.

select * from [tablename] tablesample(1 percent)

a pris 2 minutes.

Les performances s'amélioreront également sur des échantillons plus petits dans TABLESAMPLE alors qu'il ne le fera pas avec newid() .

Veuillez garder à l'esprit que ce n'est pas aussi aléatoire que le newid() mais vous obtiendrez un bon échantillon.

Voir le Page MSDN .

7 votes

Comme l'a fait remarquer Rob Boek ci-dessous, l'échantillonnage par tableaux regroupe les résultats, et n'est donc pas un bon moyen d'obtenir une petit nombre de résultats aléatoires

0 votes

Vous vous demandez comment cela fonctionne : select top 1 percent * from [tablename] order by newid() since newid() is not a column in the [tablename]. Est-ce que le serveur sql ajoute en interne la colonne newid() sur chaque ligne et fait ensuite un tri ?

1 votes

L'exemple des tables était la meilleure réponse pour moi, car j'effectuais une requête complexe sur une très grande table. Il ne fait aucun doute qu'il a été remarquablement rapide. J'ai obtenu une variation dans le nombre d'enregistrements renvoyés, car j'ai exécuté la requête plusieurs fois, mais tous les résultats se situaient dans une marge d'erreur acceptable.

46voto

Rob Boek Points 1253

Newid()/order by fonctionnera, mais sera très coûteux pour les grands ensembles de résultats car il faut générer un identifiant pour chaque ligne, puis les trier.

TABLESAMPLE() est bon du point de vue des performances, mais vous obtiendrez des résultats groupés (toutes les lignes d'une page seront retournées).

Pour obtenir un échantillon aléatoire réel plus performant, le meilleur moyen est de filtrer les lignes de manière aléatoire. J'ai trouvé l'exemple de code suivant dans l'article SQL Server Books Online Limiter les ensembles de résultats en utilisant TABLESAMPLE :

Si vous voulez vraiment un échantillon aléatoire de lignes individuelles, modifiez votre requête pour filtrer les lignes de façon aléatoire, au lieu utiliser TABLESAMPLE. Par exemple, la requête requête suivante utilise la fonction NEWID pour renvoyer environ un pour cent des lignes de la table Sales.SalesOrderDetail :

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float)
              / CAST (0x7fffffff AS int)

La colonne SalesOrderID est incluse dans l'expression l'expression CHECKSUM afin que NEWID() est évaluée une fois par ligne à afin de réaliser un échantillonnage par ligne. L'expression CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) donne comme résultat une valeur flottante aléatoire comprise entre 0 et 1.

Lorsque je l'exécute sur une table de 1 000 000 de lignes, voici mes résultats :

SET STATISTICS TIME ON
SET STATISTICS IO ON

/* newid()
   rows returned: 10000
   logical reads: 3359
   CPU time: 3312 ms
   elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()

/* TABLESAMPLE
   rows returned: 9269 (varies)
   logical reads: 32
   CPU time: 0 ms
   elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)

/* Filter
   rows returned: 9994 (varies)
   logical reads: 3359
   CPU time: 641 ms
   elapsed time: 627 ms
*/    
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float) 
              / CAST (0x7fffffff AS int)

SET STATISTICS IO OFF
SET STATISTICS TIME OFF

Si vous pouvez vous en sortir en utilisant TABLESAMPLE, vous obtiendrez les meilleures performances. Sinon, utilisez la méthode newid()/filter. newid()/order by devrait être le dernier recours si vous avez un grand ensemble de résultats.

0 votes

J'ai vu cet article aussi et en l'essayant sur mon code, il semble que NewID() est évaluée une seule fois, au lieu de l'être par ligne, ce qui ne me plaît pas...

30voto

Kyle McClellan Points 1741

Sélection aléatoire de rangs dans un grand tableau sur MSDN propose une solution simple et bien articulée qui répond aux problèmes de performance à grande échelle.

  SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10

0 votes

Très intéressant. Après avoir lu l'article, je ne comprends pas vraiment pourquoi RAND() ne renvoie pas la même valeur pour chaque ligne (ce qui irait à l'encontre de l'objectif de l'initiative BINARY_CHECKSUM() logique). Est-ce parce qu'elle est appelée à l'intérieur d'une autre fonction plutôt que de faire partie de la clause SELECT ?

0 votes

Cette requête a été exécutée sur une table de 6 millions de lignes en moins d'une seconde.

3 votes

J'ai exécuté cette requête sur une table de 35 entrées et j'ai continué à avoir deux d'entre elles dans le jeu de résultats très souvent. Cela pourrait être un problème avec rand() ou une combinaison de ce qui précède - mais je me suis détourné de cette solution pour cette raison. En outre, le nombre de résultats varie de 1 à 5, ce qui pourrait également ne pas être acceptable dans certains scénarios.

11voto

Oskar Austegard Points 2047

Si (contrairement à l'OP) vous avez besoin d'un nombre spécifique d'enregistrements (ce qui rend l'approche CHECKSUM difficile) et que vous désirez un échantillon plus aléatoire que ce que TABLESAMPLE fournit par lui-même, et que vous voulez aussi une meilleure vitesse que CHECKSUM, vous pouvez vous contenter d'une fusion des méthodes TABLESAMPLE et NEWID(), comme ceci :

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

SELECT TOP (@sampleCount) * 
FROM [yourtable] TABLESAMPLE(10 PERCENT)
ORDER BY NEWID()

SET STATISTICS TIME OFF

Dans mon cas, c'est le compromis le plus simple entre le caractère aléatoire (ce n'est pas vraiment le cas, je sais) et la rapidité. Faites varier le pourcentage (ou les lignes) de TABLESAMPLE comme il convient - plus le pourcentage est élevé, plus l'échantillon est aléatoire, mais attendez-vous à une baisse linéaire de la vitesse. (Notez que TABLESAMPLE n'acceptera pas de variable)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X