Sélectionnez n lignes aléatoires dans la table SQL Server

J'ai une table SQL Server avec environ 50 000 lignes. Je veux sélectionner environ 5 000 de ces lignes au hasard. J'ai pensé à une manière compliquée, en créant une table temporaire avec une colonne "nombre aléatoire", en copiant ma table dans celle-ci, en faisant une boucle dans la table temporaire et en mettant à jour chaque ligne avec RAND(), puis en sélectionnant à partir de cette table où la colonne de nombre aléatoire

Cet article suggérons d'utiliser la fonction NEWID(). 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?

270
demandé sur Peter O. 2009-05-11 20:19:11

15 réponses

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

En réponse au commentaire "pure trash" concernant les grandes tables: vous pouvez le faire comme ceci 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 ceci sera l'analyse clé des valeurs plus le coût de jointure, qui sur une grande table avec un petit pourcentage de sélection devrait être raisonnable.

335
répondu Ralph Shillington 2011-02-04 21:11:18

Selon vos besoins, TABLESAMPLE vous obtiendrez des performances presque aussi aléatoires et meilleures. ceci est disponible sur MS SQL server 2005 et versions ultérieures.

TABLESAMPLE retournera des données à partir de pages aléatoires au lieu de lignes aléatoires et donc deos ne récupérera 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 les échantillons plus petits dans TABLESAMPLE alors qu'elles ne le seront pas avec newid().

Gardez à l'esprit que ce n'est pas aussi aléatoire que la méthode newid() mais vous donnera un échantillonnage décent.

Voir la page MSDN .

65
répondu Patrick Taylor 2012-07-08 21:26:33

Newid () / order by fonctionnera, mais sera très coûteux pour les grands ensembles de résultats car il doit générer un id 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 un échantillon aléatoire vrai plus performant, le meilleur moyen est de filtrer les lignes au hasard. J'ai trouvé l'exemple de code suivant dans L'article SQL Server Books Onlinelimiter 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 au hasard, au lieu de utilisation de TABLESAMPLE. Par exemple, l' la requête suivante utilise le NEWID fonction pour renvoyer environ un pourcentage des lignes de l' Commerciale.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 de somme de contrôle de sorte que Newid() évalue une fois par ligne à réaliser l'échantillonnage sur un par ligne. L'expression CAST(CHECKSUM (NEWID(), SalesOrderID) & 0x7fffffff comme flottant / CAST (0x7fffffff comme int) évalue à une valeur flottante aléatoire comprise entre 0 et 1.

Lorsqu'il est exécuté sur une table avec 1 000 000 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 avec TABLESAMPLE, il vous donnera les meilleures performances. Sinon, utilisez la méthode newid()/filter. newid()/order by devrait être le dernier recours si vous avez un grand jeu de résultats.

35
répondu Rob Boek 2009-05-28 18:15:18

La sélection aléatoire de lignes à partir d'une grande Table sur MSDN a 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
20
répondu Kyle McClellan 2012-09-05 22:23:02

Si vous (contrairement à L'OP) avez besoin d'un nombre spécifique d'enregistrements (ce qui rend l'approche CHECKSUM difficile) et désirez un échantillon plus aléatoire que TABLESAMPLE ne fournit par lui-même, et 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 hasard (ce n'est pas vraiment, je sais) et la vitesse. Variez le pourcentage D'échantillon de tableau (ou les lignes) selon le cas - 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 une variable)

9
répondu Oskar Austegard 2012-09-26 17:23:46

Il suffit de commander la table par un nombre aléatoire et d'obtenir les 5 000 premières lignes en utilisant TOP.

SELECT TOP 5000 * FROM [Table] ORDER BY newid();

Mise à jour

Juste essayé et un appel newid() est suffisant-pas besoin de tous les moulages et tous les calculs.

8
répondu Daniel Brückner 2009-05-11 16:31:11

Ce lien a une comparaison intéressante entre Orderby (NEWID ()) et d'autres méthodes pour les tables avec 1, 7 et 13 millions de lignes.

Souvent, lorsque des questions sur la façon de sélectionner des lignes aléatoires sont posées dans des groupes de discussion, la requête NEWID est proposée; c'est simple et fonctionne très bien pour les petites tables.

SELECT TOP 10 PERCENT *
  FROM Table1
  ORDER BY NEWID()

