Припустимо, у нас є таблиця з чотирма стовпцями (a,b,c,d)
одного типу даних.
Чи можливо вибрати всі окремі значення в межах даних у стовпцях і повернути їх як єдиний стовпець чи мені потрібно створити функцію для досягнення цього?
UNION
Припустимо, у нас є таблиця з чотирма стовпцями (a,b,c,d)
одного типу даних.
Чи можливо вибрати всі окремі значення в межах даних у стовпцях і повернути їх як єдиний стовпець чи мені потрібно створити функцію для досягнення цього?
UNION
Відповіді:
Оновлення: протестовано всі 5 запитів у SQLfiddle зі 100K рядками (і 2 окремими випадками, один з кількома (25) різними значеннями та інший з лотами (близько 25K значень).
Дуже простим запитом було б користуватися UNION DISTINCT
. Я думаю, що це було б найефективніше, якщо на кожному з чотирьох стовпців є окремий індекс. Було б ефективно з окремим індексом на кожному з чотирьох стовпців, якби Postgres здійснив оптимізацію Loose Index Scan , яку він не зробив. Таким чином, цей запит не буде ефективним, оскільки для нього потрібно 4 сканування таблиці (а індекс не використовується):
-- 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 ;
Іншим було б спочатку, UNION ALL
а потім використовувати DISTINCT
. Для цього також знадобиться 4 сканування таблиці (і використання індексів не застосовується). Непогана ефективність, коли значень мало, а з більшим значенням стає найшвидшим у моєму (не широкому) тесті:
-- 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 ;
Інші відповіді надали більше варіантів, використовуючи функції масиву або LATERAL
синтаксис. Запит Джека ( 187 ms, 261 ms
) має розумну ефективність, але запит AndriyM видається більш ефективним ( 125 ms, 155 ms
). Вони обидва роблять послідовне сканування таблиці і не використовують жодного індексу.
Насправді результати запиту Джека трохи кращі, ніж показано вище (якщо ми видалимо order by
), і їх можна вдосконалити, видаливши 4 внутрішніх distinct
та залишивши лише зовнішній.
Нарешті, якщо - і лише якщо - відмінних значень 4 стовпців порівняно мало, ви можете використовувати WITH RECURSIVE
хак / оптимізацію, описану на вищезгаданій сторінці Loose Index Scan, та використовувати всі 4 індекси з надзвичайно швидким результатом! Тестовано з тими ж 100K рядками і приблизно 25 чіткими значеннями, рознесеними по 4 стовпцям (працює лише за 2 мс!), Тоді як з 25K чіткими значеннями це найповільніше з 368 мс:
-- 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 ;
Підводячи підсумок, коли різних значень декілька, рекурсивний запит є абсолютним переможцем, а з великою кількістю значень, мій 2-й, Джек (покращена версія нижче) та запити АндріяМ - найкращі виконавці.
Пізні доповнення, варіація 1-го запиту, яка, незважаючи на надзвичайно чіткі операції, працює набагато краще, ніж початковий 1-й і лише трохи гірше, ніж 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 ;
і Джек покращився:
-- Query 4b. (104 ms, 128 ms)
select distinct unnest( array_agg(a)||
array_agg(b)||
array_agg(c)||
array_agg(d) )
from t ;
Ви можете використовувати LATERAL, як у цьому запиті :
SELECT DISTINCT
x.n
FROM
atable
CROSS JOIN LATERAL (
VALUES (a), (b), (c), (d)
) AS x (n)
;
Ключове слово LATERAL дозволяє правій частині об'єднання посилатися на об'єкти з лівої сторони. У цьому випадку права сторона - це конструктор VALUES, який створює підмножину для одного стовпця із значень стовпців, які потрібно ввести в один стовпець. Основний запит просто посилається на новий стовпець, також застосовуючи до нього DISTINCT.
Щоб було зрозуміло, я б використовував union
як ypercube , але це також можливо з масивами:
select distinct unnest( array_agg(distinct a)|| array_agg(distinct b)|| array_agg(distinct c)|| array_agg(distinct d) ) from t order by 1;
| нестримно | | : ----- | | 0 | | 1 | | 2 | | 3 | | 5 | | 6 | | 8 | | 9 |
dbfiddle тут
SELECT DISTINCT n FROM observations, unnest(ARRAY[a,b,c,d]) n;
Менш багатослівна версія ідеї Андрія лише трохи довша, але більш елегантна та швидша.
Для багатьох чітких / декількох повторюваних значень:
SELECT DISTINCT n FROM observations, LATERAL (VALUES (a),(b),(c),(d)) t(n);
З індексом на кожну залучену колонку!
Для кількох чітких / безлічі повторюваних значень:
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;
Це ще один варіант rCTE, схожий на вже розміщений @ypercube , але я використовую ORDER BY 1 LIMIT 1
замість min(a)
нього, як правило, трохи швидше. Мені також не потрібен додатковий предикат, щоб виключити значення NULL.
І LATERAL
замість співвіднесеного підпиту, адже він чистіший (не обов’язково швидший).
Детальне пояснення в моїй відповіді на цю методику:
Я оновив SQL Fiddle ypercube і додав моє до списку відтворення.
EXPLAIN (ANALYZE, TIMING OFF)
щоб перевірити найкращу загальну ефективність? (Краще за 5 для виключення ефектів кешування.)
VALUES ...
швидше, ніж unnest(ARRAY[...])
. LATERAL
неявна для функцій повернення набору в FROM
списку.
Можна, але, як я писав і тестував функцію, я почував себе неправильним. Це відходи ресурсів.
Просто будь ласка, використовуйте союз і більше виберіть. Єдина перевага (якщо вона є), одне сканування з головної таблиці.
У sql скрипці вам потрібно змінити роздільник з $ на щось інше, наприклад /
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();
SELECT a FROM tablename UNION SELECT b FROM tablename UNION SELECT c FROM tablename UNION SELECT d FROM tablename ;
?