Registro aleatório de uma tabela de banco de dados (T-SQL)

85

Existe uma maneira sucinta de recuperar um registro aleatório de uma tabela do servidor sql?

Eu gostaria de randomizar meus dados de teste de unidade, então estou procurando uma maneira simples de selecionar um id aleatório em uma tabela. Em inglês, o select seria "Selecione um id da tabela onde o id é um número aleatório entre o id mais baixo da tabela e o id mais alto da tabela."

Não consigo descobrir uma maneira de fazer isso sem ter que executar a consulta, testar um valor nulo e, em seguida, executar novamente se for nulo.

Ideias?

Jeremy
fonte
há alguns métodos aqui brettb.com/SQL_Help_Random_Numbers.asp
Mesh
2
Tem certeza de que deseja adotar essa abordagem? Os dados do teste de unidade não devem ser aleatórios - na verdade, você deve ter a garantia de obter os mesmos resultados, não importa quantas vezes você execute o teste de unidade. Ter dados aleatórios pode violar este princípio fundamental de teste de unidade.
reinará
O link acima de @Mesh não está mais ativo.
Robert Sievers,

Respostas:

146

Existe uma maneira sucinta de recuperar um registro aleatório de uma tabela do servidor sql?

sim

SELECT TOP 1 * FROM table ORDER BY NEWID()

Explicação

Um NEWID()é gerado para cada linha e a tabela é então classificada por ele. O primeiro registro é retornado (ou seja, o registro com o GUID "mais baixo").

Notas

  1. GUIDs são gerados como números pseudoaleatórios desde a versão quatro:

    O UUID da versão 4 destina-se a gerar UUIDs a partir de números verdadeiramente aleatórios ou pseudo-aleatórios.

    O algoritmo é o seguinte:

    • Defina os dois bits mais significativos (bits 6 e 7) de clock_seq_hi_and_reserved para zero e um, respectivamente.
    • Defina os quatro bits mais significativos (bits 12 a 15) do campo time_hi_and_version para o número da versão de 4 bits da Seção 4.1.3.
    • Defina todos os outros bits para valores escolhidos aleatoriamente (ou pseudo-aleatoriamente).

    - Um namespace URN de identificador exclusivo universal (UUID) - RFC 4122

  2. A alternativa SELECT TOP 1 * FROM table ORDER BY RAND()não funcionará como se imagina. RAND()retorna um único valor por consulta, portanto, todas as linhas compartilharão o mesmo valor.

  3. Embora os valores GUID sejam pseudoaleatórios, você precisará de um PRNG melhor para os aplicativos mais exigentes.

  4. O desempenho típico é inferior a 10 segundos para cerca de 1.000.000 de linhas - claro, dependendo do sistema. Observe que é impossível atingir um índice, portanto, o desempenho será relativamente limitado.

Sklivvz
fonte
Exatamente o que eu estava procurando. Tive a sensação de que era mais simples do que eu estava fazendo.
Jeremy,
1
Você está assumindo que NEWID produz valores pseudo-aleatórios. Há uma boa chance de produzir valores sequenciais. NEWID apenas produz valores únicos. RAND, entretanto, produz valores pseudoaleatórios.
Skizz de
Estou executando em uma tabela altamente indexada com 1.671.145 linhas e leva 7 segundos para retornar. A tabela também é ótima - é virtualmente o coração do nosso banco de dados, por isso está bem cuidada.
Tom Ritter
@ ÂviewAnew. 1,6 milhão de linhas e 7 segundos em um select que não (e não pode) atingir um índice não é ruim.
Sklivvz
7
@Skizz, rand não funciona assim. Um ÚNICO valor aleatório é gerado antes do SELECT. Então, se você tentar "SELECT TOP 10 RAND () ..." você sempre obterá o mesmo valor
Sklivvz
27

Em tabelas maiores, você também pode usar TABLESAMPLEpara evitar a varredura de toda a tabela.

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

O ORDER BY NEWIDainda é necessário para evitar apenas o retorno de linhas que aparecem primeiro na página de dados.

O número a ser usado deve ser escolhido com cuidado para o tamanho e a definição da tabela e você pode considerar a lógica de repetição se nenhuma linha for retornada. A matemática por trás disso e por que a técnica não é adequada para mesas pequenas é discutida aqui

