標準SQLまたはT-SQLで1、2、3、3、2、1、1、2、3、3、2、1、…シリーズを生成する方法は?


11

二つの数を考えるnm、私は、フォームのシリーズを生成したいです

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でこの結果を2つの方法のいずれかで達成する方法を知っています。

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 ;

...または同じ目的で関数を使用し、随伴ループとネストされたループを使用します。

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 Serverで同等の方法を実行するにはどうすればよいですか?

回答:


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、つまり100万未満のサイズに妥当な制限があると仮定すると、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 2017年

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およびmは、n * 2 * mがオーバーフローしない任意の整数です。int4

どうやって?

サブクエリで:

  • 単純な昇順の数で、希望する行の総数(n * 2 * m)を生成します。名前を付けますn2m0からN-11からNではない)は、以下のモジュロ演算を簡略化します。

  • それ取る%のN * 2%モジュロ演算子である)のシリーズを取得するためにn個の数字を昇順、m個の回。名前を付けますn2

外側のクエリでは:

  • 下半分に1を追加します(n2 <n)。

  • n * 2-n2で下半分の上半分(n2> = n)ミラーの場合

  • 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が必要な場合。理論的には、ほとんどのDBMS(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つのインスタンスが必要なので、

  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;
    

    ここでは、2つの行を持つ初期値を2倍にするだけですが、必要な2番目の束は逆の順序なので、少し順序を紹介します。

  3. 注文を紹介する前に、これも事柄であることを確認してください。開始条件に2つの行があり、それぞれに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)を2回生成してから、2番目のセットの順序を変更することです。

    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

再帰的なcteを使用してSQL Serverでそれを行う方法。

1)シリーズの必要な数のメンバーを生成します(n = 3およびm = 4の場合、2 * n * mである24になります)

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

基本的なMath + - * /と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;

これは特定の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)
)
...

これにより、再帰的なCTEを使用せずに数値テーブル(1〜1000)が生成されます。サンプルを参照してください。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.