2つの数値n
とm
が与えられた場合、一連のフォームを生成したい
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で同等の方法を実行するにはどうすればよいですか?
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 ;
_
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およびmはanyintegerにすることができます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ではありません。しかし、同じロジックを再利用できます。
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;
_
プレーン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;
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つのインスタンスが必要なので、
生成シリーズ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番目のバンチは逆の順序なので、少し順序を紹介します。
注文を紹介する前に、これも重要であることを確認してください。開始条件に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;
同様に、それらを少し混ぜて、開始条件の変化をここで確認できます:ここに(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;
シリーズ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に減らします。
m
にシリーズを乗算します
(回答の最初のクエリを参照)
ポータブルなソリューションが必要な場合は、これが基本的に数学の問題であることを理解する必要があります。
@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()を使用してジグザグ波に変換します。他の操作は、その波を変換して目的の結果に一致させます。)
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;
イテレータを使用した基本的な機能。
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;
これは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
再帰的なcteを使用してSQL Serverでそれを行う方法。
1)シリーズの必要な数のメンバーを生成します(n = 3およびm = 4の場合、2 * n * mである24になります)
2)その後、case
式でロジックを使用して、必要なシリーズを生成できます。
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)
基本的な数学のみを使用+ - * /
および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 を参照してください。
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