PostgreSQL unnest() with element number
当我有一个具有分隔值的列时,我可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 ... |
我如何包含元素编号?即:
1 2 3 4 5 6 7 8 | id | elem | nr ---+------+--- 1 | ab | 1 1 | cd | 2 1 | efg | 3 1 | hi | 4 2 | jk | 1 ... |
我想要源字符串中每个元素的原始位置。 我尝试过使用窗口函数(
我知道这是一个糟糕的桌面设计。 这不是我的,我只是想解决它。
Postgres 9.4或更高版本
使用
When a function in the
FROM clause is suffixed byWITH ORDINALITY , a
bigint column is appended to the output which starts from 1 and
increments by 1 for each row of the function's output. This is most
useful in the case of set returning functions such asUNNEST() .
结合pg 9.3+中的
1 2 3 4 | 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; |
1 2 | SELECT t.id, a.elem, a.nr FROM tbl t, unnest(string_to_array(t.elements, ',')) WITH ORDINALITY a(elem, nr); |
或者更简单,如果基于实际数组(
1 2 | SELECT t.id, a.elem, a.nr FROM tbl t, unnest(t.arr) WITH ORDINALITY a(elem, nr); |
甚至,使用最少的语法:
1 2 | SELECT id, a, ordinality FROM tbl, unnest(arr) WITH ORDINALITY a; |
Postgres 8.4 - 9.3
使用
你可以简单地省略
1 2 | SELECT *, ROW_NUMBER() OVER (PARTITION BY id) AS nr FROM (SELECT id, regexp_split_to_table(elements, ',') AS elem FROM tbl) t; |
虽然这通常有效,但我从未在简单查询中看到过它,但PostgreSQL没有关于没有
要保证以空格分隔的字符串中的元素的序数:
1 2 3 4 5 | 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; |
或者如果基于实际数组更简单:
1 2 | SELECT id, arr[nr] AS elem, nr FROM (SELECT *, generate_subscripts(arr, 1) AS nr FROM tbl) t; |
关于dba.SE的相关回答:
- 如何保持unnested数组中元素的原始顺序?
Postgres 8.1 - 8.4
这些功能都不可用,但是:
但这有效:
1 2 3 4 | 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'; |
特别要注意,数组索引可以与元素的顺序位置不同。考虑这个具有扩展功能的演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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 |
相比:
- 规范化1维数组的数组下标,使它们从1开始
尝试:
1 2 3 4 5 | 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#FUNCTIONS-SRF-SUBSCRIPTS
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 ; |
更简单:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 ; |
如果元素的顺序不重要,你可以
1 2 3 4 5 6 7 8 | 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 v8.4之前的旧版本需要用户定义的
1 2 3 4 5 6 7 | 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; |