Làm cách nào để tạo chuỗi 1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1, Tập trong SQL hoặc T-SQL tiêu chuẩn?


11

Cho hai số nmtôi muốn tạo một loạt các mẫu

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

và lặp lại nó m lần

Chẳng hạn, cho n = 3m = 4, tôi muốn một chuỗi gồm 24 số sau:

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

Tôi biết cách đạt được kết quả này trong PostgreSQL bằng một trong hai phương pháp:

Sử dụng truy vấn sau, sử dụng generate_serieshàm và một vài thủ thuật để đảm bảo rằng thứ tự là đúng:

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 ;

... hoặc sử dụng một chức năng cho cùng một mục đích, với các vòng lặp bổ trợ và lồng nhau:

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 ;

Làm thế nào tôi có thể làm tương đương trong SQL tiêu chuẩn hoặc trong Máy chủ Transact-SQL / SQL?

Câu trả lời:


4

Trong Postgres, thật dễ dàng khi sử dụng generate_series()chức năng:

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 ;

Trong SQL tiêu chuẩn - và giả sử rằng có giới hạn hợp lý về kích thước của các tham số n, m, tức là dưới một triệu - bạn có thể sử dụng Numbersbảng:

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

điền nó với phương thức ưa thích của DBMS của bạn:

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

và sau đó sử dụng nó, thay vì 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 ;

Trong thực tế, tôi không mong đợi những con số đó lớn hơn 100; nhưng trên lý thuyết họ có thể là bất cứ điều gì.
joanolo

10

Bưu điện

Bạn có thể làm cho nó hoạt động với một toán học đơn generate_series() và cơ bản (xem các hàm toán học ).

Được gói vào một hàm SQL đơn giản:

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;

Gọi:

SELECT * FROM generate_up_down_series(3, 4);

Tạo kết quả mong muốn. nm có thể là bất kỳ số nguyên nào trong đó n * 2 * m không trànint4 .

Làm sao?

Trong truy vấn con:

  • Tạo tổng số hàng mong muốn ( n * 2 * m ), với số tăng dần đơn giản. Tôi đặt tên cho nó n2m. 0 đến N-1 (không phải 1 đến N ) để đơn giản hóa thao tác modulo sau .

  • Lấy % n * 2 ( %là toán tử modulo) để có được một chuỗi n số tăng dần, m lần. Tôi đặt tên cho nó n2.

Trong truy vấn bên ngoài:

  • Thêm 1 vào nửa dưới ( n2 <n ).

  • Cho nửa trên ( n2> = n ) gương của nửa dưới có n * 2 - n2 .

  • Tôi đã thêm ORDER BYđể đảm bảo thứ tự yêu cầu. Với các phiên bản hiện tại hoặc Postgres, nó cũng hoạt động mà không cần ORDER BYtruy vấn đơn giản - nhưng không nhất thiết phải trong các truy vấn phức tạp hơn! Đó là một chi tiết triển khai (và nó sẽ không thay đổi) nhưng không được bảo đảm bởi tiêu chuẩn SQL.

Không may, generate_series() là Postgres cụ thể và không phải là SQL tiêu chuẩn, như đã được nhận xét. Nhưng chúng ta có thể sử dụng lại cùng một logic:

SQL chuẩn

Bạn có thể tạo các số sê-ri bằng CTE đệ quy thay vì generate_series()hoặc hiệu quả hơn để sử dụng nhiều lần, tạo một bảng có số nguyên nối tiếp một lần. Bất cứ ai cũng có thể đọc, không ai có thể viết cho nó!

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;

Sau đó, những điều trên SELECTtrở nên đơn giản hơn:

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

Nếu bạn cần SQL đơn giản. Về mặt lý thuyết, nó phải hoạt động trên hầu hết các DBMS (được thử nghiệm trên PostgreSQL và 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;

Giải trình

  1. Tạo chuỗi 1..n

    Giả sử rằng n=3

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

    Nó khá đơn giản và có thể được tìm thấy trong hầu hết các tài liệu về CTE đệ quy. Tuy nhiên tuần sau cần hai trường hợp của mỗi giá trị

  2. Tạo chuỗi 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;

    Ở đây chúng tôi chỉ nhân đôi giá trị ban đầu, có hai hàng, nhưng bó thứ hai chúng tôi cần theo thứ tự ngược lại, vì vậy chúng tôi sẽ giới thiệu thứ tự một chút.

  3. Trước khi chúng tôi giới thiệu thứ tự quan sát rằng đây cũng là một điều. Chúng ta có thể có hai hàng trong điều kiện bắt đầu với ba cột mỗi cột, chúng ta n<3vẫn là một cột có điều kiện. Và, chúng tôi vẫn chỉ tăng giá trị của 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. Tương tự như vậy, chúng ta có thể trộn chúng lên một chút, xem sự thay đổi điều kiện bắt đầu của chúng ta ở đây : ở đây chúng ta có một (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. Tạo chuỗi 1..n, n..1

    Mẹo ở đây là tạo chuỗi, (1..n) hai lần, và sau đó chỉ cần thay đổi thứ tự trên tập thứ hai.

    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;

    Đây ilà thứ tự và zlà số của chuỗi (hoặc một nửa chuỗi nếu bạn muốn). Vì vậy, đối với chuỗi 1, chúng tôi đang tăng thứ tự từ 1 xuống 3 và đối với chuỗi 2, chúng tôi đang giảm thứ tự từ 6 xuống 4. Và cuối cùng

  6. Nhân chuỗi thành m

    (xem truy vấn đầu tiên trong câu trả lời)


3

Nếu bạn muốn một giải pháp di động, bạn cần nhận ra rằng về cơ bản đây là một vấn đề toán học .

Cho @n là số cao nhất của chuỗi và @x là vị trí của số trong chuỗi đó (bắt đầu bằng 0), hàm sau sẽ hoạt động trong 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

Bạn có thể kiểm tra nó với CTE này:

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)

(Giải thích nhanh: hàm sử dụng MODULO () để tạo một chuỗi các số lặp lại và ABS () để biến nó thành sóng zig-zag. Các hoạt động khác biến đổi sóng đó để phù hợp với kết quả mong muốn.)


2

Trong PostgreSQL, điều này thật dễ dàng,

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

Điều này hoạt động trong MS-SQL và tôi nghĩ có thể được sửa đổi cho bất kỳ hương vị SQL nào.

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

Một cách để làm điều đó trong SQL Server bằng cách sử dụng một cte đệ quy.

1) Tạo số lượng thành viên cần thiết trong chuỗi (với n = 3 và m = 4, nó sẽ là 24, là 2 * n * m)

2) Sau đó bằng cách sử dụng logic trong một casebiểu thức, bạn có thể tạo chuỗi yêu cầu.

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)

Theo đề xuất của @AndriyM .. casebiểu thức có thể được đơn giản hóa thành

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

Chỉ sử dụng toán học + - * /và Modulo cơ bản :

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;

Điều này không yêu cầu SGBD cụ thể.

Với numbersbảng số:

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

Điều này tạo ra một bảng số (1-1000) mà không cần sử dụng CTE đệ quy. Xem mẫu . 2 * n * m phải nhỏ hơn số hàng trong số.

Đầu ra với n = 3 và m = 4:

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

Phiên bản này yêu cầu bảng số nhỏ hơn (v> = n và 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;

Xem mẫu .


2

Một chức năng cơ bản sử dụng các trình vòng lặp.

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

Bưu điện

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.