T-SQL : 알려진 값의 배열을 통해 반복


90

내 시나리오는 다음과 같습니다.

특정 ID 집합에 대해 다른 저장 프로 시저를 호출해야하는 저장 프로 시저가 있다고 가정 해 보겠습니다. 이것을 할 방법이 있습니까?

즉이 작업을 수행 할 필요가 없습니다.

exec p_MyInnerProcedure 4
exec p_MyInnerProcedure 7
exec p_MyInnerProcedure 12
exec p_MyInnerProcedure 22
exec p_MyInnerProcedure 19

다음과 같이합니다.

*magic where I specify my list contains 4,7,12,22,19*

DECLARE my_cursor CURSOR FAST_FORWARD FOR
*magic select*

OPEN my_cursor 
FETCH NEXT FROM my_cursor INTO @MyId
WHILE @@FETCH_STATUS = 0
BEGIN

exec p_MyInnerProcedure @MyId

FETCH NEXT FROM my_cursor INTO @MyId
END

여기서 나의 주요 목표는 단순히 유지 보수성 (비즈니스 변화에 따라 ID를 쉽게 제거 / 추가 할 수 있음), 모든 ID를 한 줄에 나열 할 수 있다는 것입니다. 성능이 큰 문제가되지 않아야합니다.


관련, varchars와 같은 정수가 아닌 목록에서 반복해야하는 경우 커서가있는 솔루션 : iterate-through-a-list-of-strings-in-sql-server
Pac0

답변:


105
declare @ids table(idx int identity(1,1), id int)

insert into @ids (id)
    select 4 union
    select 7 union
    select 12 union
    select 22 union
    select 19

declare @i int
declare @cnt int

select @i = min(idx) - 1, @cnt = max(idx) from @ids

while @i < @cnt
begin
     select @i = @i + 1

     declare @id = select id from @ids where idx = @i

     exec p_MyInnerProcedure @id
end

나는 더 우아한 방법이 있기를 바랐지만 이것이 내가 얻을 수있는 한 가까이있을 것이라고 생각한다 : 여기서 select / unions를 사용하는 것과 예제의 커서를 사용하는 것 사이의 하이브리드를 사용하게되었다. 감사!
John

13
@ 존 : 당신이 2008을 사용하는 경우, 당신은,,,, (19) (22) (12) (7) INSERT @ids VALUES 같은 (4)를 할 수
피터 Radocchia

2
참고로, 이와 같은 메모리 테이블은 일반적으로 커서보다 빠릅니다 (5 개의 값에 대해 거의 차이가 없음).하지만 내가 좋아하는 가장 큰 이유는 응용 프로그램 코드에서 찾을 수있는 것과 유사한 구문을 찾을 수 있다는 것입니다. , 커서는 상대적으로 다른 것처럼 보입니다.
Adam Robinson

실제로 성능에 거의 영향을 미치지 않지만 정의 된 공간 내의 모든 숫자를 반복한다는 점을 지적하고 싶습니다. While exists (Select * From @Ids) ...를 사용한 아래의 솔루션은 논리적으로 더 소리가 나고 우아합니다.
Der U

41

이 시나리오에서 내가하는 일은 ID를 보유 할 테이블 변수를 만드는 것입니다.

  Declare @Ids Table (id integer primary Key not null)
  Insert @Ids(id) values (4),(7),(12),(22),(19)

-(또는 다른 테이블 값 함수를 호출하여이 테이블을 생성)

그런 다음이 테이블의 행을 기준으로 루프

  Declare @Id Integer
  While exists (Select * From @Ids)
    Begin
      Select @Id = Min(id) from @Ids
      exec p_MyInnerProcedure @Id 
      Delete from @Ids Where id = @Id
    End

또는...

  Declare @Id Integer = 0 -- assuming all Ids are > 0
  While exists (Select * From @Ids
                where id > @Id)
    Begin
      Select @Id = Min(id) 
      from @Ids Where id > @Id
      exec p_MyInnerProcedure @Id 
    End

위의 방법 중 하나는 커서보다 훨씬 빠릅니다 (일반 사용자 테이블에 대해 선언 됨). 테이블 반환 변수는 부적절하게 사용되면 (많은 행이있는 매우 넓은 테이블의 경우) 성능이 떨어지기 때문에 나쁜 rep이 있습니다. 그러나 인덱스가있는 키 값 또는 4 바이트 정수를 보유하기 위해서만 사용하는 경우 (이 경우와 같이) 매우 빠릅니다.


