Bagaimana cara menghasilkan seri 1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1,… dalam standar SQL atau T-SQL?


11

Diberi dua angka ndan m, saya ingin menghasilkan serangkaian formulir

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

dan ulangi mkali.

Misalnya, untuk n = 3dan m = 4, saya ingin urutan 24 nomor berikut:

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

Saya tahu bagaimana mencapai hasil ini dalam PostgreSQL dengan salah satu dari dua metode berikut:

Menggunakan kueri berikut, yang menggunakan generate_seriesfungsi, dan beberapa trik untuk menjamin bahwa urutannya benar:

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 ;

... atau gunakan fungsi untuk tujuan yang sama, dengan adjoin dan nested loop:

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 ;

Bagaimana saya bisa melakukan yang setara dalam SQL standar atau dalam Transact-SQL / SQL Server?

Jawaban:


4

Di Postgres, mudah menggunakan generate_series()fungsinya:

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 ;

Dalam SQL standar - dan dengan asumsi bahwa ada batasan yang masuk akal pada ukuran parameter n, m, yaitu kurang dari satu juta - Anda dapat menggunakan Numberstabel:

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

isi dengan metode pilihan DBMS Anda:

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

lalu gunakan, alih-alih 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 ;

Dalam praktiknya, saya tidak berharap angka-angka itu lebih besar dari 100; tetapi secara teori mereka bisa menjadi apa saja.
joanolo

10

Postgres

Anda dapat membuatnya bekerja dengan matematika tunggal generate_series() dan dasar (lihat fungsi matematika ).

Dibungkus menjadi fungsi SQL sederhana:

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;

Panggilan:

SELECT * FROM generate_up_down_series(3, 4);

Menghasilkan hasil yang diinginkan. n dan m dapat berupa bilangan bulat apa pun di mana n * 2 * m tidak meluap int4.

Bagaimana?

Dalam subquery:

  • Hasilkan jumlah total baris yang diinginkan ( n * 2 * m ), dengan angka naik sederhana. Saya beri nama n2m. 0 hingga N-1 (bukan 1 ke N ) untuk menyederhanakan operasi modulo berikut .

  • Ambillah % n * 2 ( %adalah operator modulo) untuk mendapatkan serangkaian n angka naik, m kali. Saya beri nama n2.

Di kueri luar:

  • Tambahkan 1 ke bagian bawah ( n2 <n ).

  • Untuk bagian atas ( n2> = n ) cermin bagian bawah dengan n * 2 - n2 .

  • Saya menambahkan ORDER BYuntuk menjamin pesanan yang diminta. Dengan versi saat ini atau Postgres juga berfungsi tanpa ORDER BYuntuk permintaan sederhana - tetapi tidak harus dalam permintaan yang lebih kompleks! Itu detail implementasi (dan itu tidak akan berubah) tetapi tidak dijamin oleh standar SQL.

Sayangnya, generate_series()Postgres spesifik dan bukan SQL standar, seperti yang telah dikomentari. Tapi kita bisa menggunakan kembali logika yang sama:

SQL standar

Anda dapat membuat nomor seri dengan CTE rekursif alih-alih generate_series(), atau, lebih efisien untuk penggunaan berulang, membuat tabel dengan nomor integer seri satu kali. Siapa pun dapat membaca, tidak ada yang bisa menulis untuk itu!

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;

Kemudian, di atas SELECTmenjadi lebih sederhana:

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

Jika Anda membutuhkan SQL biasa. Secara teoritis itu harus bekerja pada kebanyakan DBMS (diuji pada PostgreSQL dan 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;

Penjelasan

  1. Hasilkan seri 1..n

    Berasumsi bahwa n=3

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

    Ini cukup sederhana dan dapat ditemukan di hampir semua dokumen tentang CTE rekursif. Namun kami membutuhkan dua contoh dari masing-masing nilai jadi

  2. Hasilkan seri 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;

    Di sini kita hanya menggandakan nilai awal, yang memiliki dua baris, tetapi tandan kedua yang kita butuhkan dalam urutan terbalik, jadi kami akan memperkenalkan urutannya sedikit.

  3. Sebelum kami memperkenalkan pesanan perhatikan bahwa ini juga hal. Kami dapat memiliki dua baris dalam kondisi awal dengan masing-masing tiga kolom, kami n<3masih bersyarat kolom tunggal. Dan, kami masih saja meningkatkan nilainya 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. Demikian juga, kita dapat mencampurnya sedikit, perhatikan kondisi awal kita berubah di sini : di sini kita memiliki (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. Hasilkan seri 1..n, n..1

    Kuncinya di sini adalah untuk menghasilkan seri, (1..n) dua kali, dan kemudian cukup mengubah urutan pada set kedua.

    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;

    Ini iadalah urutan dan znomor urutan (atau setengah dari urutan jika Anda mau). Jadi untuk urutan 1 kami meningkatkan pesanan dari 1 menjadi 3 dan untuk urutan 2 kami mengurangi pesanan dari 6 menjadi 4. Dan akhirnya

  6. Lipat gandakan seri menjadi m

    (lihat pertanyaan pertama dalam jawabannya)


3

Jika Anda menginginkan solusi portabel, Anda perlu menyadari bahwa ini pada dasarnya adalah masalah matematika .

Diberikan @n sebagai jumlah urutan tertinggi dan @x sebagai posisi nomor dalam urutan tersebut (dimulai dengan nol), fungsi berikut akan bekerja di 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

Anda dapat memeriksanya dengan CTE ini:

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)

(Penjelasan cepat: fungsi menggunakan MODULO () untuk membuat urutan angka berulang dan ABS () untuk mengubahnya menjadi gelombang zig-zag. Operasi lain mengubah gelombang itu agar sesuai dengan hasil yang diinginkan.)


2

Di PostgreSQL, ini mudah,

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

Ini berfungsi dalam MS-SQL dan saya pikir dapat dimodifikasi untuk setiap rasa 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

Cara untuk melakukannya di SQL Server menggunakan cyt rekursif.

1) Hasilkan jumlah anggota yang diperlukan dalam seri (untuk n = 3 dan m = 4 akan menjadi 24 yaitu 2 * n * m)

2) Setelah itu menggunakan logika dalam caseekspresi, Anda dapat menghasilkan seri yang diperlukan.

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)

Seperti yang disarankan oleh @AndriyM .. caseekspresi dapat disederhanakan

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

Hanya menggunakan Matematika + - * /dan Modulo dasar :

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;

Ini tidak memerlukan SGBD tertentu.

Dengan numbersmenjadi tabel angka:

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

Ini menghasilkan tabel angka (1-1000) tanpa menggunakan CTE rekursif. Lihat Sampel . 2 * n * m harus lebih kecil dari jumlah baris dalam angka.

Output dengan n = 3 dan m = 4:

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

Versi ini membutuhkan tabel angka yang lebih kecil (v> = n dan 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;

Lihat Sampel .


2

Fungsi dasar menggunakan iterator.

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.