Wie erstelle ich eine 1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1, ... Serie in Standard-SQL oder T-SQL?

11

Bei zwei Zahlen nund mmöchte ich eine Reihe des Formulars generieren

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

und wiederhole es mmal.

Zum Beispiel möchte ich für n = 3und m = 4eine Folge der folgenden 24 Zahlen:

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

Ich weiß, wie dieses Ergebnis in PostgreSQL mit einer von zwei Methoden erzielt werden kann:

Verwenden Sie die folgende Abfrage, die die generate_seriesFunktion verwendet, und einige Tricks, um sicherzustellen, dass die Reihenfolge die richtige ist:

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 ;

... oder verwenden Sie eine Funktion für denselben Zweck mit angrenzenden und verschachtelten Schleifen:

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 ;

Wie könnte ich das Äquivalent entweder in Standard-SQL oder in Transact-SQL / SQL Server tun?

joanolo
quelle

Antworten:

4

In Postgres ist es einfach, die folgende generate_series()Funktion zu verwenden:

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 ;

In Standard-SQL - und unter der Annahme, dass die Größe der Parameter n, m, dh weniger als eine Million, angemessen begrenzt ist - können Sie eine NumbersTabelle verwenden:

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

Füllen Sie es mit der bevorzugten Methode Ihres DBMS:

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

und dann benutze es anstelle von 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 ;
ypercubeᵀᴹ
quelle
In der Praxis erwarte ich nicht, dass diese Zahlen größer als 100 sind. aber theoretisch könnten sie alles sein.
Joanolo
10

Postgres

Sie können es mit einer einzigen generate_series() und grundlegenden Mathematik arbeiten lassen (siehe mathematische Funktionen ).

Eingewickelt in eine einfache SQL-Funktion:

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;

Anruf:

SELECT * FROM generate_up_down_series(3, 4);

Erzeugt das gewünschte Ergebnis. n und m können eine beliebige Ganzzahl sein, bei der n * 2 * m nicht überläuft int4.

Wie?

In der Unterabfrage:

  • Generieren Sie die gewünschte Gesamtzahl der Zeilen ( n * 2 * m ) mit einer einfachen aufsteigenden Anzahl. Ich nenne es n2m. 0 bis N-1 (nicht 1 bis N ), um das folgende Modulo zu vereinfachen Operation .

  • Nehmen Sie % n * 2 ( %ist der Modulo-Operator), um eine Reihe von n aufsteigenden Zahlen m- mal zu erhalten. Ich nenne es n2.

In der äußeren Abfrage:

  • Addiere 1 zur unteren Hälfte ( n2 <n ).

  • Für die obere Hälfte ( n2> = n ) Spiegel der unteren Hälfte mit n * 2 - n2 .

  • Ich habe hinzugefügt ORDER BY, um die angeforderte Bestellung zu garantieren. Mit aktuellen Versionen oder Postgres funktioniert es auch ohne ORDER BYdie einfache Abfrage - aber nicht unbedingt bei komplexeren Abfragen! Dies ist ein Implementierungsdetail (und es wird sich nicht ändern), das jedoch vom SQL-Standard nicht garantiert wird.

Leider generate_series()ist Postgres spezifisch und nicht Standard-SQL, wie kommentiert wurde. Aber wir können dieselbe Logik wiederverwenden:

Standard SQL

Sie können die Seriennummern mit einem rekursiven CTE anstelle von generate_series()oder effizienter für die wiederholte Verwendung generieren und einmal eine Tabelle mit Serien-Ganzzahlen erstellen. Jeder kann lesen, niemand kann darauf schreiben!

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;

Dann wird das Obige SELECTnoch einfacher:

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;
Erwin Brandstetter
quelle
5

