web-dev-qa-db-fra.com

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 rangées au hasard. J'ai pensé à un moyen compliqué: créer une table temporaire avec une colonne "nombre aléatoire", y copier ma table, parcourir la table temporaire, mettre à jour chaque ligne avec Rand() <0.1. Je cherche un moyen plus simple de le faire, en une seule déclaration si possible.

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

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

275
John M Gant
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 la jointure, ce qui sur une grande table avec un pourcentage de sélection faible devrait être raisonnable.

345

En fonction de vos besoins, TABLESAMPLE vous proposera presque aussi aléatoire et de meilleures performances . Ceci est disponible sur MS SQL Server 2005 et versions ultérieures. 

TABLESAMPLE renverra des données de pages aléatoires au lieu de lignes aléatoires. Par conséquent, deos ne récupérera même pas les données qu’elles ne renverront 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 que ce ne sera pas avec newid().

Veuillez garder à l’esprit que ce n’est pas aussi aléatoire que la méthode newid() mais vous donnera un bon échantillonnage.

Voir la page MSDN .

70
Patrick Taylor

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

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

Pour obtenir un échantillon véritablement aléatoire plus performant, le meilleur moyen consiste à filtrer les lignes de manière aléatoire. J'ai trouvé l'exemple de code suivant dans l'article LIMITATION DES ENSEMBLES DE R&EACUTE;SULTATS &AGRAVE; L'AIDE DE TABLESAMPLE de la documentation en ligne de SQL Server:

Si vous voulez vraiment un échantillon aléatoire de lignes individuelles, modifiez votre requête en filtrer les lignes au hasard, au lieu de en utilisant TABLESAMPLE. Par exemple, le La requête suivante utilise le NEWID fonction pour retourner 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 CHECKSUM pour que NEWID () évalue une fois par ligne à réaliser un échantillonnage par rangée . L'expression CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) donne à .__ une valeur float aléatoire comprise entre 0 et 1.

Lorsqu’il est exécuté sur une table de 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 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 ensemble de résultats important.

37
Rob Boek

Sélection aléatoire de lignes dans une grande table sur MSDN, vous disposez d'une solution simple, bien articulée, qui répond aux problèmes de performances à grande échelle.

  SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  Rand()) as int)) % 100) < 10
21
Kyle McClellan

Si vous (contrairement à l'OP) avez besoin d'un nombre spécifique d'enregistrements (ce qui complique l'approche de CHECKSUM) et souhaitez un échantillon plus aléatoire que TABLESAMPLE en tant que tel, et souhaitez également une vitesse supérieure à celle de CHECKSUM, vous pouvez vous contenter d'une fusion des Les 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, il s’agit du compromis le plus simple entre le caractère aléatoire (ce n’est pas vraiment, je le sais) et la vitesse. Variez le pourcentage de TABLESAMPLE (ou les lignes) selon vos besoins - 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)

9
Oskar Austegard

Commandez simplement la table par un nombre aléatoire et obtenez les 5 000 premières lignes en utilisant TOP.

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

METTRE &AGRAVE; JOUR

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

8
Daniel Brückner

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

Souvent, lorsque des questions sur la sélection de 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()

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

  1. Le coût de revient associé à l'opération de tri est généralement élevé ..__ Le tri peut utiliser beaucoup d'E/S de disque et peut durer longtemps.
  2. Dans le pire des cas, tempdb peut manquer d'espace. Dans le meilleur des cas, tempdb peut occuper une grande quantité d'espace disque Qui ne sera jamais récupéré sans commande de réduction manuelle.

Ce dont vous avez besoin est un moyen de sélectionner des lignes de manière aléatoire, sans utiliser tempdb et avec le ralentissement de la taille du tableau. 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 est de générer un nombre aléatoire compris entre 0 et 99 pour chaque ligne du tableau, puis de sélectionner toutes les lignes dont le nombre aléatoire est inférieur à la valeur du pourcentage spécifié. Dans cet exemple, nous voulons environ 10% 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

8
RJardines

Dans MySQL, vous pouvez faire ceci:

SELECT `PRIMARY_KEY`, Rand() FROM table ORDER BY Rand() LIMIT 5000;
4
Jeff Ferland

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

SELECT TOP [number] 
FROM table_name
ORDER BY Rand(CHECKSUM(*) * Rand())
4
Nanki

Je n'ai pas encore vu cette variation dans les réponses. J'avais une contrainte supplémentaire où il me fallait, à partir d'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 très légèrement plus lent que Rand(checksum(*)), vous pouvez donc ne pas vouloir l'utiliser pour des jeux d'enregistrements volumineux.

Sélection avec la graine initiale:

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 à partir d'une graine, cela semble fonctionner.

2
klyd

Essaye ça:

SELECT TOP 10 Field1, ..., FieldN
FROM Table1
ORDER BY NEWID()
2
Ravi Parashar

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

SELECT *
FROM (
    SELECT *, ABS(CHECKSUM(NEWID())) AS Rnd
    FROM MyTable
) vw
WHERE Rnd % 100 < 10        --10%
0
Hai Phan

Je l'utilisais dans une sous-requête et il me renvoyait les mêmes lignes dans une sous-requête

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

alors j'ai résolu avec notamment la variable de table parent dans où

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
VISHMAY

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, saisissez le nombre requis (ou tous les enregistrements) et utilisez la fonction de lecture aléatoire de PHP au lieu de la randomisation dans la requête. Je ne sais pas si .net a une fonction équivalente, mais si c'est le cas, utilisez-le si vous utilisez .net

ORDER BY Rand () peut être très pénalisant en termes de performances, selon le nombre d'enregistrements impliqués.

0
SpacePhoenix