Registro aleatorio de una tabla de base de datos (T-SQL)

85

¿Existe una forma sucinta de recuperar un registro aleatorio de una tabla de servidor SQL?

Me gustaría aleatorizar mis datos de prueba unitaria, así que estoy buscando una forma sencilla de seleccionar una identificación aleatoria de una tabla. En inglés, la selección sería "Seleccione una identificación de la tabla donde la identificación es un número aleatorio entre la identificación más baja en la tabla y la identificación más alta en la tabla".

No puedo encontrar una manera de hacerlo sin tener que ejecutar la consulta, probar un valor nulo y luego volver a ejecutar si es nulo.

Ideas?

Jeremy
fuente
hay un par de métodos aquí brettb.com/SQL_Help_Random_Numbers.asp
Mesh
2
¿Está seguro de que desea adoptar este enfoque? Los datos de la prueba unitaria no deben ser aleatorios; de hecho, se le debe garantizar que obtendrá los mismos resultados sin importar cuántas veces ejecute la prueba unitaria. Tener datos aleatorios podría violar este principio fundamental de las pruebas unitarias.
reinó
El enlace anterior de @Mesh ya no está activo.
Robert Sievers

Respuestas:

145

¿Existe una forma sucinta de recuperar un registro aleatorio de una tabla de servidor SQL?

si

SELECT TOP 1 * FROM table ORDER BY NEWID()

Explicación

Se NEWID()genera A para cada fila y luego la tabla se ordena por ella. Se devuelve el primer registro (es decir, el registro con el GUID "más bajo").

Notas

  1. Los GUID se generan como números pseudoaleatorios desde la versión cuatro:

    El UUID de la versión 4 está diseñado para generar UUID a partir de números verdaderamente aleatorios o pseudoaleatorios.

    El algoritmo es como sigue:

    • Establezca los dos bits más significativos (bits 6 y 7) de clock_seq_hi_and_reserved en cero y uno, respectivamente.
    • Configure los cuatro bits más significativos (bits 12 a 15) del campo time_hi_and_version al número de versión de 4 bits de la Sección 4.1.3.
    • Establezca todos los demás bits en valores elegidos aleatoriamente (o pseudoaleatoriamente).

    - Un espacio de nombres URN de identificador universalmente único (UUID) - RFC 4122

  2. La alternativa SELECT TOP 1 * FROM table ORDER BY RAND()no funcionará como uno pensaría. RAND()devuelve un solo valor por consulta, por lo que todas las filas compartirán el mismo valor.

  3. Si bien los valores de GUID son pseudoaleatorios, necesitará un PRNG mejor para las aplicaciones más exigentes.

  4. El rendimiento típico es de menos de 10 segundos para alrededor de 1.000.000 de filas, por supuesto, dependiendo del sistema. Tenga en cuenta que es imposible alcanzar un índice, por lo que el rendimiento será relativamente limitado.

Sklivvz
fuente
Exactamente lo que estaba buscando. Tenía la sensación de que era más sencillo de lo que lo estaba haciendo.
Jeremy
1
Está asumiendo que NEWID produce valores pseudoaleatorios. Es muy probable que produzca valores secuenciales. NEWID solo produce valores únicos. RAND, sin embargo, produce valores pseudoaleatorios.
Skizz
Lo estoy ejecutando en una tabla fuertemente indexada con 1,671,145 filas, y tarda 7 segundos en regresar. La tabla también es bastante óptima: es prácticamente el corazón de nuestra base de datos, por lo que se ocupa de ella.
Tom Ritter
@ ÂviewAnew. 1.6 millones de filas y 7 segundos en una selección que no alcanza (y no puede) un índice no está mal.
Sklivvz
7
@Skizz, rand no funciona así. Se genera UN SOLO valor aleatorio antes de SELECT. Entonces, si prueba "SELECT TOP 10 RAND () ..." siempre obtendrá el mismo valor
Sklivvz
27

En tablas más grandes, también puede usar TABLESAMPLEesto para evitar escanear toda la tabla.

SELECT  TOP 1 *
FROM YourTable
TABLESAMPLE (1000 ROWS)
ORDER BY NEWID()

El ORDER BY NEWIDsigue siendo necesaria para evitar filas que acaban de volver que aparecen por primera vez en la página de datos.

El número que se utilizará debe elegirse cuidadosamente para el tamaño y la definición de la tabla y puede considerar la lógica de reintento si no se devuelve ninguna fila. Las matemáticas detrás de esto y por qué la técnica no es adecuada para tablas pequeñas se discuten aquí.

