¿Cuál es la mejor manera de obtener un pedido aleatorio?

27

Tengo una consulta donde deseo que los registros resultantes se ordenen aleatoriamente. Utiliza un índice agrupado, por lo que si no incluyo uno order by, es probable que devuelva registros en el orden de ese índice. ¿Cómo puedo asegurar un orden de fila aleatorio?

Entiendo que probablemente no sea "verdaderamente" aleatorio, seudoaleatorio es lo suficientemente bueno para mis necesidades.

goric
fuente

Respuestas:

19

ORDER BY NEWID () ordenará los registros al azar. Un ejemplo aqui

SELECT *
FROM Northwind..Orders 
ORDER BY NEWID()
Nómada
fuente
77
ORDER BY NEWID () es efectivamente aleatorio, pero no estadísticamente aleatorio. Hay una pequeña diferencia, y la mayoría de las veces la diferencia no importa.
mrdenny
44
Desde el punto de vista del rendimiento, esto es bastante lento: puede obtener una mejora significativa ORDER BY CHECKSUM (NEWID ())
Miles D
1
@mrdenny - ¿En qué basas lo "no estadísticamente aleatorio"? La respuesta aquí dice que termina usando CryptGenRandomal final. dba.stackexchange.com/a/208069/3690
Martin Smith
15

La primera sugerencia de Pradeep Adiga ORDER BY NEWID(), está bien y es algo que he usado en el pasado por este motivo.

Tenga cuidado con el uso RAND(): en muchos contextos, solo se ejecuta una vez por declaración, por ORDER BY RAND()lo que no tendrá ningún efecto (ya que obtiene el mismo resultado de RAND () para cada fila).

Por ejemplo:

SELECT display_name, RAND() FROM tr_person

devuelve cada nombre de nuestra tabla de personas y un número "aleatorio", que es el mismo para cada fila. El número varía cada vez que ejecuta la consulta, pero es el mismo para cada fila cada vez.

Para mostrar que lo mismo es el caso con RAND()utilizado en una ORDER BYcláusula, intento:

SELECT display_name FROM tr_person ORDER BY RAND(), display_name

Los resultados todavía están ordenados por el nombre que indica que el campo de clasificación anterior (el que se espera sea aleatorio) no tiene ningún efecto, por lo que presumiblemente siempre tiene el mismo valor.

NEWID()Sin embargo, ordenar por funciona, porque si NEWID () no siempre se reevalúa, el propósito de los UUID se rompería al insertar muchas filas nuevas en un estado con identificadores únicos a medida que se introducen, por lo que:

SELECT display_name FROM tr_person ORDER BY NEWID()

no ordenar los nombres "al azar".

Otros DBMS

Lo anterior es cierto para MSSQL (2005 y 2008 al menos, y si recuerdo bien 2000 también). Una función que devuelve un nuevo UUID debe evaluarse cada vez que en todos los DBMS NEWID () esté bajo MSSQL pero vale la pena verificar esto en la documentación y / o por sus propias pruebas. Es más probable que el comportamiento de otras funciones de resultados arbitrarios, como RAND (), varíe entre los DBMS, por lo que debe consultar nuevamente la documentación.

También he visto que se ignora el orden por los valores de UUID en algunos contextos, ya que el DB asume que el tipo no tiene un orden significativo. Si considera que este es el caso, convierta explícitamente el UUID a un tipo de cadena en la cláusula de pedido, o envuelva alguna otra función a su alrededor, como CHECKSUM()en SQL Server (puede haber una pequeña diferencia de rendimiento de esto también, ya que el pedido se realizará en un valor de 32 bits no uno de 128 bits, aunque si el beneficio de eso supera el costo de ejecución CHECKSUM()por valor primero, lo dejaré para que lo pruebe).

Nota al margen

Si desea un ordenamiento arbitrario pero algo repetible, ordene por un subconjunto relativamente no controlado de los datos en las filas mismas. Por ejemplo, uno o estos devolverán los nombres en un orden arbitrario pero repetible:

SELECT display_name FROM tr_person ORDER BY CHECKSUM(display_name), display_name -- order by the checksum of some of the row's data
SELECT display_name FROM tr_person ORDER BY SUBSTRING(display_name, LEN(display_name)/2, 128) -- order by part of the name field, but not in any an obviously recognisable order)

Los pedidos arbitrarios pero repetibles a menudo no son útiles en las aplicaciones, aunque pueden ser útiles en las pruebas si desea probar algún código en los resultados en una variedad de pedidos, pero desea poder repetir cada ejecución de la misma manera varias veces (para obtener el tiempo promedio resultados en varias ejecuciones, o probar que una solución que ha realizado en el código elimina un problema o ineficiencia previamente resaltada por un conjunto de resultados de entrada en particular, o simplemente para probar que su código es "estable", es decir, devuelve el mismo resultado cada vez si se envían los mismos datos en un orden dado).

