Domanda

Supponiamo di avere una tabella con quattro colonne (a,b,c,d) dello stesso tipo di dati.

È possibile selezionare tutti i valori distinti all'interno dei dati nelle colonne e restituirli come un'unica colonna o devo creare una funzione per raggiungere questo obiettivo?

È stato utile?

Soluzione

Aggiornamento: Testato tutte e 5 le query in SQLfiddle con 100.000 righe (e 2 casi separati, uno con pochi (25) valori distinti e un altro con lotti (circa 25.000 valori).

Una query molto semplice sarebbe quella da utilizzare UNION DISTINCT. Penso che sarebbe più efficiente se ci fosse un indice separato su ciascuna delle quattro colonne Sarebbe efficiente con un indice separato su ciascuna delle quattro colonne, se Postgres lo avesse implementato Scansione dell'indice sciolto ottimizzazione, cosa che non ha.Quindi questa query non sarà efficiente poiché richiede 4 scansioni della tabella (e non viene utilizzato alcun indice):

-- 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 ;

Un altro sarebbe il primo UNION ALL e poi utilizzare DISTINCT.Ciò richiederà anche 4 scansioni di tabelle (e nessun utilizzo di indici).Non male l'efficienza quando i valori sono pochi, e con più valori diventa il più veloce nel mio (non approfondito) test:

-- 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 ;

Le altre risposte hanno fornito più opzioni utilizzando le funzioni di array o il file LATERAL sintassi.La domanda di Jack (187 ms, 261 ms) ha prestazioni ragionevoli ma la query di AndriyM sembra più efficiente (125 ms, 155 ms).Entrambi eseguono una scansione sequenziale della tabella e non utilizzano alcun indice.

In realtà i risultati della query di Jack sono leggermente migliori di quelli mostrati sopra (se rimuoviamo il file order by) e può essere ulteriormente migliorato rimuovendo i 4 interni distinct e lasciando solo quello esterno.


Infine, se - e solo se - i valori distinti delle 4 colonne sono relativamente pochi, puoi utilizzare il file WITH RECURSIVE hack/ottimizzazione descritti nella pagina Loose Index Scan sopra e utilizza tutti e 4 gli indici, con risultati straordinariamente veloci!Testato con le stesse 100.000 righe e circa 25 valori distinti distribuiti sulle 4 colonne (viene eseguito in soli 2 ms!) mentre con 25.000 valori distinti è il più lento con 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


Per riassumere, quando i valori distinti sono pochi, la query ricorsiva è la vincitrice assoluta mentre con molti valori, la mia seconda, le query di Jack (versione migliorata di seguito) e AndriyM hanno le prestazioni migliori.


Aggiunte tardive, una variazione della prima query che, nonostante le operazioni più distinte, funziona molto meglio della prima query originale e solo leggermente peggiore della seconda:

-- 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 è migliorato:

-- Query 4b.  (104 ms, 128 ms)
select distinct unnest( array_agg(a)||
                        array_agg(b)||
                        array_agg(c)||
                        array_agg(d) )
from t ;

Altri suggerimenti

Puoi usare laterale, come in Questa richiesta :

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

La parola chiave laterale consente il lato destro del join agli oggetti di riferimento dal lato sinistro.In questo caso, il lato destro è un costruttore di valori che costruisce un sottoinsieme a colonna singola dai valori della colonna che si desidera inserire in una singola colonna.La query principale riferisce semplicemente la nuova colonna, applicando anche distinti ad esso.

Per essere chiari, utilizzerei union come Ypercube suggerisce , ma è anche possibile con gli array:

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

dbfidddle qui

il più breve

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

Una versione meno verbosa di L'idea di Andriy è solo leggermente più lunga, ma più elegante e più veloce.
Per molti distinti / pochi valori duplicati :

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

più veloce

con un indice su ciascuna colonna coinvolta!
Per pochi distinti / molti valori duplicati :

WITH RECURSIVE
  ta AS (
   (SELECT a FROM observations ORDER BY a LIMIT 1)  -- parentheses required!
   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;
.

Questa è un'altra variante RCTE, simile a quella @ypercube già pubblicato , ma uso ORDER BY 1 LIMIT 1 invece di min(a) che è in genere un po 'più veloce. Non ho anche bisogno di un predicato aggiuntivo per escludere valori nullo.
E LATERAL invece di una sottoquery correlata, perché è più pulito (non necessariamente più veloce).

Spiegazione dettagliata nel mio go-tows Rispondi per questa tecnica:

Ho aggiornato il SQL Fiddle e ha aggiunto il mio alla playlist.

puoi, ma come ho scritto e testato la funzione che ho sentito sbagliato.È uno spreco di risorse.
solo per favore usa un'unione e più seleziona.Solo vantaggio (se lo è), una singola scansione dalla tabella principale.

In SQL Fiddle è necessario modificare il separatore da $ a qualcos'altro, come /

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();
.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a dba.stackexchange
scroll top