Selecione n linhas aleatórias da tabela do SQL Server


309

Eu tenho uma tabela do SQL Server com cerca de 50.000 linhas. Quero selecionar cerca de 5.000 dessas linhas aleatoriamente. Eu pensei em uma maneira complicada, criando uma tabela temporária com uma coluna "número aleatório", copiando minha tabela para isso, percorrendo a tabela temporária e atualizando cada linha com RAND()e selecionando nessa tabela onde a coluna de número aleatório < 0.1 Estou procurando uma maneira mais simples de fazer isso, em uma única declaração, se possível.

Este artigo sugere o uso da NEWID()função Parece promissor, mas não vejo como selecionar de maneira confiável uma certa porcentagem de linhas.

Alguém já fez isso antes? Alguma ideia?


3
MSDN tem um bom artigo que abrange uma série destas questões: Seleção de linhas aleatoriamente a partir de uma tabela grande
KyleMit

Respostas:


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

Em resposta ao comentário "lixo puro" sobre tabelas grandes: você pode fazer assim para melhorar o desempenho.

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

O custo disso será a varredura principal dos valores mais o custo da junção, que em uma tabela grande com uma pequena seleção percentual deve ser razoável.


1
Eu gosto muito mais dessa abordagem do que usar o artigo que ele referenciou.
21119 JoshBerke

14
É sempre bom lembrar que newid () não é realmente um bom gerador de números pseudo-aleatórios, pelo menos não tão bom quanto rand (). Mas se você apenas precisar de algumas amostras vagamente aleatórias e não se importar com qualidades matemáticas e outras coisas, será bom o suficiente. Caso contrário, você precisa: stackoverflow.com/questions/249301/…
user12861

1
Desculpe se isso é óbvio ... mas a que se [yourPk]refere? EDIT: Nvm, descobri ... Chave Primária. Durrr
Snailer

4
newid - guid é disigned de ser único, mas não aleatório .. abordagem incorreta
Brans Ds

2
com grande número de linhas, por exemplo, mais de 1 milhão de newid()custos de E / S de estimativa de classificação serão muito altos e afetarão o desempenho.
Aadi1295 # 8/17

81

Dependendo das suas necessidades, TABLESAMPLEvocê terá desempenho quase tão aleatório e melhor. isso está disponível no MS SQL Server 2005 e posterior.

TABLESAMPLE retornará dados de páginas aleatórias em vez de linhas aleatórias e, portanto, nem recupera os dados que não retornará.

Em uma mesa muito grande eu testei

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

demorou mais de 20 minutos.

select * from [tablename] tablesample(1 percent)

demorou 2 minutos.

O desempenho também melhorará em amostras menores, TABLESAMPLEenquanto isso não ocorrerá newid().

Lembre-se de que isso não é tão aleatório quanto o newid()método, mas fornecerá uma amostra decente.

Veja a página do MSDN .


7
Como apontado por Rob Boek abaixo, tablesampling clumps resultados e, portanto, não é uma boa maneira de obter um pequeno número de resultados aleatórios
Oskar Austegard

Você se preocupa com a questão de como isso funciona: selecione 1% * da ordem [tablename] por newid (), pois newid () não é uma coluna no [tablename]. O servidor sql anexa internamente a coluna newid () em cada linha e depois faz uma classificação?
precisa saber é o seguinte

A amostra da tabela foi a melhor resposta para mim, pois estava fazendo uma consulta complexa em uma tabela muito grande. Não há dúvida de que foi notavelmente rápido. Recebi uma variação no número de registros retornados ao executar isso várias vezes, mas todos estavam dentro de uma margem de erro aceitável.
jessier3

38

newid () / order by funcionará, mas será muito caro para grandes conjuntos de resultados porque ele precisa gerar um ID para cada linha e, em seguida, classificá-los.

TABLESAMPLE () é bom do ponto de vista de desempenho, mas você obterá agrupamentos de resultados (todas as linhas em uma página serão retornadas).

Para uma amostra aleatória verdadeira com melhor desempenho, a melhor maneira é filtrar as linhas aleatoriamente. Encontrei o seguinte exemplo de código no artigo Manuais Online do SQL Server Limitando conjuntos de resultados usando TABLESAMPLE :

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 está incluída na expressão CHECKSUM para que NEWID () avalie uma vez por linha para obter 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.

Quando executado em uma tabela com 1.000.000 de linhas, eis meus 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