Este truco también se puede usar para obtener resultados más arbitrarios de las funciones, que no permiten llamadas no deterministas como NEWID () dentro de su cuerpo. Nuevamente, esto no es algo que probablemente sea útil en el mundo real, pero podría ser útil si desea que una función devuelva algo aleatorio y "random-ish" es lo suficientemente bueno (pero tenga cuidado de recordar las reglas que determinan cuando se evalúan las funciones definidas por el usuario, es decir, generalmente solo una vez por fila, o sus resultados pueden no ser lo que espera / requiere).

Actuación

Como señala EBarr, puede haber problemas de rendimiento con cualquiera de los anteriores. Para más de unas pocas filas, está casi garantizado de ver el resultado en cola en tempdb antes de que se vuelva a leer el número solicitado de filas en el orden correcto, lo que significa que incluso si está buscando los 10 principales, puede encontrar un índice completo El escaneo (o peor, escaneo de tabla) ocurre junto con un gran bloque de escritura en tempdb. Por lo tanto, puede ser de vital importancia, como con la mayoría de las cosas, comparar con datos realistas antes de usar esto en la producción.

David Spillett
fuente
14

Esta es una vieja pregunta, pero falta un aspecto de la discusión, en mi opinión: RENDIMIENTO. ORDER BY NewId()Es la respuesta general. Cuando la fantasía de que alguien se añaden que realmente debe envolver NewID()en CheckSum(), ya sabes, para un rendimiento!

El problema con este método es que todavía tiene garantizado un escaneo de índice completo y luego un tipo completo de datos. Si ha trabajado con un volumen de datos serio, esto puede volverse rápidamente costoso. Mire este plan de ejecución típico y observe cómo el ordenamiento toma el 96% de su tiempo ...

ingrese la descripción de la imagen aquí

Para darle una idea de cómo se escala esto, le daré dos ejemplos de una base de datos con la que trabajo.

  • Tabla A: tiene 50,000 filas en 2500 páginas de datos. La consulta aleatoria genera 145 lecturas en 42 ms.
  • Tabla B: tiene 1.2 millones de filas en 114,000 páginas de datos. La ejecución Order By newid()de esta tabla genera 53.700 lecturas y lleva 16 segundos.

La moraleja de la historia es que si tiene tablas grandes (piense en miles de millones de filas) o necesita ejecutar esta consulta con frecuencia, el newid()método se descompone. Entonces, ¿qué debe hacer un niño?

Meet TABLESAMPLE ()

En SQL 2005 TABLESAMPLEse creó una nueva capacidad llamada . Solo he visto un artículo discutiendo su uso ... debería haber más. Documentos de MSDN aquí . Primero un ejemplo:

SELECT Top (20) *
FROM Northwind..Orders TABLESAMPLE(20 PERCENT)
ORDER BY NEWID()

La idea detrás de la muestra de tabla es darle aproximadamente el tamaño de subconjunto que solicita. SQL numera cada página de datos y selecciona el X por ciento de esas páginas. El número real de filas que obtiene puede variar según lo que exista en las páginas seleccionadas.

Entonces, ¿cómo lo uso? Seleccione un tamaño de subconjunto que cubra más que la cantidad de filas que necesita, luego agregue un Top(). La idea es que puede hacer que su mesa descomunal parezca más pequeña antes del tipo costoso.

Personalmente, lo he estado usando para limitar el tamaño de mi mesa. Entonces, en esa tabla de millones de filas, top(20)...TABLESAMPLE(20 PERCENT)la consulta cae a 5600 lecturas en 1600 ms. También hay una REPEATABLE()opción donde puede pasar una "Semilla" para la selección de página. Esto debería dar como resultado una selección de muestra estable.

De todos modos, pensé que esto debería agregarse a la discusión. Espero que ayude a alguien.

EBarr
fuente
Sería bueno poder escribir una consulta de orden aleatorio escalable que no solo se amplíe sino que funcione con pequeños conjuntos de datos. Parece que tiene que cambiar manualmente entre tener y no tener en TABLESAMPLE()función de la cantidad de datos que tiene. No creo que TABLESAMPLE(x ROWS)eso garantice que se devuelvan al menos x filas porque la documentación dice: “El número real de filas que se devuelven puede variar significativamente. Si especifica un número pequeño, como por ejemplo 5, es posible que no reciba los resultados de la muestra.”- por lo que la ROWSsintaxis realmente todavía es sólo un enmascarado PERCENTdentro?
binki
Claro, la magia automática es agradable. En la práctica, rara vez he visto una escala de tabla de 5 filas a millones de filas sin previo aviso. TABLESAMPLE () parece basar la selección del número de páginas en una tabla, por lo que el tamaño de fila dado influye en lo que vuelve. El punto de la muestra de la tabla, al menos como yo lo veo, es darle un buen subconjunto del cual puede seleccionar, algo así como una tabla derivada.
EBarr
3

