如何在标准SQL或T-SQL中生成1、2、3、3、2、1、1、2、3、3、2、1…系列?


11

给定两个数字nm,我想生成一系列的表格

1, 2, ..., (n-1), n, n, (n-1), ... 2, 1

并重复m一次。

例如,对于n = 3m = 4,我需要以下24个数字组成的序列:

1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1
----------------  ----------------  ----------------  ----------------

我知道如何通过两种方法之一在PostgreSQL中实现此结果:

使用以下查询,该查询使用该generate_series函数,以及一些技巧以确保顺序正确:

WITH parameters (n, m) AS
(
    VALUES (3, 5)
)
SELECT 
    xi
FROM
(
    SELECT
        i, i AS xi
    FROM
        parameters, generate_series(1, parameters.n) AS x(i)
    UNION ALL
    SELECT
        i + parameters.n, parameters.n + 1 - i AS xi
    FROM
        parameters, generate_series(1, parameters.n) AS x(i)
) AS s0 
CROSS JOIN 
    generate_series (1, (SELECT m FROM parameters)) AS x(j)
ORDER BY
    j, i ;

...或将功能用于相同目的,以及伴随和嵌套循环:

CREATE FUNCTION generate_up_down_series(
    _elements    /* n */ integer,
    _repetitions /* m */ integer)
RETURNS SETOF integer AS
$BODY$
declare
    j INTEGER ;
    i INTEGER ;
begin
    for j in 1 .. _repetitions loop
        for i in         1 .. _elements loop
              return next i ;
        end loop ;
        for i in reverse _elements .. 1 loop
              return next i ;
        end loop ;
    end loop ;
end ;
$BODY$
LANGUAGE plpgsql IMMUTABLE STRICT ;

在标准SQL或Transact-SQL / SQL Server中,我怎么可能做等效的操作?

Answers:


4

在Postgres中,使用generate_series()函数很容易:

WITH 
  parameters (n, m) AS
  ( VALUES (3, 5) )
SELECT 
    CASE WHEN g2.i = 1 THEN gn.i ELSE p.n + 1 - gn.i END AS xi
FROM
    parameters AS p, 
    generate_series(1, p.n) AS gn (i),
    generate_series(1, 2)   AS g2 (i),
    generate_series(1, p.m) AS gm (i)
ORDER BY
    gm.i, g2.i, gn.i ;

在标准SQL中-并假设参数n,m的大小有合理的限制,即小于一百万,您可以使用一个Numbers表:

CREATE TABLE numbers 
( n int not null primary key ) ;

用您的DBMS的首选方法填充它:

INSERT INTO numbers (n)
VALUES (1), (2), .., (1000000) ;  -- some mildly complex SQL here
                                  -- no need to type a million numbers

然后使用它,而不是generate_series()

WITH 
  parameters (n, m) AS
  ( VALUES (3, 5) )
SELECT 
    CASE WHEN g2.i = 1 THEN gn.i ELSE p.n + 1 - gn.i END AS xi
FROM
    parameters AS p
  JOIN numbers AS gn (i) ON gn.i <= p.n
  JOIN numbers AS g2 (i) ON g2.i <= 2
  JOIN numbers AS gm (i) ON gm.i <= p.m 
ORDER BY
    gm.i, g2.i, gn.i ;

实际上,我不希望这些数字大于100。但从理论上讲,它们可以是任何东西。
joanolo

10

Postgres

您可以使它与单个 generate_series()和基本数学一起使用(请参阅数学函数)。

包装成一个简单的SQL函数:

CREATE OR REPLACE FUNCTION generate_up_down_series(n int, m int)
  RETURNS SETOF int AS
$func$
SELECT CASE WHEN n2 < n THEN n2 + 1 ELSE n*2 - n2 END
FROM  (
   SELECT n2m, n2m % (n*2) AS n2
   FROM   generate_series(0, n*2*m - 1) n2m
   ) sub
