获取偶数/奇数/两个数字之间的所有数字
Get even / odd / all numbers between two numbers
我想在一个(或两个)列中显示两个数字(1-9;2-10;11-20)之间的所有数字(偶数/奇数/混合)。
示例初始数据:
| rang | | r1 | r2 |
-------- -----|-----
| 1-9 | | 1 | 9 |
| 2-10 | | 2 | 10 |
| 11-20 | or | 11 | 20 |
CREATE TABLE initialtableone(rang TEXT);
INSERT INTO initialtableone(rang) VALUES
('1-9'),
('2-10'),
('11-20');
CREATE TABLE initialtabletwo(r1 NUMERIC, r2 NUMERIC);
INSERT INTO initialtabletwo(r1, r2) VALUES
('1', '9'),
('2', '10'),
('11', '20');
结果:
| output |
----------------------------------
| 1,3,5,7,9 |
| 2,4,6,8,10 |
| 11,12,13,14,15,16,17,18,19,20 |
像这样:
create table ranges (range varchar);
insert into ranges
values
('1-9'),
('2-10'),
('11-20');
with bounds as (
select row_number() over (order by range) as rn,
range,
(regexp_split_to_array(range,'-'))[1]::int as start_value,
(regexp_split_to_array(range,'-'))[2]::int as end_value
from ranges
)
select rn, range, string_agg(i::text, ',' order by i.ordinality)
from bounds b
cross join lateral generate_series(b.start_value, b.end_value) with ordinality i
group by rn, range
这输出:
rn | range | string_agg
---+-------+------------------------------
3 | 2-10 | 2,3,4,5,6,7,8,9,10
1 | 1-9 | 1,2,3,4,5,6,7,8,9
2 | 11-20 | 11,12,13,14,15,16,17,18,19,20
在您的第一个示例的基础上进行了简化,但使用了 PK:
CREATE TABLE tbl1 (
tbl1_id serial PRIMARY KEY -- optional
, rang text -- can be NULL ?
);
使用split_part()
to extract lower and upper bound. (regexp_split_to_array()
would be needlessly expensive and error-prone). And generate_series()
生成数字。
在这种情况下使用 and aggregate the set immediately to simplify aggregation. An ARRAY constructor 是最快的:
SELECT t.tbl1_id, a.output -- array; added id is optional
FROM (
SELECT tbl1_id
, split_part(rang, '-', 1)::int AS a
, split_part(rang, '-', 2)::int AS z
FROM tbl1
) t
, LATERAL (
SELECT ARRAY( -- preserves rows with NULL
SELECT g FROM generate_series(a, z, CASE WHEN (z-a)%2 = 0 THEN 2 ELSE 1 END) g
) AS output
) a;
AIUI,如果上限和下限是偶数和奇数的混合,则您需要 每个 范围内的数字 仅。否则,每第二个数字只有 return,导致这些情况的偶数/奇数。这个表达式实现了区间的计算:
CASE WHEN (z-a)%2 = 0 THEN 2 ELSE 1 END
结果如愿:
output
-----------------------------
1,3,5,7,9
2,4,6,8,10
11,12,13,14,15,16,17,18,19,20
在这种情况下,你不需要WITH ORDINALITY
,因为元素的顺序是有保证的。
聚合函数array_agg()
makes the query slightly shorter (but slower) - or use string_agg()
直接生成一个字符串,具体取决于你想要的输出格式:
SELECT a.output -- string
FROM (
SELECT split_part(rang, '-', 1)::int AS a
, split_part(rang, '-', 2)::int AS z
FROM tbl1
) t
, LATERAL (
SELECT string_agg(g::text, ',') AS output
FROM generate_series(a, z, CASE WHEN (z-a)%2 = 0 THEN 2 ELSE 1 END) g
) a;
注意在 LATERAL
子查询中使用聚合函数或 ARRAY
构造函数时的 细微差别:通常,具有 rang IS NULL
的行是从结果中排除,因为 LATERAL
子查询 returns 没有行 .
如果立即聚合结果,"no row" 将转换为 一行 并具有 NULL 值,因此保留原始行。我在 fiddle.
添加了演示
您不需要 CTE,这会更贵。
旁白:integer
的类型转换会自动删除前导/训练白色 space,因此这样的字符串也适用于 rank
:' 1 - 3'
。
我想在一个(或两个)列中显示两个数字(1-9;2-10;11-20)之间的所有数字(偶数/奇数/混合)。
示例初始数据:
| rang | | r1 | r2 |
-------- -----|-----
| 1-9 | | 1 | 9 |
| 2-10 | | 2 | 10 |
| 11-20 | or | 11 | 20 |
CREATE TABLE initialtableone(rang TEXT);
INSERT INTO initialtableone(rang) VALUES
('1-9'),
('2-10'),
('11-20');
CREATE TABLE initialtabletwo(r1 NUMERIC, r2 NUMERIC);
INSERT INTO initialtabletwo(r1, r2) VALUES
('1', '9'),
('2', '10'),
('11', '20');
结果:
| output |
----------------------------------
| 1,3,5,7,9 |
| 2,4,6,8,10 |
| 11,12,13,14,15,16,17,18,19,20 |
像这样:
create table ranges (range varchar);
insert into ranges
values
('1-9'),
('2-10'),
('11-20');
with bounds as (
select row_number() over (order by range) as rn,
range,
(regexp_split_to_array(range,'-'))[1]::int as start_value,
(regexp_split_to_array(range,'-'))[2]::int as end_value
from ranges
)
select rn, range, string_agg(i::text, ',' order by i.ordinality)
from bounds b
cross join lateral generate_series(b.start_value, b.end_value) with ordinality i
group by rn, range
这输出:
rn | range | string_agg
---+-------+------------------------------
3 | 2-10 | 2,3,4,5,6,7,8,9,10
1 | 1-9 | 1,2,3,4,5,6,7,8,9
2 | 11-20 | 11,12,13,14,15,16,17,18,19,20
在您的第一个示例的基础上进行了简化,但使用了 PK:
CREATE TABLE tbl1 (
tbl1_id serial PRIMARY KEY -- optional
, rang text -- can be NULL ?
);
使用split_part()
to extract lower and upper bound. ( would be needlessly expensive and error-prone). And regexp_split_to_array()
generate_series()
生成数字。
在这种情况下使用
SELECT t.tbl1_id, a.output -- array; added id is optional
FROM (
SELECT tbl1_id
, split_part(rang, '-', 1)::int AS a
, split_part(rang, '-', 2)::int AS z
FROM tbl1
) t
, LATERAL (
SELECT ARRAY( -- preserves rows with NULL
SELECT g FROM generate_series(a, z, CASE WHEN (z-a)%2 = 0 THEN 2 ELSE 1 END) g
) AS output
) a;
AIUI,如果上限和下限是偶数和奇数的混合,则您需要 每个 范围内的数字 仅。否则,每第二个数字只有 return,导致这些情况的偶数/奇数。这个表达式实现了区间的计算:
CASE WHEN (z-a)%2 = 0 THEN 2 ELSE 1 END
结果如愿:
output
-----------------------------
1,3,5,7,9
2,4,6,8,10
11,12,13,14,15,16,17,18,19,20
在这种情况下,你不需要WITH ORDINALITY
,因为元素的顺序是有保证的。
聚合函数array_agg()
makes the query slightly shorter (but slower) - or use string_agg()
直接生成一个字符串,具体取决于你想要的输出格式:
SELECT a.output -- string
FROM (
SELECT split_part(rang, '-', 1)::int AS a
, split_part(rang, '-', 2)::int AS z
FROM tbl1
) t
, LATERAL (
SELECT string_agg(g::text, ',') AS output
FROM generate_series(a, z, CASE WHEN (z-a)%2 = 0 THEN 2 ELSE 1 END) g
) a;
注意在 LATERAL
子查询中使用聚合函数或 ARRAY
构造函数时的 细微差别:通常,具有 rang IS NULL
的行是从结果中排除,因为 LATERAL
子查询 returns 没有行 .
如果立即聚合结果,"no row" 将转换为 一行 并具有 NULL 值,因此保留原始行。我在 fiddle.
您不需要 CTE,这会更贵。
旁白:integer
的类型转换会自动删除前导/训练白色 space,因此这样的字符串也适用于 rank
:' 1 - 3'
。