简体   繁体   中英

Get even / odd / all numbers between two numbers

I want to display all the numbers (even / odd / mixed) between two numbers (1-9; 2-10; 11-20) in one (or two) column.
Example initial data:

| 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');

Result:

| output                         |
----------------------------------
| 1,3,5,7,9                      |
| 2,4,6,8,10                     |
| 11,12,13,14,15,16,17,18,19,20  |

Something like this:

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

This outputs:

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

Building on your first example, simplified, but with PK:

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

Use split_part() to extract lower and upper bound. ( regexp_split_to_array() would be needlessly expensive and error-prone). And generate_series() to generate the numbers.

Use a LATERAL join and aggregate the set immediately to simplify aggregation. An ARRAY constructor is fastest in this case:

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, you want every number in the range only if upper and lower bound are a mix of even and odd numbers. Else, only return every 2nd number, resulting in even / odd numbers for those cases. This expression implements the calculation of the interval:

CASE WHEN (z-a)%2 = 0 THEN 2 ELSE 1 END

Result as desired:

output
-----------------------------
1,3,5,7,9
2,4,6,8,10
11,12,13,14,15,16,17,18,19,20

You do not need WITH ORDINALITY in this case, because the order of elements is guaranteed.

The aggregate function array_agg() makes the query slightly shorter (but slower) - or use string_agg() to produce a string directly, depending on your desired output format:

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;

Note a subtle difference when using an aggregate function or ARRAY constructor in the LATERAL subquery: Normally, rows with rang IS NULL are excluded from the result because the LATERAL subquery returns no row .
If you aggregate the result immediately, "no row" is transformed to one row with a NULL value, so the original row is preserved. I added demos to the fiddle.

SQL Fiddle.

You do not need a CTE for this, which would be more expensive.

Aside: The type conversion to integer removes leading / training white space automatically, so a string like this works as well for rank : ' 1 - 3' .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM