web-dev-qa-db-ja.com

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

2つの数値nmが与えられた場合、一連のフォームを生成したい

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

そして、それをm回繰り返します。

たとえば、n = 3m = 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で同等の方法を実行するにはどうすればよいですか?

11
joanolo

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 ;
_
4
ypercubeᵀᴹ

Postgres

singlegenerate_series() と基本的な数学( mathematical functions を参照)で動作させることができます。

単純な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およびmanyintegerにすることができますn * 2 * mは_int4_をオーバーフローしません。

どうやって?

サブクエリ内:

  • 単純な昇順の数で、必要な行の総数(n * 2 * m)を生成します。 _n2m_という名前を付けます。 からN-1(not1から[〜#〜] n [〜#〜])は、次のmodulo演算を簡略化します。

  • それを取る%n * 2(_%_はモジュロ演算子です)一連のn昇順の数値を取得するには、 m回。 _n2_という名前を付けます。

外部クエリでは:

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

  • 上半分の場合(n2> = n)n * 2-n2

  • リクエストされた注文を保証するために_ORDER BY_を追加しました。現在のバージョンまたはPostgresでは、単純なクエリに対して_ORDER BY_なしでも機能しますが、必ずしもより複雑なクエリでは機能しません。これは実装の詳細です(変更されることはありません)が、SQL標準では保証されていません。

残念ながら、コメントされているように、generate_series()はPostgres固有であり、標準SQLではありません。しかし、同じロジックを再利用できます。

標準SQL

generate_series()の代わりに再帰CTEを使用してシリアル番号を生成できます。または、より効率的に繰り返し使用するには、シリアル整数値を持つテーブルを1回作成します。 誰でも読み取り、書き込みはできません!

_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;
_
10

プレーン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. Generate series 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にシリーズを乗算します

    (回答の最初のクエリを参照)

5
Abelisto

ポータブルなソリューションが必要な場合は、これが基本的に数学の問題であることを理解する必要があります。

@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()を使用してジグザグ波に変換します。他の操作は、その波を変換して目的の結果に一致させます。)

3
Twinkles

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
Evan Carroll

イテレータを使用した基本的な機能。

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;
2
McNets

これは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
Jules

再帰的な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
vkp

基本的な数学のみを使用+ - * /および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)が生成されます。 Sample を参照してください。 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;

Sample を参照してください。

2
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
1
paparazzo