Seleccione n filas aleatorias de la tabla de SQL Server


309

Tengo una tabla de SQL Server con aproximadamente 50,000 filas. Quiero seleccionar alrededor de 5,000 de esas filas al azar. He pensado en una forma complicada: crear una tabla temporal con una columna de "número aleatorio", copiar mi tabla en eso, recorrer la tabla temporal y actualizar cada fila con RAND(), y luego seleccionar de esa tabla donde está la columna de números aleatorios < 0.1. Estoy buscando una manera más simple de hacerlo, en una sola declaración si es posible.

Este artículo sugiere usar la NEWID()función. Eso parece prometedor, pero no puedo ver cómo podría seleccionar de manera confiable un cierto porcentaje de filas.

¿Alguien ha hecho esto antes? ¿Algunas ideas?


3
MSDN tiene un buen artículo que cubre muchos de estos problemas: Selección de filas al azar de una tabla grande
KyleMit

Respuestas:


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

En respuesta al comentario de "basura pura" sobre tablas grandes: podría hacerlo así para mejorar el rendimiento.

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

El costo de esto será la exploración de valores clave más el costo de unión, que en una tabla grande con una pequeña selección porcentual debería ser razonable.


1
Me gusta este enfoque mucho mejor que usar el artículo al que hizo referencia.
JoshBerke

14
Siempre es bueno tener en cuenta que newid () no es un generador de números pseudoaleatorios realmente bueno, al menos no tan bueno como rand (). Pero si solo necesita algunas muestras vagamente aleatorias y no le importan las cualidades matemáticas y demás, será lo suficientemente bueno. De lo contrario, necesita: stackoverflow.com/questions/249301/…
user12861

1
Lo siento si esto es obvio ... pero ¿a qué se [yourPk]refiere? EDITAR: Nvm, lo descubrí ... Clave primaria. Durrr
Snailer

44
newid - guid está diseñado para ser único pero no aleatorio ... enfoque incorrecto
Brans Ds

2
con un gran número de filas, por ejemplo, más de 1 millón newid(). El costo estimado de E / S será muy alto y afectará el rendimiento.
aadi1295

81

Dependiendo de sus necesidades, TABLESAMPLEobtendrá un rendimiento casi tan aleatorio y mejor. Esto está disponible en el servidor MS SQL 2005 y posterior.

TABLESAMPLE devolverá datos de páginas aleatorias en lugar de filas aleatorias y, por lo tanto, ni siquiera recuperará datos que no devolverá.

En una mesa muy grande probé

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

tomó más de 20 minutos.

select * from [tablename] tablesample(1 percent)

tomó 2 minutos

El rendimiento también mejorará en muestras más pequeñas, TABLESAMPLEmientras que no lo hará con newid().

Tenga en cuenta que esto no es tan aleatorio como el newid()método, pero le dará una muestra decente.

Ver la página de MSDN .


77
Como se ha señalado por Rob Boek continuación, tablesampling grumos resultados, y por lo tanto no es una buena manera de conseguir un pequeño número de resultados aleatorios
Oskar Austegard

Le importa la pregunta de cómo funciona esto: seleccione el 1% superior * del orden [nombre de tabla] por newid () ya que newid () no es una columna en el [nombre de tabla]. ¿El servidor sql agrega internamente la columna newid () en cada fila y luego realiza una ordenación?
FrenkyB

La muestra de tabla fue la mejor respuesta para mí, ya que estaba haciendo una consulta compleja en una tabla muy grande. No hay duda de que fue notablemente rápido. Obtuve una variación en el número de registros devueltos cuando lo ejecuté varias veces, pero todos estaban dentro de un margen de error aceptable.
jessier3

38

newid () / order by funcionará, pero será muy costoso para grandes conjuntos de resultados porque tiene que generar una identificación para cada fila y luego ordenarlos.

TABLESAMPLE () es bueno desde el punto de vista del rendimiento, pero obtendrá una agrupación de resultados (se devolverán todas las filas de una página).

Para obtener una muestra aleatoria verdadera con mejor rendimiento, la mejor manera es filtrar las filas al azar. Encontré el siguiente ejemplo de código en el artículo de los Libros en pantalla de SQL Server Limitar los conjuntos de resultados mediante TABLESAMPLE :

Si realmente desea una muestra aleatoria de filas individuales, modifique su consulta para filtrar las filas al azar, en lugar de usar TABLESAMPLE. Por ejemplo, la siguiente consulta usa la función NEWID para devolver aproximadamente el uno por ciento de las filas de la tabla Sales.SalesOrderDetail:

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