Se você conseguir usar o TABLESAMPLE, ele fornecerá o melhor desempenho. Caso contrário, use o método newid () / filter. newid () / order by deve ser o último recurso se você tiver um grande conjunto de resultados.


Eu vi esse artigo também e tentei no meu código, parece que NewID()é avaliado apenas uma vez, em vez de por linha, o que eu não gosto ...
Andrew Mao

23

A seleção aleatória de linhas de uma tabela grande no MSDN possui uma solução simples e bem articulada que trata das preocupações de desempenho em larga escala.

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

Muito interessante. Depois de ler o artigo, eu realmente não entendo por RAND()que não retorna o mesmo valor para cada linha (o que anularia a BINARY_CHECKSUM()lógica). É porque está sendo chamado dentro de outra função em vez de fazer parte da cláusula SELECT?
John M Gant

Esta consulta foi executada em uma tabela com 6MM linhas em menos de um segundo.
Mark Melville

2
Eu executei essa consulta em uma tabela com 35 entradas e continuava tendo duas delas no conjunto de resultados com muita frequência. Isso pode ser um problema rand()ou uma combinação dos itens acima - mas eu me afastei dessa solução por esse motivo. Além disso, o número de resultados variou de 1 a 5, portanto, isso também pode não ser aceitável em alguns cenários.
21414 Oliver

RAND () não retorna o mesmo valor para cada linha?
Sarsaparilla

RAND()retorna o mesmo valor para cada linha (é por isso que esta solução é rápida). No entanto, linhas com somas de verificação binárias muito próximas correm alto risco de gerar resultados semelhantes de soma de verificação, causando aglomeração quando RAND()pequeno. Por exemplo, (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. Se os seus dados sofre deste problema, multiplique BINARY_CHECKSUMpor 9923.
Brian

12

Este link tem uma comparação interessante entre Orderby (NEWID ()) e outros métodos para tabelas com 1, 7 e 13 milhões de linhas.

Freqüentemente, quando perguntas sobre como selecionar linhas aleatórias são feitas em grupos de discussão, a consulta NEWID é proposta; é simples e funciona muito bem para pequenas mesas.

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

No entanto, a consulta NEWID tem uma grande desvantagem quando você a usa para tabelas grandes. A cláusula ORDER BY faz com que todas as linhas da tabela sejam copiadas no banco de dados tempdb, onde são classificadas. Isso causa dois problemas:

  1. A operação de classificação geralmente tem um alto custo associado a ela. A classificação pode usar muitas E / S de disco e pode ser executada por um longo tempo.
  2. Na pior das hipóteses, o tempdb pode ficar sem espaço. Na melhor das hipóteses, o tempdb pode ocupar uma grande quantidade de espaço em disco que nunca será recuperada sem um comando de redução manual.

O que você precisa é uma maneira de selecionar linhas aleatoriamente que não usarão o tempdb e não ficarão muito mais lentas à medida que a tabela aumentar. Aqui está uma nova idéia de como fazer isso:

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

A idéia básica por trás dessa consulta é que queremos gerar um número aleatório entre 0 e 99 para cada linha da tabela e escolher todas as linhas cujo número aleatório é menor que o valor da porcentagem especificada. Neste exemplo, queremos aproximadamente 10% das linhas selecionadas aleatoriamente; portanto, escolhemos todas as linhas cujo número aleatório é menor que 10.

Leia o artigo completo no MSDN .


2
Oi Deumber, foi encontrado, você pode se aprofundar, pois as respostas apenas no link provavelmente serão excluídas.
Bummi

1
@bummi eu mudei para evitar ser link só responda :)
Qmaster

Esta é a melhor resposta. 'ORDER BY NEWID ()' funciona na maioria dos casos (tabelas menores), mas como os benchmarks no link atualizado mostram claramente que fica para trás à medida que a tabela cresce
pedram bashiri

10

Se você (ao contrário do OP) precisar de um número específico de registros (o que dificulta a abordagem CHECKSUM) e desejar uma amostra mais aleatória do que a TABLESAMPLE fornece por si só, e também desejar uma velocidade melhor que a CHECKSUM, poderá se contentar com uma fusão da Métodos TABLESAMPLE e NEWID (), assim:

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

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

SET STATISTICS TIME OFF

No meu caso, esse é o compromisso mais direto entre aleatoriedade (não é realmente, eu sei) e velocidade. Varie a porcentagem (ou linhas) de TABLESAMPLE conforme apropriado - quanto maior a porcentagem, mais aleatória a amostra, mas espere uma queda linear na velocidade. (Observe que TABLESAMPLE não aceitará uma variável)