Martin Smith
fonte
Encontrei isso no site da Microsoft: Você pode usar TABLESAMPLE para retornar rapidamente uma amostra de uma grande tabela quando uma das seguintes condições for verdadeira: A amostra não precisa ser uma amostra verdadeiramente aleatória no nível de linhas individuais. As linhas em páginas individuais da tabela não estão correlacionadas com outras linhas na mesma página.
Mark Entingh,
1
@MarkEntingh - No caso TOP 1disso, não importa se as linhas na mesma página estão correlacionadas ou não. Você está escolhendo apenas um deles.
Martin Smith,
9

Experimente também o seu método para obter um Id aleatório entre MIN (Id) e MAX (Id) e depois

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

Você sempre terá uma linha.

Sklivvz
fonte
2
-1, isso só funcionaria quando não houvesse IDs ausentes entre mín. E máx. Se um for excluído, o mesmo ID será gerado pela função aleatória, você receberá zero registros de volta.
Neil N
6
@ Neil, na verdade não - ele vai te dar a primeira linha com um Id maior do que o número aleatório se houver Ids ausentes. O problema aqui é que a probabilidade de cada linha sair não é constante. Mas, novamente, isso é suficiente na maioria dos casos.
Sklivvz
1
+1. Para o teste de unidade que deve atingir valores diferentes, é bom o suficiente - se você requer um aleatório real, isso é outra coisa. Mas, no contexto do OP, deve ser bom o suficiente.
TomTom
7

Se você deseja selecionar grandes volumes de dados, a melhor maneira que conheço é:

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

Fonte: MSDN

hmfarimani
fonte
Não tenho certeza, mas acho que usar RAND () em vez de NEWID () para gerar números verdadeiramente aleatórios pode ser melhor por causa das desvantagens de usar NEWID () no processo de seleção.
QMaster de
Eu tento usar este método com o número exato de registros em vez da base percentual, fiz isso expandindo o intervalo de seleção e limitando com TOP n, há alguma sugestão?
QMaster de
Eu encontrei outro problema com este cenário, se você usar group by, você obterá sempre a mesma ordem de linhas selecionadas aleatoriamente, então parece que em tabelas pequenas a abordagem @skilvvz é a mais adequada.
QMaster de
0

Eu estava procurando melhorar os métodos que experimentei e me deparei com este post. Sei que é antigo, mas esse método não está listado. Estou criando e aplicando dados de teste; isso mostra o método para "endereço" em um SP chamado com @st (estado de dois 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
user2788934
fonte
0

Se você realmente deseja uma amostra aleatória de linhas individuais, modifique sua consulta para filtrar linhas aleatoriamente, em vez de usar TABLESAMPLE. Por exemplo, a consulta a seguir usa a função NEWID para retornar aproximadamente um por cento das linhas da tabela Sales.SalesOrderDetail:

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

A coluna SalesOrderID é incluída na expressão CHECKSUM para que NEWID () seja avaliada uma vez por linha para obter a amostragem por linha. A expressão CAST (CHECKSUM (NEWID (), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) é avaliada como um valor flutuante aleatório entre 0 e 1. "

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

Isso é explicado mais detalhadamente abaixo:

Como é que isso funciona? Vamos dividir a cláusula WHERE e explicá-la.

A função CHECKSUM está calculando uma soma de verificação sobre os itens da lista. É discutível se SalesOrderID é mesmo necessário, uma vez que NEWID () é uma função que retorna um novo GUID aleatório, portanto, multiplicar uma figura aleatória por uma constante deve resultar em um aleatório em qualquer caso. Na verdade, excluir SalesOrderID parece não fazer diferença. Se você é um especialista em estatística e pode justificar a inclusão disso, por favor use a seção de comentários abaixo e me diga porque estou errado!

A função CHECKSUM retorna um VARBINÁRIO. Realizar uma operação AND bit a bit com 0x7fffffff, que é o equivalente a (111111111 ...) em binário, produz um valor decimal que é efetivamente uma representação de uma string aleatória de 0s e 1s. A divisão pelo coeficiente 0x7fffffff normaliza efetivamente este número decimal para um número entre 0 e 1. Em seguida, para decidir se cada linha merece inclusão no conjunto de resultados final, um limite de 1 / x é usado (neste caso, 0,01), onde x é a porcentagem dos dados a serem recuperados como uma amostra.

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

XpiritO
fonte