La columna SalesOrderID se incluye en la expresión CHECKSUM para que NEWID () evalúe una vez por fila para lograr el muestreo por fila. La expresión CAST (CHECKSUM (NEWID (), SalesOrderID) y 0x7fffffff AS float / CAST (0x7fffffff AS int) se evalúa como un valor flotante aleatorio entre 0 y 1.

Cuando se ejecuta contra una tabla con 1,000,000 de filas, aquí están mis resultados:

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 puede salirse con la suya usando TABLESAMPLE, le dará el mejor rendimiento. De lo contrario, use el método newid () / filter. newid () / order by debería ser el último recurso si tiene un gran conjunto de resultados.


También vi ese artículo y probándolo en mi código, parece que NewID()se evalúa solo una vez, en lugar de por fila, lo que no me gusta ...
Andrew Mao

23

La selección de filas al azar de una tabla grande en MSDN tiene una solución simple y bien articulada que aborda los problemas de rendimiento a gran escala.

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

Muy interesante. Después de leer el artículo, realmente no entiendo por qué RAND()no devuelve el mismo valor para cada fila (lo que anularía la BINARY_CHECKSUM()lógica). ¿Es porque se llama dentro de otra función en lugar de ser parte de la cláusula SELECT?
John M Gant

Esta consulta se ejecutó en una tabla con filas de 6MM en menos de un segundo.
Mark Melville el

2
Ejecuté esta consulta en una tabla con 35 entradas y seguí teniendo dos de ellas en el conjunto de resultados con mucha frecuencia. Esto podría ser un problema rand()o una combinación de lo anterior, pero me alejé de esta solución por ese motivo. Además, el número de resultados varió de 1 a 5, por lo que esto podría no ser aceptable en algunos escenarios.
Oliver

¿RAND () no devuelve el mismo valor para cada fila?
Zarzaparrilla

RAND()devuelve el mismo valor para cada fila (razón por la cual esta solución es rápida). Sin embargo, las filas con sumas de verificación binarias que están muy juntas tienen un alto riesgo de generar resultados de suma de verificación similares, lo que causa agrupamiento cuando RAND()es pequeño. Por ejemplo, (ABS(CAST((BINARY_CHECKSUM(111,null,null) * 0.1) as int))) % 100== SELECT (ABS(CAST((BINARY_CHECKSUM(113,null,null) * 0.1) as int))) % 100. Si sus datos padecen este problema, multiplique BINARY_CHECKSUMpor 9923.
Brian

12

Este enlace tiene una comparación interesante entre Orderby (NEWID ()) y otros métodos para tablas con 1, 7 y 13 millones de filas.

A menudo, cuando se hacen preguntas sobre cómo seleccionar filas aleatorias en grupos de discusión, se propone la consulta NEWID; Es simple y funciona muy bien para mesas pequeñas.

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

Sin embargo, la consulta NEWID tiene un gran inconveniente cuando la usa para tablas grandes. La cláusula ORDER BY hace que todas las filas de la tabla se copien en la base de datos tempdb, donde se ordenan. Esto causa dos problemas:

  1. La operación de clasificación generalmente tiene un alto costo asociado. La ordenación puede usar muchas E / S de disco y puede ejecutarse durante mucho tiempo.
  2. En el peor de los casos, tempdb puede quedarse sin espacio. En el mejor de los casos, tempdb puede ocupar una gran cantidad de espacio en disco que nunca será reclamado sin un comando de reducción manual.

Lo que necesita es una forma de seleccionar filas al azar que no usen tempdb y no se volverán mucho más lentas a medida que la tabla se agrande. Aquí hay una nueva idea sobre cómo hacer eso:

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

La idea básica detrás de esta consulta es que queremos generar un número aleatorio entre 0 y 99 para cada fila de la tabla, y luego elegir todas esas filas cuyo número aleatorio es menor que el valor del porcentaje especificado. En este ejemplo, queremos aproximadamente el 10 por ciento de las filas seleccionadas al azar; por lo tanto, elegimos todas las filas cuyo número aleatorio es menor que 10.

Por favor, lea el artículo completo en MSDN .


2
Hola, Deumber, bonito hallazgo, podrías desarrollarlo, ya que es probable que las respuestas de solo enlace se eliminen.
bummi

1
@bummi Lo cambié para evitar ser solo enlace respuesta :)
QMaster

Esta es la mejor respuesta. 'ORDER BY NEWID ()' funciona en la mayoría de los casos (tablas más pequeñas), pero como los puntos de referencia en el enlace refrenced muestran claramente que se queda atrás a medida que la tabla crece
pedram bashiri

10

Si (a diferencia del OP) necesita un número específico de registros (lo que dificulta el enfoque CHECKSUM) y desea una muestra más aleatoria que TABLESAMPLE por sí mismo, y también desea una mejor velocidad que CHECKSUM, puede hacerlo con una fusión de Métodos TABLESAMPLE y NEWID (), como este:

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

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

SET STATISTICS TIME OFF