ORDER  BY n2m
$func$  LANGUAGE sql IMMUTABLE;

呼叫:

SELECT * FROM generate_up_down_series(3, 4);

产生所需的结果。nm可以是n * 2 * m不溢出的任何整数。int4

怎么样?

在子查询中:

  • 生成所需的总行数(n * 2 * m),并使用简单的升序数。我命名n2m0N-1(不是1N)以简化以下运算。

  • 取它%n * 2%是模运算符)以得到mn个升序数列。我命名。n2

在外部查询中:

  • 将1加到下半部分(n2 <n)。

  • 对于上半部分(n2> = n),下半部分的镜像为n * 2-n2

  • 我添加ORDER BY以保证所请求的订单。对于当前版本或Postgres,它也可以ORDER BY用于简单查询,但不一定适用于更复杂的查询!这是一个实现细节(并且不会改变),但是SQL标准并不能保证。

不幸的generate_series()是,正如已经提到的,它是Postgres特定的,而不是标准的SQL。但是我们可以重用相同的逻辑:

标准SQL

您可以使用递归CTE而不是来生成序列号generate_series(),或者为了更有效地重复使用,可以一次创建带有序列整数的表。任何人都可以阅读,没有人可以写!

CREATE TABLE int_seq (i integer);

WITH RECURSIVE cte(i) AS (
   SELECT 0
   UNION ALL
   SELECT i+1 FROM cte
   WHERE  i < 20000  -- or as many you might need!
   )
INSERT INTO int_seq
SELECT i FROM cte;

然后,以上内容SELECT变得更加简单:

SELECT CASE WHEN n2 < n THEN n2 + 1 ELSE n*2 - n2 END AS x
FROM  (
   SELECT i, i % (n*2) AS n2
   FROM   int_seq
   WHERE  i < n*2*m  -- remember: 0 to N-1
   ) sub
ORDER  BY i;

5

如果您需要纯SQL。从理论上讲,它应该可以在大多数DBMS上运行(在PostgreSQL和SQLite上测试):

with recursive 
  s(i,n,z) as (
    select * from (values(1,1,1),(3*2,1,2)) as v  -- Here 3 is n
    union all
    select
      case z when 1 then i+1 when 2 then i-1 end, 
      n+1,
      z 
    from s 
    where n < 3), -- And here 3 is n
  m(m) as (select 1 union all select m+1 from m where m < 2) -- Here 2 is m

select n from s, m order by m, i;

说明

  1. 生成系列1..n

    假如说 n=3

    with recursive s(n) as (
      select 1
      union all
      select n+1 from s where n<3
    )
    select * from s;

    它非常简单,几乎可以在有关递归CTE的所有文档中找到。但是我们需要每个值有两个实例,所以

  2. 生成系列1,1,..,n,n

    with recursive s(n) as (
      select * from (values(1),(1)) as v
      union all
      select n+1 from s where n<3
    )
    select * from s;

    在这里,我们只是将具有两行的初始值加倍,但是第二组需要以相反的顺序进行,因此我们将稍作介绍。

  3. 在介绍命令之前,请注意这也是一回事。我们可以在开始条件中有两行,每行三列,但我们n<3仍然是单列条件。而且,我们仍然只是增加的价值n

    with recursive s(i,n,z) as (
      select * from (values(1,1,1),(1,1,1)) as v
      union all
      select
        i,
        n+1,
        z 
      from s where n<3
    )
    select * from s;
  4. 同样,我们可以将它们混合在一起,在这里观看我们的起始条件变化:在这里,我们有一个(6,2)(1,1)

    with recursive s(i,n,z) as (
      select * from (values(1,1,1),(6,1,2)) as v
      union all
      select
        i,
        n+1,
        z 
      from s where n<3
    )
    select * from s;
  5. 生成系列1..n,n..1

    这里的技巧是生成两次序列(1..n),然后简单地更改第二组的顺序。

    with recursive s(i,n,z) as (
      select * from (values(1,1,1),(3*2,1,2)) as v
      union all
      select
        case z when 1 then i+1 when 2 then i-1 end, 
        n+1,
        z 
      from s where n<3
    )
    select * from s order by i;

    i是顺序,z是序列的编号(如果需要,可以是序列的一半)。因此,对于序列1,我们将顺序从1增加到3,对于序列2,我们将顺序从6减少到4。最后

  6. 将系列乘以 m

    (请参阅答案中的第一个查询)


