Standart SQL veya T-SQL'de 1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1,… serisi nasıl oluşturulur?


11

İki sayı göz önüne alındığında nve mben formun bir dizi oluşturmak istediğiniz

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

ve tekrarlayın m.

Örneğin, n = 3ve için m = 4, aşağıdaki 24 sayıdan oluşan bir dizi istiyorum:

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'de bu sonuca iki yöntemden birini kullanarak nasıl ulaşacağımı biliyorum:

generate_seriesİşlevi kullanan aşağıdaki sorguyu ve siparişin doğru olduğunu garanti etmek için birkaç püf noktası kullanarak:

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 ;

... veya bitişik ve iç içe döngülerle aynı amaç için bir işlev kullanın:

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 ;

Standart SQL veya Transact-SQL / SQL Server'da eşdeğerini nasıl yapabilirim?

Yanıtlar:


4

Postgres'de bu generate_series()işlevi kullanmak kolaydır :

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 ;

Standart SQL'de - ve n, m, yani bir milyondan az parametrelerin boyutu için makul bir sınır olduğu varsayılarak, bir Numberstablo kullanabilirsiniz :

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

DBMS'nizin tercih edilen yöntemiyle doldurun:

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

ve bunun yerine şunu kullanın 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 ;

Uygulamada, bu sayıların 100'den büyük olmasını beklemiyorum; ama teoride herhangi bir şey olabilirler.
joanolo

10

postgres

Tek generate_series() ve basit bir matematikle çalışmasını sağlayabilirsiniz ( matematiksel fonksiyonlara bakınız ).

Basit bir SQL fonksiyonuna sarılmış:

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;

Telefon etmek:

SELECT * FROM generate_up_down_series(3, 4);

İstenen sonucu üretir. n ve m, olabilir herhangi bir tam sayıdır x 2 x m, n taşmaz int4.

Nasıl?

Alt sorguda:

  • Basit bir artan sayı ile istenen toplam satır sayısını ( n * 2 * m ) oluşturun. Adı ben n2m. Aşağıdaki modulo çalışmasını basitleştirmek için 0 ila N-1 ( 1 ila N değil ) .

  • O al % n * 2 ( %bir dizi almak için modülo operatörüdür) n artan numaraları, m süreleri. Adı ben n2.

Dış sorguda:

  • Alt yarısına 1 ekleyin ( n2 <n ).

  • N * 2 - n2 ile alt yarının üst yarısı ( n2> = n ) aynası için .

  • ORDER BYİstenen siparişi garanti altına almak için ekledim . Mevcut sürümler veya Postgres ORDER BYile basit bir sorgu olmadan da çalışır - ancak daha karmaşık sorgularda olması gerekmez! Bu bir uygulama detayıdır (ve değişmeyecektir) ancak SQL standardı tarafından garanti edilmemektedir.

Ne yazık ki, yorumlandığı gibi generate_series()Postgres'e özgü ve standart SQL değil. Ama aynı mantığı tekrar kullanabiliriz:

Standart SQL

Seri numaralarını özyinelemeli bir CTE ile oluşturabilir generate_series()veya tekrarlanan kullanım için daha verimli bir şekilde seri tamsayı numaralarına sahip bir tablo oluşturabilirsiniz. Herkes okuyabilir, kimse yazamaz!

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;

Sonra, yukarıdakiler SELECTdaha da basitleşir:

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

Düz SQL'e ihtiyacınız varsa. Teorik olarak çoğu DBMS üzerinde çalışmalıdır (PostgreSQL ve SQLite üzerinde test edilmiştir):

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;

açıklama

  1. Seri 1 oluşturun.

    Varsayalım n=3

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

    Oldukça basit ve özyinelemeli CTE'lerle ilgili hemen hemen her dokümanda bulunabilir. Ancak her değerin iki örneğine ihtiyacımız var.

  2. Seri 1,1, .., n, n oluştur

    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;

    Burada sadece iki satırı olan başlangıç ​​değerini ikiye katlıyoruz, ancak ters sırada ihtiyacımız olan ikinci demet, bu yüzden siparişi biraz tanıtacağız.

  3. Siparişi vermeden önce bunun da bir şey olduğunu gözlemleyin. Başlangıç ​​koşulunda her birinde üç sütun bulunan iki satır olabilir, bizim n<3hala tek bir sütun şartlı. Ve hala değerini arttırıyoruz 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. Benzer şekilde, onları biraz karıştırabilir, başlangıç ​​koşulu değişikliğimizi buradan izleyebiliriz : burada bir (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. Seri oluştur 1..n, n..1

    Buradaki hile, seriyi (1..n) iki kez oluşturmak ve daha sonra ikinci setteki sıralamayı değiştirmek.

    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;

    İşte isırasıdır ve z(isterseniz veya dizinin yarısı) dizisinin sayısıdır. Yani sıra 1 için sıra 1'den 3'e artar ve sıra 2 için sıra 6'dan 4'e düşer.

  6. Diziyi şununla çarp: m

    (yanıttaki ilk sorguyu inceleyin)


3

Taşınabilir bir çözüm istiyorsanız, bunun temel olarak bir matematik problemi olduğunu anlamalısınız .

@N dizinin en yüksek sayısı ve @x bu dizideki sayının konumu (sıfırdan başlayarak) olarak verildiğinde, SQL Server'da aşağıdaki işlev çalışır:

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

Bu CTE ile kontrol edebilirsiniz:

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)

(Hızlı açıklama: fonksiyon, tekrarlayan sayılar dizisi oluşturmak için MODULO () ve onu zig-zag dalgasına dönüştürmek için ABS () kullanır. Diğer işlemler bu dalgayı istenen sonuca uyacak şekilde dönüştürür.)


2

PostgreSQL'de bu kolaydır,

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

Bu MS-SQL çalışır ve bence herhangi bir SQL lezzet için değiştirilebilir.

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'da yinelemeli bir cte kullanarak bunu yapmanın bir yolu.

1) Seride gerekli sayıda üye oluşturun (n = 3 ve m = 4 için 2, n * m * m olan 24 olur)

2) caseİfadede mantık kullanarak bundan sonra gerekli seriyi oluşturabilirsiniz.

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 .. tarafından önerildiği gibi caseifade basitleştirilebilir

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

Yalnızca temel Math + - * /ve Modulo'yu kullanma:

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;

Bu belirli bir SGBD gerektirmez.

İle numbersbir sayı tablosu olma:

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

Bu, özyinelemeli bir CTE kullanmadan bir sayı tablosu (1-1000) oluşturur. Bakınız Örnek . 2 * n * m, sayı olarak satır sayısından küçük olmalıdır.

N = 3 ve m = 4 ile çıktı:

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

Bu sürüm daha küçük bir sayı tablosu gerektirir (v> = n ve 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;

Bakınız Örnek .


2

Yineleyicileri kullanan temel bir işlev.

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.