두 개의 숫자가 주어 n
지고 m
일련의 양식을 생성하고 싶습니다.
1, 2, ..., (n-1), n, n, (n-1), ... 2, 1
반복합니다 m
.
예를 들어 for 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에서 두 가지 방법 중 하나를 사용 하여이 결과를 얻는 방법을 알고 있습니다.
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의 크기, 즉 백만 미만의 크기에 대한 합리적인 제한이 있다고 가정하면 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 ;
답변
포스트그레스
단일 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은 될 수 있는 정수 * 2 * N m은 오버 플로우하지 않는다 int4
.
어떻게?
하위 쿼리에서 :
-
간단한 오름차순으로 원하는 총 행 수 ( n * 2 * m )를 생성하십시오 . 나는 그것을 명명한다
n2m
. 다음 모듈로 연산 을 단순화하려면 0 ~ N-1 ( 1 ~ N 아님) -
그것을 받아 % N * 2 (
%
일련 얻을 모듈러 연산자이다) N의 오름차순 번호 m의 시간. 나는 그것을 명명한다n2
.
외부 쿼리에서 :
-
하반신에 1을 더합니다 ( n2 <n ).
-
n * 2-n2 의 아래쪽 절반 의 위쪽 절반 ( n2> = n ) 미러의 경우 .
-
ORDER BY
요청한 주문을 보장하기 위해 추가 했습니다. 현재 버전이나 PostgresORDER BY
에서는 간단한 쿼리 없이도 작동 하지만 더 복잡한 쿼리 에서는 작동하지 않습니다! 그것은 구현 세부 사항이며 변경되지는 않지만 SQL 표준에서는 보증하지 않습니다.
불행히도, generate_series()
주석 처리 된 것처럼 Postgres는 표준 SQL이 아니며 특정 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;
답변
일반 SQL이 필요한 경우 이론적 으로 PostgreSQL 및 SQLite에서 테스트 된 대부분의 DBMS 에서 작동해야합니다 .
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..n 생성
그것을 가정
n=3
with recursive s(n) as ( select 1 union all select n+1 from s where n<3 ) select * from s;
매우 간단하며 재귀 CTE에 대한 거의 모든 문서에서 찾을 수 있습니다. 그러나 각 값의 두 인스턴스가 필요합니다.
-
시리즈 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;
여기서 우리는 두 개의 행을 가진 초기 값을 두 배로 늘리지 만 두 번째 행은 역순으로 필요하므로 순서를 조금 소개하겠습니다.
-
우리가 명령을 소개하기 전에 이것 또한 문제라는 것을 관찰하십시오. 시작 조건에서 각각 3 개의 열을 갖는 2 개의 행을 가질 수 있으며,
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)를 두 번 생성 한 다음 두 번째 세트의 순서를 변경하는 것입니다.
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를 해당 시퀀스의 숫자 위치로 지정하면 (0으로 시작) 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;
답변
이것은 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의 경우 24 * 2 * n * m)
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)