Martin Smith
fuente
Encontré esto en el sitio web de Microsoft: Puede usar TABLESAMPLE para devolver rápidamente una muestra de una tabla grande cuando se cumpla alguna de las siguientes condiciones: La muestra no tiene que ser una muestra verdaderamente aleatoria a nivel de filas individuales. Las filas de las páginas individuales de la tabla no están correlacionadas con otras filas de la misma página.
Mark Entingh
1
@MarkEntingh - En el caso de TOP 1que no importa si las filas en la misma página están correlacionadas o no. Solo estás eligiendo uno de ellos.
Martin Smith
9

También pruebe su método para obtener una identificación aleatoria entre MIN (Id) y MAX (Id) y luego

SELECT TOP 1 * FROM table WHERE Id >= @yourrandomid

Siempre te dará una fila.

Sklivvz
fuente
2
-1, esto solo funcionaría cuando no faltan ID entre el mínimo y el máximo. Si se elimina uno, la función aleatoria genera ese mismo ID, y obtendrá cero registros.
Neil N
6
@Neil, en realidad no; obtendrá la primera fila con un Id mayor que el número aleatorio si faltan Id. El problema aquí es que la probabilidad de que salga cada fila no es constante. Pero, de nuevo, esto es suficiente en la mayoría de los casos.
Sklivvz
1
+1. Para las pruebas unitarias que deberían alcanzar diferentes valores, eso es lo suficientemente bueno: si necesita un azar real, entonces esto es otra cosa. Pero en el contexto de OP debería ser lo suficientemente bueno.
TomTom
7

Si desea seleccionar datos grandes, la mejor manera que conozco es:

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

Fuente: MSDN

hmfarimani
fuente
No estoy seguro, pero creo que usar RAND () en lugar de NEWID () para generar números verdaderamente aleatorios puede ser mejor debido a las desventajas de usar NEWID () en el proceso de selección.
QMaster
Intento usar este método con el número exacto de registros en lugar de la base porcentual, lo hice con expandir el rango de selección y limitando con TOP n, ¿hay alguna sugerencia?
QMaster
Encontré otro problema con este escenario. Si usa group by, siempre obtendrá el mismo orden de filas seleccionadas al azar, por lo que parece que en tablas pequeñas el enfoque @skilvvz es el más adecuado.
QMaster
0

Estaba buscando mejorar los métodos que había probado y encontré esta publicación. Me doy cuenta de que es antiguo, pero este método no figura en la lista. Estoy creando y aplicando datos de prueba; esto muestra el método para "dirección" en un SP llamado con @st (estado de dos caracteres)

Create Table ##TmpAddress (id Int Identity(1,1), street VarChar(50), city VarChar(50), st VarChar(2), zip VarChar(5))
Insert Into ##TmpAddress(street, city, st, zip)
Select street, city, st, zip 
From tbl_Address (NOLOCK)
Where st = @st


-- unseeded RAND() will return the same number when called in rapid succession so
-- here, I seed it with a guaranteed different number each time. @@ROWCOUNT is the count from the most recent table operation.

Set @csr = Ceiling(RAND(convert(varbinary, newid())) * @@ROWCOUNT)

Select street, city, st, Right(('00000' + ltrim(zip)),5) As zip
From ##tmpAddress (NOLOCK)
Where id = @csr
usuario2788934
fuente
0

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 () se evalúe una vez por fila para lograr el muestreo por fila. La expresión CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) se evalúa como un valor flotante aleatorio entre 0 y 1. "

Fuente: http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx

Esto se explica con más detalle a continuación:

¿Como funciona esto? Dividamos la cláusula WHERE y expliquemosla.

La función CHECKSUM calcula una suma de comprobación sobre los elementos de la lista. Es discutible si SalesOrderID es incluso necesario, ya que NEWID () es una función que devuelve un nuevo GUID aleatorio, por lo que multiplicar una cifra aleatoria por una constante debería resultar en un aleatorio en cualquier caso. De hecho, la exclusión de SalesOrderID parece no hacer ninguna diferencia. Si es un experto en estadística y puede justificar la inclusión de esto, utilice la sección de comentarios a continuación y hágame saber por qué me equivoco.

La función CHECKSUM devuelve un VARBINARIO. Al realizar una operación AND bit a bit con 0x7fffffff, que es el equivalente de (111111111 ...) en binario, se obtiene un valor decimal que es efectivamente una representación de una cadena aleatoria de 0 y 1. Dividir por el coeficiente 0x7fffffff efectivamente normaliza esta cifra decimal a una cifra entre 0 y 1. Luego, para decidir si cada fila merece ser incluida en el conjunto de resultados final, se usa un umbral de 1 / x (en este caso, 0.01) donde x es el porcentaje de los datos que se recuperarán como muestra.

Fuente: https://www.mssqltips.com/sqlservertip/3157/different-ways-to-get-random-data-for-sql-server-data-sampling

XpiritO
fuente