9

Apenas ordene a tabela por um número aleatório e obtenha as primeiras 5.000 linhas usando TOP.

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

ATUALIZAR

Apenas tentei e uma newid()ligação é suficiente - não há necessidade de todos os elencos e toda a matemática.


10
A razão pela qual 'todos os elencos e todas as matemáticas' é usada é para um melhor desempenho.
Hkf #

6

Essa é uma combinação da ideia inicial da semente e de uma soma de verificação, que parece fornecer resultados aleatórios corretamente sem o custo de NEWID ():

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

3

No MySQL você pode fazer isso:

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

3
Isso não vai funcionar. Como a instrução select é atômica, ela pega apenas um número aleatório e o duplica para cada linha. Você precisaria realimentá-lo novamente em cada linha para forçá-lo a mudar.
Tom H

4
Mmm ... adoro diferenças de fornecedor. Select é atômico no MySQL, mas suponho de uma maneira diferente. Isso irá funcionar no MySQL.
1013 Jeff Jeffland

2

Ainda não vi essa variação nas respostas. Eu tinha uma restrição adicional onde precisava, com base em uma semente inicial, para selecionar o mesmo conjunto de linhas a cada vez.

Para MS SQL:

Exemplo mínimo:

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

Tempo de execução normalizado: 1,00

Exemplo NewId ():

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

Tempo de execução normalizado: 1,02

NewId()é insignificantemente mais lento que rand(checksum(*)), portanto, você pode não querer usá-lo em grandes conjuntos de registros.

Seleção com semente 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 */

Se você precisar selecionar o mesmo conjunto dado uma semente, isso parece funcionar.


Existe alguma vantagem em usar o @seed especial contra o RAND ()?
QMaster 22/09/16

absolutamente, você usou o parâmetro seed e preenche-o por parâmetro de data, a função RAND () faz o mesmo, exceto usando o valor de tempo completo, quero saber se há alguma vantagem em usar parâmetros criados à mão, como seed acima de RAND () ou não?
QMaster 24/09/16

Ah! OK, este era um requisito do projeto. Eu precisava gerar uma lista de linhas n aleatórias de uma maneira determinística. Basicamente, a liderança queria saber quais linhas "aleatórias" estaríamos selecionando alguns dias antes de as linhas serem selecionadas e processadas. Ao criar um valor inicial com base no ano / mês, eu poderia garantir que qualquer chamada para a consulta desse ano retornasse a mesma lista "aleatória". Eu sei, era estranho e provavelmente havia maneiras melhores, mas ele trabalhou ...
klyd

HAHA :) Entendo, mas acho que o significado geral de registros selecionados aleatoriamente não é o mesmo em diferentes consultas em execução.
QMaster 26/09/16

1

Tente o seguinte:

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

0

Parece que newid () não pode ser usado na cláusula where, portanto, esta solução requer uma consulta interna:

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

0

Eu estava usando-o na subconsulta e ele me retornou as mesmas linhas na subconsulta

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

então eu resolvi com a inclusão de variável de tabela pai em que

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

Observe a condição where


0

A linguagem de processamento do servidor em uso (por exemplo, PHP, .net, etc) não é especificada, mas se for PHP, pegue o número necessário (ou todos os registros) e, em vez de fazer aleatoriamente a consulta, use a função aleatória do PHP. Não sei se o .net tem uma função equivalente, mas se o fizer, use-o se você estiver usando o .net

ORDER BY RAND () pode ter uma penalidade de desempenho, dependendo de quantos registros estão envolvidos.


Não me lembro exatamente para o que eu estava usando isso na época, mas provavelmente estava trabalhando em C #, talvez em um servidor ou em um aplicativo cliente, não tenho certeza. O C # não tem nada diretamente comparável ao shuffle do PHP, mas isso pode ser feito aplicando funções do objeto Random dentro de uma operação Select, ordenando o resultado e, em seguida, assumindo os dez por cento principais. Mas teríamos que ler a tabela inteira do disco no servidor de banco de dados e transmiti-la pela rede, apenas para descartar 90% desses dados. Processá-lo diretamente no DB é quase certamente mais eficiente.
John M Gant

-2

Isso funciona para mim:

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

9
@ user537824, você tentou isso no SQL Server? RANDOM não é uma função e LIMIT não é uma palavra-chave. A sintaxe do SQL Server para o que você está fazendo seria select top 10 percent from table_name order by rand(), mas isso também não funciona porque rand () retorna o mesmo valor em todas as linhas.
John M Gant
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.