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()
etc.) но я всегда получаю 1
. Может, потому, что они находятся в одной строке исходной таблицы?
Я знаю, что это плохой дизайн таблицы. Это не мое, я просто пытаюсь исправить это.
5 ответов
Postgres 9.4 или более поздней версии
использовать WITH ORDINALITY
для функций set-returning:
когда функция
FROM
п. суффиксомWITH ORDINALITY
, abigint
столбец добавляется к выходу, который начинается с 1 и увеличивается на 1 для каждой строки вывода функции. Это самое полезно в случае набора возвращающих функций, таких какUNNEST()
.
в сочетании с LATERAL
функция в pg 9.3+, и согласно этому поток на pgsql-хакеры, вышеуказанный запрос теперь можно написать так:
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 составляет 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 [i], i - array_lower(,1) + 1
FROM generate_series(array_lower(,1), array_upper(,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 [i], i - array_lower(,1) + 1, i
FROM generate_series(array_lower(,1), array_upper(,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
использовать Создание Индекса Functions.
http://www.postgresql.org/docs/current/static/functions-srf.html#FUNCTIONS-SRF-SUBSCRIPTS
например:
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
unnest2()
упражнения
старые версии перед ПГ и V8.4 нужна пользовательская unnest()
. Мы можем адаптировать эту старую функцию для возврата элементов с индексом:
CREATE FUNCTION unnest2(anyarray)
RETURNS TABLE(v anyelement, i integer) AS
$BODY$
SELECT [i], i
FROM generate_series(array_lower(,1),
array_upper(,1)) i;
$BODY$ LANGUAGE sql IMMUTABLE;