AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 159213
Accepted
joanolo
joanolo
Asked: 2016-12-27 12:27:58 +0800 CST2016-12-27 12:27:58 +0800 CST 2016-12-27 12:27:58 +0800 CST

Como obter (otimamente) amostras aleatórias de pares (a.id, b.id) de duas tabelas (a, b)?

  • 772

Vamos supor que eu tenha tabelas muito simples

CREATE TABLE a(id integer PRIMARY KEY, 
       t timestamp default now(), 
       sensor_readings real[]);
CREATE TABLE b(id integer PRIMARY KEY, 
       t timestamp default now(), 
       sensor_readings real[]);

com alguns dados sobre eles

INSERT INTO a(id) SELECT generate_series(    1,   100);
INSERT INTO b(id) SELECT generate_series(10001, 10100);

Na realidade, a tabela a pode ter cerca de 100_000 linhas e a tabela b cerca de 50_000. Na prática, também, a sequência id pode ter lacunas (da ordem de alguns %). Assim, o produto cartesiano axb tem cardinalidade de bilhões.

Eu quero pegar uma amostra aleatória de 1_000 pares classificados (a.id, b.id). Eu posso usar algo como a seguinte consulta:

SELECT  
    *
FROM
(
    SELECT
        *
    FROM
        (
        SELECT 
            a.id AS a_id, b.id AS b_id
        FROM
            a CROSS JOIN b
        ORDER BY
            random()
        ) AS s0
    LIMIT
        1000 
) AS s1
ORDER BY
    a_id, b_id ;

... mas se tornaria extremamente ineficiente assim que o número de linhas em a ou b crescesse (devido ao crescimento do CROSS JOIN).

Existe alguma maneira de fazer algo equivalente a isso de maneira ideal? Ou seja, existe uma maneira prática de obter uma amostra aleatória de linhas da a x brelação sem realmente ter que instanciá-la.

NOTA: Não há limitação quanto ao fato de que a.id ou b.id podem ser repetidos. Embora o par (a.id, b.id) não possa.

Se eu estivesse tentando programar isso em uma linguagem imperativa, provavelmente usaria um loop e faria algo como o pseudocódigo a seguir (e, em seguida, verificaria por um estatístico, para ter certeza de que realmente peguei uma amostra onde todos os pares têm a mesma probabilidade de serem escolhidos):

start with a result set equal to {} (empty set)
while size of result set < 1000
    Pick the id value from a random row from table a -> rand_id_a
    Pick the id value from a random row from table b -> rand_id_b
    If (rand_id_a, rand_id_b) not in result set
        append (rand_id_a, rand_id_b) to result set
    end if
end while
sort result set and return it

Existe uma maneira de obter um resultado equivalente sem recorrer a loops? Se não, existe uma maneira eficiente de fazer isso usando plpgSQL? (ou qualquer outro idioma)

postgresql random
  • 2 2 respostas
  • 2569 Views