Muchas tablas tienen una columna de identificación numérica indexada relativamente densa (pocos valores faltantes).

Esto nos permite determinar el rango de valores existentes y elegir filas usando valores de ID generados aleatoriamente en ese rango. Esto funciona mejor cuando el número de filas a devolver es relativamente pequeño y el rango de valores de ID está densamente poblado (por lo que la posibilidad de generar un valor faltante es lo suficientemente pequeña).

Para ilustrar, el siguiente código elige 100 usuarios aleatorios distintos de la tabla de usuarios de Desbordamiento de pila, que tiene 8,123,937 filas.

El primer paso es determinar el rango de valores de ID, una operación eficiente debido al índice:

DECLARE 
    @MinID integer,
    @Range integer,
    @Rows bigint = 100;

--- Find the range of values
SELECT
    @MinID = MIN(U.Id),
    @Range = 1 + MAX(U.Id) - MIN(U.Id)
FROM dbo.Users AS U;

Consulta de rango

El plan lee una fila de cada extremo del índice.

Ahora generamos 100 ID aleatorios distintos en el rango (con filas coincidentes en la tabla de usuarios) y devolvemos esas filas:

WITH Random (ID) AS
(
    -- Find @Rows distinct random user IDs that exist
    SELECT DISTINCT TOP (@Rows)
        Random.ID
    FROM dbo.Users AS U
    CROSS APPLY
    (
        -- Random ID
        VALUES (@MinID + (CONVERT(integer, CRYPT_GEN_RANDOM(4)) % @Range))
    ) AS Random (ID)
    WHERE EXISTS
    (
        SELECT 1
        FROM dbo.Users AS U2
            -- Ensure the row continues to exist
            WITH (REPEATABLEREAD)
        WHERE U2.Id = Random.ID
    )
)
SELECT
    U3.Id,
    U3.DisplayName,
    U3.CreationDate
FROM Random AS R
JOIN dbo.Users AS U3
    ON U3.Id = R.ID
-- QO model hint required to get a non-blocking flow distinct
OPTION (MAXDOP 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

consulta de filas aleatorias

El plan muestra que en este caso se necesitaban 601 números aleatorios para encontrar 100 filas coincidentes. Es bastante rápido:

Tabla 'Usuarios'. Cuenta de escaneo 1, lecturas lógicas 1937, lecturas físicas 2, lecturas de lectura anticipada 408
Mesa 'Mesa de trabajo'. Recuento de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0
Tabla 'Archivo de trabajo'. Recuento de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0

 Tiempos de ejecución de SQL Server:
   Tiempo de CPU = 0 ms, tiempo transcurrido = 9 ms.

Pruébelo en el Stack Exchange Data Explorer.

Paul White dice GoFundMonica
fuente
0

Como expliqué en este artículo , para mezclar el conjunto de resultados de SQL, debe usar una llamada de función específica de la base de datos.

Tenga en cuenta que ordenar un conjunto de resultados grande usando una función ALEATORIA puede resultar muy lento, así que asegúrese de hacerlo en conjuntos de resultados pequeños.

Si tiene que barajar un conjunto de resultados grande y limitarlo después, entonces es mejor usar SQL Server TABLESAMPLEen SQL Server en lugar de una función aleatoria en la cláusula ORDER BY.

Entonces, suponiendo que tengamos la siguiente tabla de base de datos:

ingrese la descripción de la imagen aquí

Y las siguientes filas en la songtabla:

| id | artist                          | title                              |
|----|---------------------------------|------------------------------------|
| 1  | Miyagi & Эндшпиль ft. Рем Дигга | I Got Love                         |
| 2  | HAIM                            | Don't Save Me (Cyril Hahn Remix)   |
| 3  | 2Pac ft. DMX                    | Rise Of A Champion (GalilHD Remix) |
| 4  | Ed Sheeran & Passenger          | No Diggity (Kygo Remix)            |
| 5  | JP Cooper ft. Mali-Koa          | All This Love                      |

En SQL Server, debe usar la NEWIDfunción, como se ilustra en el siguiente ejemplo:

SELECT
    CONCAT(CONCAT(artist, ' - '), title) AS song
FROM song
ORDER BY NEWID()

Al ejecutar la consulta SQL mencionada anteriormente en SQL Server, obtendremos el siguiente conjunto de resultados:

| song                                              |
|---------------------------------------------------|
| Miyagi & Эндшпиль ft. Рем Дигга - I Got Love      |
| JP Cooper ft. Mali-Koa - All This Love            |
| HAIM - Don't Save Me (Cyril Hahn Remix)           |
| Ed Sheeran & Passenger - No Diggity (Kygo Remix)  |
| 2Pac ft. DMX - Rise Of A Champion (GalilHD Remix) |

Observe que las canciones se enumeran en orden aleatorio, gracias a la NEWIDllamada a la función utilizada por la cláusula ORDER BY.

Vlad Mihalcea
fuente