En mi caso, este es el compromiso más directo entre la aleatoriedad (no es realmente, lo sé) y la velocidad. Varíe el porcentaje (o filas) de TABLESAMPLE según corresponda: cuanto mayor sea el porcentaje, más aleatoria será la muestra, pero se espera una disminución lineal de la velocidad. (Tenga en cuenta que TABLESAMPLE no aceptará una variable)


9

Simplemente ordene la tabla por un número aleatorio y obtenga las primeras 5,000 filas usando TOP.

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

ACTUALIZAR

Solo lo probé y una newid()llamada es suficiente: no es necesario tener todos los yesos y todas las matemáticas.


10
La razón por la que se usa 'todos los moldes y todas las matemáticas' es para un mejor rendimiento.
hkf

6

Esta es una combinación de la idea inicial y una suma de verificación, que me parece que da resultados aleatorios sin el costo de NEWID ():

SELECT TOP [number] 
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())

3

En MySQL puedes hacer esto:

SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;

3
Esto no funcionará. Como la instrucción select es atómica, solo toma un número aleatorio y lo duplica para cada fila. Tendría que reiniciarlo en cada fila para forzarlo a cambiar.
Tom H

44
Mmm ... me encantan las diferencias entre vendedores. Select es atómico en MySQL, pero supongo que de una manera diferente. Esto funcionará en MySQL.
Jeff Ferland

2

Todavía no vi esta variación en las respuestas. Tenía una restricción adicional donde necesitaba, dada una semilla inicial, para seleccionar el mismo conjunto de filas cada vez.

Para MS SQL:

Ejemplo mínimo:

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

Tiempo de ejecución normalizado: 1.00

Ejemplo de NewId ():

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

Tiempo de ejecución normalizado: 1.02

NewId()es insignificantemente más lento que rand(checksum(*)), por lo que es posible que no desee utilizarlo contra grandes conjuntos de registros.

Selección con semilla inicial:

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 necesita seleccionar el mismo conjunto dado una semilla, esto parece funcionar.


¿Hay alguna ventaja de usar @seed especial contra RAND ()?
QMaster

absolutamente, Usó el parámetro semilla y lo completó por parámetro de fecha, la función RAND () hace lo mismo, excepto que usa el valor de tiempo completo, quiero saber ¿tiene alguna ventaja usar un parámetro creado útil como semilla arriba de RAND () o no?
QMaster

Ah! OK, este era un requisito del proyecto. Necesitaba generar una lista de filas n-aleatorias de manera determinista. Básicamente, el liderazgo quería saber qué filas "aleatorias" estaríamos seleccionando unos días antes de que las filas fueran seleccionadas y procesadas. Al crear un valor inicial basado en el año / mes, podría garantizar que cualquier llamada a la consulta ese año devolvería la misma lista "aleatoria". Lo sé, era extraño y probablemente había mejores maneras, pero funcionó ...
klyd

HAHA :) Ya veo, pero creo que el significado general de los registros seleccionados al azar no son los mismos registros en diferentes consultas en ejecución.
QMaster


0

Parece que newid () no se puede usar en la cláusula where, por lo que esta solución requiere una consulta interna:

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

0

Lo estaba usando en subconsulta y me devolvió las mismas filas en subconsulta

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

entonces resolví con incluir la variable de tabla principal en donde

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

Tenga en cuenta la condición donde


0

El lenguaje de procesamiento del lado del servidor en uso (por ejemplo, PHP, .net, etc.) no se especifica, pero si es PHP, tome el número requerido (o todos los registros) y, en lugar de aleatorizar en la consulta, use la función aleatoria de PHP. No sé si .net tiene una función equivalente, pero si la tiene, úsela si está usando .net

ORDER BY RAND () puede tener una gran penalización de rendimiento, dependiendo de cuántos registros estén involucrados.


No recuerdo exactamente para qué estaba usando esto en ese momento, pero probablemente estaba trabajando en C #, tal vez en un servidor o tal vez en una aplicación cliente, no estoy seguro. C # no tiene nada directamente comparable con el afaik aleatorio de PHP, pero podría hacerse aplicando funciones desde el objeto Aleatorio dentro de una operación Seleccionar, ordenando el resultado y luego tomando el diez por ciento superior. Pero tendríamos que leer toda la tabla del disco en el servidor DB y transmitirla a través de la red, solo para descartar el 90% de esos datos. Procesarlo directamente en el DB es casi seguro que sea más eficiente.
John M Gant

-2

Esto funciona para mi:

SELECT * FROM table_name
ORDER BY RANDOM()
LIMIT [number]

99
@ user537824, ¿probaste eso en SQL Server? RANDOM no es una función y LIMIT no es una palabra clave. La sintaxis de SQL Server para lo que está haciendo sería select top 10 percent from table_name order by rand(), pero eso tampoco funciona porque rand () devuelve el mismo valor en todas las filas.
John M Gant
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.