3

如果需要便携式解决方案,则需要意识到这基本上是一个数学问题。

给定@n作为序列的最高编号,并将@x作为该序列中编号的位置(从零开始),以下函数将在SQL Server中起作用:

CREATE FUNCTION UpDownSequence
(
    @n int, -- Highest number of the sequence
    @x int  -- Position of the number we need
)
RETURNS int
AS
BEGIN
    RETURN  @n - 0.5 * (ABS((2*((@x % (@n+@n))-@n)) +1) -1)
END
GO

您可以通过以下CTE进行检查:

DECLARE @n int=3;--change the value as needed
DECLARE @m int=4;--change the value as needed

WITH numbers(num) AS (SELECT 0 
                      UNION ALL
                      SELECT num+1 FROM numbers WHERE num+1<2*@n*@m) 
SELECT num AS Position, 
       dbo.UpDownSequence(@n,num) AS number
FROM numbers
OPTION(MAXRECURSION 0)

(快速说明:该函数使用MODULO()创建一个重复数字序列,并使用ABS()将其转换成锯齿形波形。其他操作将该波形转换为与所需结果相匹配。)


2

在PostgreSQL中,这很容易,

CREATE OR REPLACE FUNCTION generate_up_down_series(n int, m int)
RETURNS setof int AS $$
SELECT x FROM (
  SELECT 1, ordinality AS o, x FROM generate_series(1,n) WITH ORDINALITY AS t(x)
  UNION ALL
  SELECT 2, ordinality AS o, x FROM generate_series(n,1,-1) WITH ORDINALITY AS t(x)
) AS t(o1,o2,x)
CROSS JOIN (
  SELECT * FROM generate_series(1,m)
) AS g(y)
ORDER BY y,o1,o2
$$ LANGUAGE SQL;

2

这在MS-SQL中有效,我认为可以针对任何SQL风格进行修改。

declare @max int, @repeat int, @rid int

select @max = 3, @repeat = 4

-- create a temporary table
create table #temp (row int)

--create seed rows
while (select count(*) from #temp) < @max * @repeat * 2
begin
    insert into #temp
    select 0
    from (values ('a'),('a'),('a'),('a'),('a')) as a(col1)
    cross join (values ('a'),('a'),('a'),('a'),('a')) as b(col2)
end

-- set row number can also use identity
set @rid = -1

update #temp
set     @rid = row = @rid + 1

-- if the (row/max) is odd, reverse the order
select  case when (row/@max) % 2 = 1 then @max - (row%@max) else (row%@max) + 1 end
from    #temp
where   row < @max * @repeat * 2
order by row

2

在SQL Server中使用递归cte的一种方法。

1)生成序列中所需的成员数(对于n = 3和m = 4,它将是24,即2 * n * m)

2)之后,在case表达式中使用逻辑,您可以生成所需的序列。

Sample Demo

declare @n int=3;--change the value as needed
declare @m int=4;--change the value as needed

with numbers(num) as (select 1 
                      union all
                      select num+1 from numbers where num<2*@n*@m) 
select case when (num/@n)%2=0 and num%@n<>0 then num%@n 
            when (num/@n)%2=0 and num%@n=0 then 1  
            when (num/@n)%2=1 and num%@n<>0 then @n+1-(num%@n)  
            when (num/@n)%2=1 and num%@n=0 then @n
       end as num