Cependant, la requête NEWID présente un gros inconvénient lorsque vous l'utilisez pour de grandes tables. La clause ORDER BY entraîne la copie de toutes les lignes de la table dans la tempdb la base de données, où ils sont triés. Cela provoque deux problèmes:

  1. L'opération de tri a généralement un coût élevé associé. Le tri peut utiliser beaucoup d'e / s de disque et peut fonctionner pendant une longue période.
  2. dans le pire des cas, tempdb peut manquer d'espace. Dans le dans le meilleur des cas, tempdb peut prendre une grande quantité d'espace disque cela ne sera jamais récupéré sans une commande manuelle de rétrécissement.

Ce dont vous avez besoin est un moyen de sélectionner des lignes au hasard qui ne seront pas utilisées tempdb et ne sera pas beaucoup plus lent que la table devient plus grande. Voici une nouvelle idée sur la façon de le faire:

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

L'idée de base de cette requête, c'est que nous voulons générer un nombre aléatoire entre 0 et 99 pour chaque ligne du tableau, puis sélectionnez toutes les lignes dont le nombre aléatoire est inférieur à la valeur spécifiée pour cent. Dans cet exemple, nous voulons environ 10 pour cent des lignes sélectionnées au hasard; par conséquent, nous choisissons toutes les lignes dont le nombre aléatoire est inférieur à 10.

Veuillez lire l'article complet dans MSDN .

7
répondu RJardines 2016-09-22 13:44:41

Dans MySQL, vous pouvez faire ceci:

SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;
4
répondu Jeff Ferland 2014-02-28 14:48:29

C'est une combinaison de l'idée de départ initiale et d'une somme de contrôle, qui me semble donner des résultats correctement aléatoires sans le coût de NEWID ():

SELECT TOP [number] 
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())
3
répondu Nanki 2015-10-16 17:03:52

Essayez ceci:

SELECT TOP 10 Field1, ..., FieldN
FROM Table1
ORDER BY NEWID()
2
répondu Ravi Parashar 2011-10-06 13:28:52

N'a pas encore vu cette variation dans les réponses. J'avais une contrainte supplémentaire où j'ai besoin, étant donné une graine initiale, pour sélectionner le même ensemble de lignes à chaque fois.

Pour MS SQL:

Exemple Minimum:

select top 10 percent *
from table_name
order by rand(checksum(*))

Temps D'exécution normalisé: 1.00

Newid () exemple:

select top 10 percent *
from table_name
order by newid()

Temps D'exécution normalisé: 1.02

NewId() est légèrement plus lent que rand(checksum(*)), donc vous ne voudrez peut-être pas l'utiliser contre de grands ensembles d'enregistrements.

Sélection avec initiale Semences:

declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */

select top 10 percent *
from table_name
order by rand(checksum(*) % @seed) /* any other math function here */

Si vous devez sélectionner le même ensemble donné une graine, cela semble fonctionner.

2
répondu klyd 2014-08-07 17:33:01

Cela fonctionne pour moi:

SELECT * FROM table_name
ORDER BY RANDOM()
LIMIT [number]
0
répondu Deep 2011-10-06 20:57:50

Il semble que newid () ne puisse pas être utilisé dans la clause where, donc cette solution nécessite une requête interne:

SELECT *
FROM (
    SELECT *, ABS(CHECKSUM(NEWID())) AS Rnd
    FROM MyTable
) vw
WHERE Rnd % 100 < 10        --10%
0
répondu Hai Phan 2015-05-07 22:17:37

Je l'utilisais dans la sous-requête et il m'a renvoyé les mêmes lignes dans la sous-requête

 SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

Ensuite, j'ai résolu avec l'inclusion de la variable de table parent dans where

SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              Where Mytable.ID>0
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

Notez la condition où

0
répondu VISHMAY 2017-11-10 12:40:18

Le langage de traitement côté serveur utilisé (par exemple PHP,. net, etc.) n'est pas spécifié, mais S'il s'agit de PHP, récupérez le nombre requis (ou tous les enregistrements) et, au lieu de randomiser la requête, utilisez la fonction shuffle de PHP. Je ne sais pas si. Net a une fonction équivalente mais si elle l'utilise alors si vous utilisez. net

ORDER BY RAND() peut avoir une pénalité de performance, en fonction du nombre d'enregistrements impliqués.

0
répondu SpacePhoenix 2018-06-13 06:05:42