표준 SQL 또는 T-SQL에서 1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1,… 시리즈를 생성하는 방법은 무엇입니까? 숫자 시퀀스를

두 개의 숫자가 주어 n지고 m일련의 양식을 생성하고 싶습니다.

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

반복합니다 m.

예를 들어 for 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에서 두 가지 방법 중 하나를 사용 하여이 결과를 얻는 방법을 알고 있습니다.

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

원하는 결과를 생성합니다. Nm은 될 수 있는 정수 * 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요청한 주문을 보장하기 위해 추가 했습니다. 현재 버전이나 Postgres ORDER 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. 시리즈 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;

    여기서 우리는 두 개의 행을 가진 초기 값을 두 배로 늘리지 만 두 번째 행은 역순으로 필요하므로 순서를 조금 소개하겠습니다.

  3. 우리가 명령을 소개하기 전에 이것 또한 문제라는 것을 관찰하십시오. 시작 조건에서 각각 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;
  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)를 두 번 생성 한 다음 두 번째 세트의 순서를 변경하는 것입니다.

    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

    (답변의 첫 번째 쿼리 참조)


답변

휴대용 솔루션을 원한다면 이것이 기본적으로 수학 문제 라는 것을 알아야합니다 .

@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식에서 논리를 사용한 후에 필요한 시리즈를 생성 할 수 있습니다.

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