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 / 102677
Accepted
Fabrizio Mazzoni
Fabrizio Mazzoni
Asked: 2015-05-29 04:55:08 +0800 CST2015-05-29 04:55:08 +0800 CST 2015-05-29 04:55:08 +0800 CST

SELECT DISTINCT em várias colunas

  • 772

Supondo que tenhamos uma tabela com quatro colunas (a,b,c,d)do mesmo tipo de dados.

É possível selecionar todos os valores distintos dentro dos dados nas colunas e devolvê-los como uma única coluna ou tenho que criar uma função para conseguir isso?

postgresql performance
  • 5 5 respostas
  • 86835 Views

5 respostas

  • Voted
  1. Best Answer
    ypercubeᵀᴹ
    2015-05-29T08:39:06+08:002015-05-29T08:39:06+08:00

    Atualização: testei todas as 5 consultas no SQLfiddle com 100K linhas (e 2 casos separados, um com poucos (25) valores distintos e outro com muitos (cerca de 25K valores).

    Uma consulta muito simples seria usar UNION DISTINCT. Acho que seria mais eficiente se houvesse um índice separado em cada uma das quatro colunas . Seria eficiente com um índice separado em cada uma das quatro colunas, se o Postgres tivesse implementado a otimização Loose Index Scan , o que não aconteceu. Portanto, esta consulta não será eficiente, pois requer 4 varreduras da tabela (e nenhum índice é usado):

    -- Query 1. (334 ms, 368ms) 
    SELECT a AS abcd FROM tablename 
    UNION                           -- means UNION DISTINCT
    SELECT b FROM tablename 
    UNION 
    SELECT c FROM tablename 
    UNION 
    SELECT d FROM tablename ;
    

    Outra seria primeiro UNION ALLe depois usar DISTINCT. Isso também exigirá 4 varreduras de tabela (e nenhum uso de índices). Não é má eficiência quando os valores são poucos, e com mais valores se torna o mais rápido no meu teste (não extenso):

    -- Query 2. (87 ms, 117 ms)
    SELECT DISTINCT a AS abcd
    FROM
      ( SELECT a FROM tablename 
        UNION ALL 
        SELECT b FROM tablename 
        UNION ALL
        SELECT c FROM tablename 
        UNION ALL
        SELECT d FROM tablename 
      ) AS x ;
    

    As outras respostas forneceram mais opções usando funções de matriz ou a LATERALsintaxe. A consulta de Jack ( 187 ms, 261 ms) tem desempenho razoável, mas a consulta de AndriyM parece mais eficiente ( 125 ms, 155 ms). Ambos fazem uma varredura sequencial da tabela e não usam nenhum índice.

    Na verdade, os resultados da consulta de Jack são um pouco melhores do que os mostrados acima (se removermos o order by) e podem ser melhorados ainda mais removendo os 4 internos distincte deixando apenas o externo.


    Finalmente, se - e somente se - os valores distintos das 4 colunas forem relativamente poucos, você pode usar o WITH RECURSIVEhack/otimização descrito na página Loose Index Scan acima e usar todos os 4 índices, com resultados notavelmente rápidos! Testado com as mesmas 100 mil linhas e aproximadamente 25 valores distintos espalhados pelas 4 colunas (executou em apenas 2 ms!), enquanto com 25 mil valores distintos é o mais lento com 368 ms:

    -- Query 3.  (2 ms, 368ms)
    WITH RECURSIVE 
        da AS (
           SELECT min(a) AS n  FROM observations
           UNION ALL
           SELECT (SELECT min(a) FROM observations
                   WHERE  a > s.n)
           FROM   da AS s  WHERE s.n IS NOT NULL  ),
        db AS (
           SELECT min(b) AS n  FROM observations
           UNION ALL
           SELECT (SELECT min(b) FROM observations
                   WHERE  b > s.n)
           FROM   db AS s  WHERE s.n IS NOT NULL  ),
       dc AS (
           SELECT min(c) AS n  FROM observations
           UNION ALL
           SELECT (SELECT min(c) FROM observations
                   WHERE  c > s.n)
           FROM   dc AS s  WHERE s.n IS NOT NULL  ),
       dd AS (
           SELECT min(d) AS n  FROM observations
           UNION ALL
           SELECT (SELECT min(d) FROM observations
                   WHERE  d > s.n)
           FROM   db AS s  WHERE s.n IS NOT NULL  )
    SELECT n 
    FROM 
    ( TABLE da  UNION 
      TABLE db  UNION 
      TABLE dc  UNION 
      TABLE dd
    ) AS x 
    WHERE n IS NOT NULL ;
    

    SQLfiddle


    Para resumir, quando os valores distintos são poucos, a consulta recursiva é a vencedora absoluta, enquanto com muitos valores, minha segunda, as consultas de Jack (versão melhorada abaixo) e AndriyM são as de melhor desempenho.


    Adições tardias, uma variação da 1ª consulta que, apesar das operações extra distintas, tem um desempenho muito melhor que a 1ª original e apenas um pouco pior que a 2ª:

    -- Query 1b.  (85 ms, 149 ms)
    SELECT DISTINCT a AS n FROM observations 
    UNION 
    SELECT DISTINCT b FROM observations 
    UNION 
    SELECT DISTINCT c FROM observations 
    UNION 
    SELECT DISTINCT d FROM observations ;
    

    e Jack melhorou:

    -- Query 4b.  (104 ms, 128 ms)
    select distinct unnest( array_agg(a)||
                            array_agg(b)||
                            array_agg(c)||
                            array_agg(d) )
    from t ;
    
    • 27
  2. Andriy M
    2015-05-29T08:09:52+08:002015-05-29T08:09:52+08:00

    Você pode usar LATERAL, como nesta consulta :

    SELECT DISTINCT
      x.n
    FROM
      atable
      CROSS JOIN LATERAL (
        VALUES (a), (b), (c), (d)
      ) AS x (n)
    ;
    

    A palavra-chave LATERAL permite que o lado direito da junção faça referência a objetos do lado esquerdo. Nesse caso, o lado direito é um construtor VALUES que cria um subconjunto de coluna única a partir dos valores de coluna que você deseja colocar em uma única coluna. A consulta principal simplesmente faz referência à nova coluna, aplicando também DISTINCT a ela.

    • 15
  3. Jack Douglas
    2015-05-29T07:55:17+08:002015-05-29T07:55:17+08:00

    Para ser claro, eu usaria unioncomo ypercube sugere , mas também é possível com arrays:

    select distinct unnest( array_agg(distinct a)||
                            array_agg(distinct b)||
                            array_agg(distinct c)||
                            array_agg(distinct d) )
    from t
    order by 1;
    
    | desaninhar |
    | :----- |
    | 0 |
    | 1 |
    | 2 |
    | 3 |
    | 5 |
    | 6 |
    | 8 |
    | 9 |
    

    dbfiddle aqui

    • 10
  4. Erwin Brandstetter
    2015-05-29T19:21:21+08:002015-05-29T19:21:21+08:00

    Mais curto

    SELECT DISTINCT n FROM observations, unnest(ARRAY[a,b,c,d]) n;
    

    Uma versão menos detalhada da ideia de Andriy é apenas um pouco mais longa, mas mais elegante e rápida. Para muitos valores distintos/ poucos duplicados:

    SELECT DISTINCT n FROM observations, LATERAL (VALUES (a),(b),(c),(d)) t(n);
    

    O mais rápido

    Com um índice em cada coluna envolvida!
    Para alguns valores distintos/ muitos duplicados:

    WITH RECURSIVE
      ta AS (
       (SELECT a FROM observations ORDER BY a LIMIT 1)
       UNION ALL
       SELECT o.a FROM ta t, LATERAL (SELECT a FROM observations WHERE a > t.a ORDER BY a LIMIT 1) o
       )
    , tb AS (
       (SELECT b FROM observations ORDER BY b LIMIT 1)
       UNION ALL
       SELECT o.b FROM tb t, LATERAL (SELECT b FROM observations WHERE b > t.b ORDER BY b LIMIT 1) o
       )
    , tc AS (
       (SELECT c FROM observations ORDER BY c LIMIT 1)
       UNION ALL
       SELECT o.c FROM tc t, LATERAL (SELECT c FROM observations WHERE c > t.c ORDER BY c LIMIT 1) o
       )
    , td AS (
       (SELECT d FROM observations ORDER BY d LIMIT 1)
       UNION ALL
       SELECT o.d FROM td t, LATERAL (SELECT d FROM observations WHERE d > t.d ORDER BY d LIMIT 1) o
       )
    SELECT a
    FROM  (
             TABLE ta
       UNION TABLE tb
       UNION TABLE tc
       UNION TABLE td
       ) sub
    ORDER  BY 1;  -- optional
    

    Esta é outra variante do rCTE, semelhante à que o @ypercube já postou , mas eu uso ORDER BY 1 LIMIT 1em vez do min(a)que normalmente é um pouco mais rápido. Também não preciso de predicado adicional para excluir valores NULL.
    E LATERALem vez de uma subconsulta correlacionada, porque é mais limpa (não necessariamente mais rápida).

    Explicação detalhada na minha resposta para esta técnica:

    • Otimize a consulta GROUP BY para recuperar o registro mais recente por usuário

    Eu o adicionei ao sqlfiddle do ypercube
    ... e agora portei isso para o dbfiddle.uk, pois o sqlfiddle.com não está acompanhando:

    db<>fique aqui

    • 7
  5. user_0
    2015-05-29T07:31:51+08:002015-05-29T07:31:51+08:00

    Você pode, mas como eu escrevi e testei a função me senti errado. É um desperdício de recursos.
    Apenas por favor use uma união e mais selecione. Única vantagem (se for), uma única varredura da tabela principal.

    No violino sql você precisa alterar o separador de $ para outra coisa, como /

    CREATE TABLE observations (
        id         serial
      , a int not null
      , b int not null
      , c int not null
      , d int not null
      , created_at timestamp
      , foo        text
    );
    
    INSERT INTO observations (a, b, c, d, created_at, foo)
    SELECT (random() * 20)::int        AS a          -- few values for a,b,c,d
         , (15 + random() * 10)::int 
         , (10 + random() * 10)::int 
         , ( 5 + random() * 20)::int 
         , '2014-01-01 0:0'::timestamp 
           + interval '1s' * g         AS created_at -- ascending (probably like in real life)
         , 'aöguihaophgaduigha' || g   AS foo        -- random ballast
    FROM generate_series (1, 10) g;               -- 10k rows
    
    CREATE INDEX observations_a_idx ON observations (a);
    CREATE INDEX observations_b_idx ON observations (b);
    CREATE INDEX observations_c_idx ON observations (c);
    CREATE INDEX observations_d_idx ON observations (d);
    
    CREATE OR REPLACE FUNCTION fn_readuniqu()
      RETURNS SETOF text AS $$
    DECLARE
        a_array     text[];
        b_array     text[];
        c_array     text[];
        d_array     text[];
        r       text;
    BEGIN
    
        SELECT INTO a_array, b_array, c_array, d_array array_agg(a), array_agg(b), array_agg(c), array_agg(d)
        FROM observations;
    
        FOR r IN
            SELECT DISTINCT x
            FROM
            (
                SELECT unnest(a_array) AS x
                UNION
                SELECT unnest(b_array) AS x
                UNION
                SELECT unnest(c_array) AS x
                UNION
                SELECT unnest(d_array) AS x
            ) AS a
    
        LOOP
            RETURN NEXT r;
        END LOOP;
    
    END;
    $$
      LANGUAGE plpgsql STABLE
      COST 100
      ROWS 1000;
    
    SELECT * FROM fn_readuniqu();
    
    • 3

relate perguntas

  • Sequências Biológicas do UniProt no PostgreSQL

  • Como determinar se um Índice é necessário ou necessário

  • Onde posso encontrar o log lento do mysql?

  • Como posso otimizar um mysqldump de um banco de dados grande?

  • 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