Question

En supposant que nous avons un tableau avec quatre colonnes (a,b,c,d) du même type de données.

Est-il possible de sélectionner toutes les valeurs distinctes dans les données dans les colonnes et les retourner sur une seule colonne ou dois-je créer une fonction pour réaliser cet objectif?

Était-ce utile?

La solution

Mise à jour: Testé toutes les 5 requêtes dans SQLfiddle avec 100K lignes (et 2 cas distincts, l'un avec quelques (25) valeurs distinctes et un autre avec beaucoup (environ 25K valeurs).

Un très requête simple serait d'utiliser UNION DISTINCT. Je pense qu'il serait plus efficace s'il existe un index sur chacune des quatre colonnes Il serait plus efficace avec un index séparé sur chacune des quatre colonnes, si Postgres avait mis en œuvre Lâche Index Scan d'optimisation, dont il n'a pas.Si cette requête ne sera pas efficace, car elle nécessite 4 scans de la table (et pas d'index est utilisé):

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

Une autre possibilité serait la première UNION ALL et ensuite utiliser DISTINCT.Ce sera également besoin de 4 analyses de la table (et pas de l'utilisation de l'index).Pas mal d'efficacité lorsque les valeurs sont peu nombreux, et de plus les valeurs devient le plus rapide dans mon (non étendu) 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 ;

Les autres réponses ont fourni davantage d'options à l'aide de fonctions de tableau ou de la LATERAL la syntaxe.Jack query (187 ms, 261 ms) a de bonnes performances, mais AndriyM de la requête semble plus efficace (125 ms, 155 ms).Deux d'entre eux un balayage séquentiel de la table et de ne pas utiliser un index.

En fait Jack résultats de la requête sont un peu mieux que ce qui est illustré ci-dessus (si on enlève le order by) et peut encore être améliorée en supprimant le 4 interne distinct et ne laissant que de l'externe.


Enfin, si - et seulement si - les valeurs distinctes de 4 colonnes sont relativement peu, vous pouvez utiliser le WITH RECURSIVE hack/optimisation décrit ci-dessus Lâche Index Scan de la page et d'utiliser tous les 4 indices, avec très vite la suite!!!Testé avec la même 100K lignes et environ 25 valeurs distinctes réparties sur 4 colonnes (fonctionne en seulement 2 ms!) alors qu'avec 25K de valeurs distinctes (c'est le plus lent avec 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


Pour résumer, lorsque les valeurs distinctes sont que quelques-uns, la requête récursive est le vainqueur absolu tout avec beaucoup de valeurs, ma 2ème, Jack (version améliorée ci-dessous) et AndriyM interrogations sont les meilleurs interprètes.


Des ajouts tardifs, une variation sur le 1er requête qui, en dépit de la opérations distinctes, effectue beaucoup mieux que l'original 1er et seulement légèrement pire que le 2ème:

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

et Jack s'est améliorée:

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

Autres conseils

Vous pouvez utiliser LATÉRALE, comme dans cette requête:

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

Le LATÉRAL mot-clé permet le côté droit de la rejoindre pour faire référence à des objets à partir de la gauche.Dans ce cas, le côté droit est une des VALEURS constructeur qui construit une seule colonne sous-ensemble de la colonne des valeurs que vous souhaitez mettre dans une seule colonne.La requête principale simplement les références de la nouvelle colonne, également une demande DISTINCTE à elle.

Pour être clair, je ne l'utiliserais union comme ypercube suggère, mais il est également possible avec les tableaux:

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      |

dbfiddle ici

Plus court

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

Moins détaillé de la version de Andriy l'idée de est seulement un peu plus long, mais plus élégant et plus rapide.
Pour de nombreux distinct / quelques les valeurs en double:

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

Plus rapide

Avec un index sur chaque colonne!
Pour quelques distinct / de nombreux les valeurs en double:

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;

C'est un autre rCTE variante, semblable à celui @ypercube déjà posté, mais j'utilise ORDER BY 1 LIMIT 1 au lieu de min(a) qui est généralement un peu plus rapide.J'ai aussi besoin d'aucun prédicat supplémentaire pour exclure les valeurs NULL.
Et LATERAL au lieu d'une sous-requête en corrélation, parce que c'est plus propre (pas nécessairement plus rapide).

Explication détaillée dans ma réponse à cette technique:

J'ai mis à jour ypercube de l' SQL Violon et ajouté de la mine à la liste de lecture.

Vous pouvez, mais comme je l'ai écrit et testé la fonction je me sentais mal.C'est une des ressources les déchets.
Juste s'il vous plaît utiliser un syndicat et plus.Seul avantage (si elle est), un seul balayage de la table principale.

Dans sql violon vous avez besoin de changer le séparateur de $ pour quelque chose d'autre, comme /

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();
Licencié sous: CC-BY-SA avec attribution
Non affilié à dba.stackexchange
scroll top