2 respostas

  • Voted
  1. Best Answer
    Erwin Brandstetter
    2016-12-27T16:54:58+08:002016-12-27T16:54:58+08:00

    A melhor solução depende da definição exata de sua configuração. Para a configuração de exemplo, é trivial:

    • Colunas inteiras em série sem lacunas.

    SELECT DISTINCT
               1 + trunc(random() * 100)::int AS a_id
         , 10001 + trunc(random() * 100)::int AS b_id
    FROM   generate_series(1, 1100) g  -- enough excess to make up for possible dupes
    LIMIT  1000;  -- only take 1000
    

    A única questão interessante: como dobrar dupes de forma eficiente. A solução: deixe o Postgres decidir. Basta usar DISTINCT.
    Nem precisamos envolver as tabelas. Super rápido.

    Observe que random()gera ( por documentação ):

    valor aleatório no intervalo 0,0 <= x < 1,0

    Portanto 1 + trunc(random() * 100)::int, para cobrir exatamente os números de identificação entre 1 e 100 .

    Configuração real?

    Você precisa ser mais específico sobre sua configuração real. Vamos supor que haja pelo menos uma coluna de carga útil em cada uma de suas tabelas, não apenas colunas de ID.

    CREATE TABLE a(a_id integer PRIMARY KEY, a text);
    CREATE TABLE b(b_id integer PRIMARY KEY, b text);
    
    INSERT INTO a(a_id, a)
    SELECT g, 't' || g FROM generate_series(    1,   100) g;
    INSERT INTO b(b_id, b)
    SELECT g, 't' || g FROM generate_series(10001, 10100) g;
    

    Consulta:

    SELECT a.a_id, a.a, b.b_id, b.b
    FROM  (
        SELECT DISTINCT
                   1 + trunc(random() * 100)::int AS a_id  -- cover *whole* key space
             , 10001 + trunc(random() * 100)::int AS b_id  -- maybe add reserve for new rows
        FROM   generate_series(1, 1100) g
        LIMIT  1000
        ) ra
    JOIN   a USING (a_id)
    JOIN   b USING (b_id);
    

    Verdadeiramente aleatório, muito rápido e quase independente do tamanho real da mesa.

    Tudo o que você precisa são índices em a(a_id)e b(b_id). Ou possivelmente índices de várias colunas para permitir varreduras somente de índice.


    A solução também funciona para alguns gaps de chamadas puladas nextval(), desde que não haja muito mais gaps do que ilhas , ainda é muito barato gerar combinações suficientes para cobrir perdas por gaps. ( Muito mais barato do que trabalhar com um produto cartesiano de tabelas grandes ou classificar tabelas grandes inteiras de ORDER BY random()qualquer maneira.) Apenas certifique-se de gerar combinações suficientes.

    SELECT a.a_id, a.a, b.b_id, b.b
    FROM  (
        SELECT DISTINCT
                   1 + trunc(random() * 100)::int AS a_id
             , 10001 + trunc(random() * 100)::int AS b_id
        FROM   generate_series(1, 1100) g  -- enough to cover dupes *and* gaps
        ) ra
    JOIN   a USING (a_id)
    JOIN   b USING (b_id)
    LIMIT  1000;  -- LIMIT moves to outer query to cover gaps
    

    Com mais do que algumas lacunas , comece com um número de combinações que seja suficiente em 95% do tempo e adicione uma etapa recursiva para adicionar mais linhas se você ficar aquém. Existe uma receita para esta solução (para uma única tabela) na resposta relacionada. Também mais explicações e variações:

    • Melhor maneira de selecionar linhas aleatórias PostgreSQL
    • 4
  2. Evan Carroll
    2016-12-27T16:27:32+08:002016-12-27T16:27:32+08:00

    Eu quero pegar uma amostra aleatória de 1000 pares classificados (a.id, b.id).

    Sempre depende do que aleatório significa, mas se você estiver definindo a quantidade de linhas que deseja, provavelmente desejará a extensãotsm_system_rows

    tsm_system_rows

    O módulo fornece o método de amostragem de tabela SYSTEM_ROWS, que pode ser usado na cláusula TABLESAMPLE de um comando SELECT.

    Este método de amostragem de tabela aceita um único argumento inteiro que é o número máximo de linhas a serem lidas. A amostra resultante sempre conterá exatamente esse número de linhas, a menos que a tabela não contenha linhas suficientes, caso em que toda a tabela é selecionada. Assim como o método de amostragem SYSTEM integrado, SYSTEM_ROWS realiza amostragem em nível de bloco, de modo que a amostra não seja completamente aleatória, mas pode estar sujeita a efeitos de agrupamento, especialmente se apenas um pequeno número de linhas for solicitado.

    Primeiro instale a extensão

    CREATE EXTENSION tsm_system_rows;
    

    Então sua consulta,

    SELECT *
    FROM a
    CROSS JOIN b
    TABLESAMPLE SYSTEM_ROWS(1000);
    

    O importante aqui é que ele sempre fornece 1000 ROWS , o que é mais do que podemos dizer para random() <= 0.10, ou para TABLESAMPLE BERNOULLI.

    Se isso não é bom nuff'

    Se você realmente precisa de random e não pode aceitar a desvantagem de clustering, eu usaria

    ORDER BY random()
    LIMIT x;
    

    Se você precisar eliminar duplicatas

    A única maneira sensata de eliminar duplicatas (se a.id, e b.idnão são UNIQUE) e manter o conjunto de resultados aleatório, é fazer isso de antemão. Isso pode ser desagradável porque TABLESAMPLEainda não funciona em tabelas virtuais, então você terá que criar uma tabela temporária (que ainda pode persistir na memória). Tímido disso, você pode usar o outro método que também é lento e feio, mas pelo menos não precisa escrever o

    SELECT *
    FROM (
      SELECT DISTINCT ON(a.id, b.id) a.id, b.id
      FROM a
      CROSS JOIN b
    ) AS t
    ORDER BY random()
    FETCH FIRST 1000 ROWS ONLY;
    
    • 0

relate perguntas

  • Posso ativar o PITR depois que o banco de dados foi usado

  • Práticas recomendadas para executar a replicação atrasada do deslocamento de tempo

  • Os procedimentos armazenados impedem a injeção de SQL?

  • Sequências Biológicas do UniProt no PostgreSQL

  • Qual é a diferença entre a replicação do PostgreSQL 9.0 e o Slony-I?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve