วิธีสร้างซีรีย์ 1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1, ... ใน SQL มาตรฐานหรือ T-SQL


11

ด้วยตัวเลขสองตัวnและmฉันต้องการสร้างชุดของแบบฟอร์ม

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

และทำซ้ำmครั้ง

ตัวอย่างเช่นสำหรับn = 3และm = 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 ;

... หรือใช้ฟังก์ชันเพื่อจุดประสงค์เดียวกันโดยมีลูป adjoint และซ้อนกัน:

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 ได้อย่างไร

คำตอบ:


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

สร้างผลลัพธ์ที่ต้องการ nและม.สามารถใด ๆจำนวนเต็มที่n * 2 * เมตรint4ไม่ล้น

อย่างไร?

ในแบบสอบถามย่อย:

  • สร้างจำนวนแถวทั้งหมดที่ต้องการ ( n * 2 * m ) ด้วยจำนวนจากน้อยไปหามาก n2mผมชื่อมัน 0ถึงN-1 (ไม่ใช่1ถึงN ) เพื่อทำให้การดำเนินการโมดูโลต่อไปนี้ง่ายขึ้น

  • รับไปได้% n * 2 ( %เป็นโอเปอเรเตอร์โมดูโล) เพื่อรับชุดตัวเลขnจากน้อยไปหามากmคูณ 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 ธรรมดา ในทางทฤษฎีมันควรจะทำงานกับDBMSs ส่วนใหญ่ (ทดสอบบน 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 โดยใช้ recursive 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.