위의 접근 방식은 테이블 변수에 선언 된 커서와 같거나 느립니다. 확실히 빠르지는 않습니다. 그러나 일반 사용자 테이블에서 기본 옵션으로 선언 된 커서보다 빠릅니다.
Peter Radocchia

@Peter, ahhh, 맞습니다. 커서 사용이 테이블 변수가 아닌 일반 사용자 테이블을 의미한다고 잘못 가정합니다. 구분을 명확히하기 위해 편집했습니다
Charles Bretana

16

정적 커서 변수와 분할 함수를 사용하십시오 .

declare @comma_delimited_list varchar(4000)
set @comma_delimited_list = '4,7,12,22,19'

declare @cursor cursor
set @cursor = cursor static for 
  select convert(int, Value) as Id from dbo.Split(@comma_delimited_list) a

declare @id int
open @cursor
while 1=1 begin
  fetch next from @cursor into @id
  if @@fetch_status <> 0 break
  ....do something....
end
-- not strictly necessary w/ cursor variables since they will go out of scope like a normal var
close @cursor
deallocate @cursor

사용자 테이블에 대해 선언 될 때 기본 옵션이 많은 오버 헤드를 생성 할 수 있기 때문에 커서는 잘못된 rep을가집니다.

그러나이 경우 오버 헤드는 여기에있는 다른 어떤 방법보다 적습니다. STATIC은 SQL Server에 tempdb에서 결과를 구체화 한 다음이를 반복하도록 지시합니다. 이와 같은 작은 목록의 경우 최적의 솔루션입니다.


7

다음과 같이 시도 할 수 있습니다.

declare @list varchar(MAX), @i int
select @i=0, @list ='4,7,12,22,19,'

while( @i < LEN(@list))
begin
    declare @item varchar(MAX)
    SELECT  @item = SUBSTRING(@list,  @i,CHARINDEX(',',@list,@i)-@i)
    select @item

     --do your stuff here with @item 
     exec p_MyInnerProcedure @item 

    set @i = CHARINDEX(',',@list,@i)+1
    if(@i = 0) set @i = LEN(@list) 
end

6
나는 다음과 같이 목록 선언을 할 것입니다 : @list ='4,7,12,22,19' + ','-목록이 쉼표로 끝나야한다는 것이 완전히 분명합니다 (그것 없이는 작동하지 않습니다!).
AjV Jsy

5

나는 보통 다음 접근 방식을 사용합니다

DECLARE @calls TABLE (
    id INT IDENTITY(1,1)
    ,parameter INT
    )

INSERT INTO @calls
select parameter from some_table where some_condition -- here you populate your parameters

declare @i int
declare @n int
declare @myId int
select @i = min(id), @n = max(id) from @calls
while @i <= @n
begin
    select 
        @myId = parameter
    from 
        @calls
    where id = @i

        EXECUTE p_MyInnerProcedure @myId
    set @i = @i+1
end

2
CREATE TABLE #ListOfIDs (IDValue INT)

DECLARE @IDs VARCHAR(50), @ID VARCHAR(5)
SET @IDs = @OriginalListOfIDs + ','

WHILE LEN(@IDs) > 1
BEGIN
SET @ID = SUBSTRING(@IDs, 0, CHARINDEX(',', @IDs));
INSERT INTO #ListOfIDs (IDValue) VALUES(@ID);
SET @IDs = REPLACE(',' + @IDs, ',' + @ID + ',', '')
END

SELECT * 
FROM #ListOfIDs

0

절차 적 프로그래밍 언어 (여기서는 Python)를 사용하여 DB에 연결하고 거기에서 루프를 수행합니다. 이렇게하면 복잡한 루프도 수행 할 수 있습니다.

# make a connection to your db
import pyodbc
conn = pyodbc.connect('''
                        Driver={ODBC Driver 13 for SQL Server};
                        Server=serverName;
                        Database=DBname;
                        UID=userName;
                        PWD=password;
                      ''')
cursor = conn.cursor()

# run sql code
for id in [4, 7, 12, 22, 19]:
  cursor.execute('''
    exec p_MyInnerProcedure {}
  '''.format(id))
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.