Wenn Sie einfaches SQL benötigen. Theoretisch sollte es auf den meisten DBMS funktionieren (getestet auf PostgreSQL und 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;

Erläuterung

  1. Generieren Sie die Serie 1..n

    Vorausgesetzt, dass n=3

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

    Es ist recht einfach und kann in fast allen Dokumenten über rekursive CTEs gefunden werden. Wir benötigen jedoch zwei Instanzen von jedem Wert

  2. Generieren Sie die Serien 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;

    Hier verdoppeln wir nur den Anfangswert, der zwei Zeilen hat, aber den zweiten Haufen, den wir brauchen, in umgekehrter Reihenfolge, also werden wir die Reihenfolge in Kürze einführen.

  3. Bevor wir die Reihenfolge einführen, beachten Sie, dass dies auch eine Sache ist. Wir können zwei Zeilen in der Startbedingung mit jeweils drei Spalten haben, unsere n<3ist immer noch eine einzelne Spaltenbedingung. Und wir steigern immer noch nur den Wert von 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. Ebenso können wir sie ein wenig verwechseln, beobachten, wie sich unsere Startbedingungen hier ändern : hier haben wir eine (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. Generieren Sie die Serien 1..n, n..1

    Der Trick hier besteht darin, die Serie (1..n) zweimal zu generieren und dann einfach die Reihenfolge im zweiten Satz zu ändern.

    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;

    Hier iist die Reihenfolge und zdie Nummer der Sequenz (oder die Hälfte der Sequenz, wenn Sie möchten). Für Sequenz 1 erhöhen wir also die Reihenfolge von 1 auf 3 und für Sequenz 2 verringern wir die Reihenfolge von 6 auf 4. Und schließlich

  6. Multiplizieren Sie die Reihe mit m

    (siehe die erste Abfrage in der Antwort)

Abelisto
quelle
3

Wenn Sie eine tragbare Lösung wünschen, müssen Sie erkennen, dass dies im Grunde ein mathematisches Problem ist.

Wenn @n als höchste Nummer der Sequenz und @x als Position der Nummer in dieser Sequenz angegeben wird (beginnend mit Null), würde die folgende Funktion in SQL Server funktionieren:

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

Sie können dies mit diesem CTE überprüfen:

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)

(Kurze Erklärung: Die Funktion verwendet MODULO (), um eine Folge sich wiederholender Zahlen zu erstellen, und ABS (), um daraus eine Zick-Zack-Welle zu machen. Die anderen Operationen transformieren diese Welle so, dass sie dem gewünschten Ergebnis entspricht.)

Funkeln
quelle
2

In PostgreSQL ist dies einfach,

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;
Evan Carroll
quelle
2

Dies funktioniert in MS-SQL und ich denke, kann für jede SQL-Variante geändert werden.

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
Jules
quelle
2

Eine Möglichkeit, dies in SQL Server mithilfe eines rekursiven cte zu tun.

1) Generieren Sie die erforderliche Anzahl von Mitgliedern in der Reihe (für n = 3 und m = 4 wäre es 24, was 2 * n * m ist)

2) Anschließend können Sie mithilfe der Logik in einem caseAusdruck die erforderlichen Reihen generieren.

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)

Wie von @AndriyM vorgeschlagen .. kann der caseAusdruck vereinfacht werden

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

vkp
quelle
2

Verwenden Sie nur grundlegende Mathematik + - * /und Modulo:

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;

Dies erfordert kein bestimmtes SGBD.

Mit numberseiner Zahlentabelle:

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

Dadurch wird eine Zahlentabelle (1-1000) ohne Verwendung eines rekursiven CTE generiert. Siehe Beispiel . 2 * n * m muss kleiner sein als die Anzahl der Zeilen in Zahlen.

Ausgabe mit n = 3 und m = 4:

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

Diese Version erfordert eine kleinere Zahlentabelle (v> = n und 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;

Siehe Beispiel .

Julien Vavasseur
quelle
2

Eine Grundfunktion mit Iteratoren.

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;
McNets
quelle
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
Paparazzo
quelle