PostgreSQL unnest() с номером элемента
Когда у меня есть столбец с разделенными значениями, я могу использовать unnest()
функция:
myTable
id | elements
---+------------
1 |ab,cd,efg,hi
2 |jk,lm,no,pq
3 |rstuv,wxyz
select id, unnest(string_to_array(elements, ',')) AS elem
from myTable
id | elem
---+-----
1 | ab
1 | cd
1 | efg
1 | hi
2 | jk
...
Как я могу включить номера элементов? То есть:
id | elem | nr
---+------+---
1 | ab | 1
1 | cd | 2
1 | efg | 3
1 | hi | 4
2 | jk | 1
...
Я хочу, чтобы исходная позиция каждого элемента в исходной строке. Я пробовал с оконными функциями (row_number()
, rank()
и т.д.) но я всегда получаю 1
, Может быть, потому что они находятся в одной строке исходной таблицы?
Я знаю, что это плохой дизайн стола. Это не мое, я просто пытаюсь это исправить.
6 ответов
Postgres 9.4 или позже
использование WITH ORDINALITY
для функций, возвращающих множество:
Когда функция в
FROM
предложение дополненоWITH ORDINALITY
,bigint
Столбец добавляется к выводу, который начинается с 1 и увеличивается на 1 для каждой строки вывода функции. Это наиболее полезно в случае набора возвращающих функций, таких какUNNEST()
,
В сочетании с LATERAL
особенность в pg 9.3+, и в соответствии с этой веткой на pgsql-hackers, вышеупомянутый запрос теперь может быть записан как:
SELECT t.id, a.elem, a.nr
FROM tbl AS t
LEFT JOIN LATERAL unnest(string_to_array(t.elements, ','))
WITH ORDINALITY AS a(elem, nr) ON TRUE;
LEFT JOIN ... ON TRUE
сохраняет все строки в левой таблице, даже если табличное выражение справа не возвращает строк. Если это не имеет значения, вы можете использовать эту эквивалентную, менее многословную форму с неявным CROSS JOIN LATERAL
:
SELECT t.id, a.elem, a.nr
FROM tbl t, unnest(string_to_array(t.elements, ',')) WITH ORDINALITY a(elem, nr);
Или проще, если основано на реальном массиве (arr
будучи столбцом массива):
SELECT t.id, a.elem, a.nr
FROM tbl t, unnest(t.arr) WITH ORDINALITY a(elem, nr);
Или даже с минимальным синтаксисом:
SELECT id, a, ordinality
FROM tbl, unnest(arr) WITH ORDINALITY a;
a
автоматически псевдоним таблицы и столбца. Имя по умолчанию для добавленного столбца ординальности: ordinality
, Но лучше (безопаснее, чище) добавлять явные псевдонимы столбцов и столбцы таблицы квалификации.
Postgres 8,4 - 9,3
С row_number() OVER (PARTITION BY id ORDER BY elem)
Вы получаете числа в соответствии с порядком сортировки, а не порядковым номером исходной порядковой позиции в строке.
Вы можете просто опустить ORDER BY
:
SELECT *, row_number() OVER (PARTITION by id) AS nr
FROM (SELECT id, regexp_split_to_table(elements, ',') AS elem FROM tbl) t;
Хотя это обычно работает, и я никогда не видел, чтобы оно ломалось в простых запросах, PostgreSQL ничего не утверждает относительно порядка строк без ORDER BY
, Это работает из-за деталей реализации.
Чтобы гарантировать порядковые номера элементов в строке через пробел:
SELECT id, arr[nr] AS elem, nr
FROM (
SELECT *, generate_subscripts(arr, 1) AS nr
FROM (SELECT id, string_to_array(elements, ' ') AS arr FROM tbl) t
) sub;
Или проще, если основано на реальном массиве:
SELECT id, arr[nr] AS elem, nr
FROM (SELECT *, generate_subscripts(arr, 1) AS nr FROM tbl) t;
Соответствующий ответ на dba.SE:
Postgres 8.1 - 8.4
Ни одна из этих функций не доступна, но: RETURNS TABLE
, generate_subscripts()
, unnest()
, array_length()
,
Но это работает:
CREATE FUNCTION f_unnest_ord(anyarray, OUT val anyelement, OUT ordinality integer)
RETURNS SETOF record LANGUAGE sql IMMUTABLE AS
'SELECT $1[i], i - array_lower($1,1) + 1
FROM generate_series(array_lower($1,1), array_upper($1,1)) i';
В частности, обратите внимание, что индекс массива может отличаться от порядковых позиций элементов. Рассмотрим это демо с расширенной функцией:
CREATE FUNCTION f_unnest_ord_idx(anyarray, OUT val anyelement, OUT ordinality int, OUT idx int)
RETURNS SETOF record LANGUAGE sql IMMUTABLE AS
'SELECT $1[i], i - array_lower($1,1) + 1, i
FROM generate_series(array_lower($1,1), array_upper($1,1)) i';
SELECT id, arr, (rec).*
FROM (
SELECT *, f_unnest_ord_idx(arr) AS rec
FROM (VALUES (1, '{a,b,c}'::text[]) -- short for: '[1:3]={a,b,c}'
, (2, '[5:7]={a,b,c}')
, (3, '[-9:-7]={a,b,c}')
) t(id, arr)
) sub;
id | arr | val | ordinality | idx
----+-----------------+-----+------------+-----
1 | {a,b,c} | a | 1 | 1
1 | {a,b,c} | b | 2 | 2
1 | {a,b,c} | c | 3 | 3
2 | [5:7]={a,b,c} | a | 1 | 5
2 | [5:7]={a,b,c} | b | 2 | 6
2 | [5:7]={a,b,c} | c | 3 | 7
3 | [-9:-7]={a,b,c} | a | 1 | -9
3 | [-9:-7]={a,b,c} | b | 2 | -8
3 | [-9:-7]={a,b,c} | c | 3 | -7
Для сравнения:
Пытаться:
select v.*, row_number() over (partition by id order by elem) rn from
(select
id,
unnest(string_to_array(elements, ',')) AS elem
from myTable) v
Используйте функции генерации подписей.
http://www.postgresql.org/docs/current/static/functions-srf.html
Например:
SELECT
id
, elements[i] AS elem
, i AS nr
FROM
( SELECT
id
, elements
, generate_subscripts(elements, 1) AS i
FROM
( SELECT
id
, string_to_array(elements, ',') AS elements
FROM
myTable
) AS foo
) bar
;
Проще говоря:
SELECT
id
, unnest(elements) AS elem
, generate_subscripts(elements, 1) AS nr
FROM
( SELECT
id
, string_to_array(elements, ',') AS elements
FROM
myTable
) AS foo
;
Если порядок элементов не важен, вы можете
select
id, elem, row_number() over (partition by id) as nr
from (
select
id,
unnest(string_to_array(elements, ',')) AS elem
from myTable
) a
Я думаю, что это связано с использованием коррелированного подзапроса для присвоения произвольных ранжированных / порядковых значений окончательному набору. Это скорее практическое прикладное использование с использованием обработки массива PG для де-поворота набора данных (работает с PG 9.4).
WITH _students AS ( /** CTE **/
SELECT * FROM
( SELECT 'jane'::TEXT ,'doe'::TEXT , 1::INT
UNION
SELECT 'john'::TEXT ,'doe'::TEXT , 2::INT
UNION
SELECT 'jerry'::TEXT ,'roe'::TEXT , 3::INT
UNION
SELECT 'jodi'::TEXT ,'roe'::TEXT , 4::INT
) s ( fn, ln, id )
) /** end WITH **/
SELECT s.id
, ax.fanm
, ax.anm
, ax.val
, ax.num
FROM _students s
,UNNEST /** MULTI-UNNEST() BLOCK **/
(
( SELECT ARRAY[ fn, ln ]::text[] AS anm
/** CORRELATED SUBQUERY **/
FROM _students s2 WHERE s2.id = s.id
)
,( SELECT ARRAY[ 'first name', 'last name' ]::text[] AS fanm )
,( SELECT ARRAY[ '9','8','7'] AS val)
,( SELECT ARRAY[ 1,2,3,4,5 ] AS num)
) ax ( anm, fanm, val, num )
;
ПЕРЕДАННЫЙ НАБОР РЕЗУЛЬТАТОВ:
+--+----------+-----+----+---+
|id|fanm |anm |val |num|
+--+----------+-----+----+---+
|2 |first name|john |9 |1 |
|2 |last name |doe |8 |2 |
|2 |NULL |NULL |7 |3 |
|2 |NULL |NULL |NULL|4 |
|2 |NULL |NULL |NULL|5 |
|1 |first name|jane |9 |1 |
|1 |last name |doe |8 |2 |
|1 |NULL |NULL |7 |3 |
|1 |NULL |NULL |NULL|4 |
|1 |NULL |NULL |NULL|5 |
|4 |first name|jodi |9 |1 |
|4 |last name |roe |8 |2 |
|4 |NULL |NULL |7 |3 |
|4 |NULL |NULL |NULL|4 |
|4 |NULL |NULL |NULL|5 |
|3 |first name|jerry|9 |1 |
|3 |last name |roe |8 |2 |
|3 |NULL |NULL |7 |3 |
|3 |NULL |NULL |NULL|4 |
|3 |NULL |NULL |NULL|5 |
+--+----------+-----+----+---+
unnest2()
как упражнение
Более старые версии до pg v8.4 требуют пользовательского unnest()
, Мы можем адаптировать эту старую функцию для возврата элементов с индексом:
CREATE FUNCTION unnest2(anyarray)
RETURNS TABLE(v anyelement, i integer) AS
$BODY$
SELECT $1[i], i
FROM generate_series(array_lower($1,1),
array_upper($1,1)) i;
$BODY$ LANGUAGE sql IMMUTABLE;