获取两个数字之间的偶数/奇数/所有数字

Posted

技术标签:

【中文标题】获取两个数字之间的偶数/奇数/所有数字【英文标题】:Get even / odd / all numbers between two numbers 【发布时间】:2015-11-13 17:31:59 【问题描述】:

我想在一列(或两列)中显示两个数字(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  |

【问题讨论】:

rang 可以为空还是 NULL?结果应该是数组还是字符串?还是说一套会更好? 【参考方案1】:

类似这样的:

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

【讨论】:

感谢您的回答!第一行和第二行可能只生成偶数和奇数吗?【参考方案2】:

以您的第一个示例为基础,经过简化,但带有 PK:

CREATE TABLE tbl1 (
  tbl1_id serial PRIMARY KEY  -- optional
, rang text  -- can be NULL ?
);

使用split_part() 提取下限和上限。 (regexp_split_to_array() 会不必要地昂贵且容易出错)。和generate_series() 生成数字。

使用LATERAL join 并立即聚合集合以简化聚合。在这种情况下,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,如果上限和下限是偶数和奇数的混合,您需要 范围内的每个数字。否则,只返回每第二个数字,在这些情况下产生偶数/奇数。这个表达式实现了区间的计算:

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() 使查询略短(但速度较慢) - 或使用 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 子查询返回无行。 如果您立即聚合结果,“无行”将转换为具有 NULL 值的一行,因此保留原始行。我在小提琴中添加了演示。

SQL Fiddle.

你不需要 CTE,这会更贵。

除此之外:到integer 的类型转换会自动删除前导/训练空白,因此这样的字符串也适用于rank' 1 - 3'

【讨论】:

谢谢,这很有帮助...想知道是否有任何选项可以使用范围数据类型创建范围,并通过步骤创建奇数或偶数或混合范围,例如 range(lower-bound,upper-bound , step) 具有内置范围类型

以上是关于获取两个数字之间的偶数/奇数/所有数字的主要内容,如果未能解决你的问题,请参考以下文章

24输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

手动输入两个数字m和n,运算求出m~n之间所有偶数的和

调整该数组中数字的顺序,奇数在前,偶数在后

调整数组顺序使奇数位于偶数前面

面试题14: 调整数组顺序使奇数位于偶数前面

调整数组顺序使奇数位于偶数前面