from numbers
OPTION(MAXRECURSION 0)

如@AndriyM ..所建议,该case表达式可以简化为

with numbers(num) as (select 0
                      union all
                      select num+1 from numbers where num<2*@n*@m-1) 
select case when (num/@n)%2=0 then num%@n + 1
            when (num/@n)%2=1 then @n - num%@n
       end as num
from numbers
OPTION(MAXRECURSION 0)

Demo


2

仅使用基本数学+ - * /和模数:

SELECT x
    , s = x % (2*@n) +
         (1-2*(x % @n)) * ( ((x-1) / @n) % 2)
FROM (SELECT TOP(2*@n*@m) x FROM numbers) v(x)
ORDER BY x;

这不需要特定的SGBD。

随着numbers作为一个数字表:

...; 
WITH numbers(x) AS(
    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
    FROM (VALUES(0), (0), (0), (0), (0), (0), (0), (0), (0), (0)) AS n0(x)
    CROSS JOIN (VALUES(0), (0), (0), (0), (0), (0), (0), (0), (0), (0)) AS n1(x)
    CROSS JOIN (VALUES(0), (0), (0), (0), (0), (0), (0), (0), (0), (0)) AS n2(x)
)
...

这将生成数字表(1-1000),而无需使用递归CTE。请参阅示例。2 * n * m必须小于数字中的行数。

n = 3和m = 4的输出:

x   s
1   1
2   2
3   3
4   3
5   2
6   1
7   1
8   2
... ...

此版本需要较小的数字表(v> = n和v> = m):

WITH numbers(v) AS(
    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
    FROM (VALUES(1), (2), (3), (4), (5), (6), ...) AS n(x)
)
SELECT ord = @n*(v+2*m) + n
    , n*(1-v) + ABS(-@n-1+n)*v
FROM (SELECT TOP(@n) v FROM numbers ORDER BY v ASC) n(n)
CROSS JOIN (VALUES(0), (1)) AS s(v)
CROSS JOIN (SELECT TOP(@m) v-1 FROM numbers ORDER BY v ASC) m(m)
ORDER BY ord;

请参阅示例


2

使用迭代器的基本功能。

T-SQL

create function generate_up_down_series(@max int, @rep int)
returns @serie table
(
    num int
)
as
begin

    DECLARE @X INT, @Y INT;
    SET @Y = 0;

    WHILE @Y < @REP
    BEGIN

        SET @X = 1;
        WHILE (@X <= @MAX)
        BEGIN
            INSERT @SERIE
            SELECT @X;
            SET @X = @X + 1;
        END

        SET @X = @MAX;
        WHILE (@X > 0)
        BEGIN
            INSERT @SERIE
            SELECT @X;
            SET @X = @X -1;
        END

        SET @Y = @Y + 1;
    END

    RETURN;
end
GO

Postgres

create or replace function generate_up_down_series(maxNum int, rep int)
returns table (serie int) as
$body$
declare
    x int;
    y int;
    z int;
BEGIN

    x := 0;
    while x < rep loop

        y := 1;
        while y <= maxNum loop
            serie := y;
            return next;
            y := y + 1;
        end loop;

        z := maxNum;
        while z > 0 loop
            serie := z;
            return next;
            z := z - 1;
        end loop;

        x := x + 1;
    end loop;

END;
$body$ LANGUAGE plpgsql;

1
declare @n int = 5;
declare @m int = 3;
declare @t table (i int, pk int identity);
WITH  cte1 (i) 
AS ( SELECT 1
     UNION ALL
     SELECT i+1 FROM cte1
     WHERE  i < 100  -- or as many you might need!
   )
insert into @t(i) select i from cte1 where i <= @m  order by i
insert into @t(i) select i from @t order by i desc
select t.i --, t.pk, r.pk 
from @t as t 
cross join (select pk from @t where pk <= @n) as r
order by r.pk, t.pk
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.