SQL Server에서 foreach를 작성하는 방법은 무엇입니까?


194

나는 for-each 줄을 따라 무언가를 달성하려고 노력하고 있는데, 여기서 리턴 된 select 문의 ID를 가져 와서 각각을 사용하고 싶습니다.

DECLARE @i int
DECLARE @PractitionerId int
DECLARE @numrows int
DECLARE @Practitioner TABLE (
    idx smallint Primary Key IDENTITY(1,1)
    , PractitionerId int
)

INSERT @Practitioner
SELECT distinct PractitionerId FROM Practitioner

SET @i = 1
SET @numrows = (SELECT COUNT(*) FROM Practitioner)
IF @numrows > 0
    WHILE (@i <= (SELECT MAX(idx) FROM Practitioner))
    BEGIN

        SET @PractitionerId = (SELECT PractitionerId FROM @Practitioner WHERE idx = @i)

        --Do something with Id here
        PRINT @PractitionerId

        SET @i = @i + 1
    END

현재 위와 같은 것이 있지만 오류가 발생합니다.

'idx'열 이름이 잘못되었습니다.

누군가가


2
SQL Server에서 Transact-SQL을 사용하여 결과 집합을 반복하는 방법 : support.microsoft.com/kb/111401/nl
Anonymoose

idx@Practitioner없습니다 Practitioner. 행 값으로 수행하는 작업을 보여 주면 대안을 제안 할 수있는 경우 for-each 접근법에 대해 대체로 우수한 세트 기반 대안이 있습니다.
Alex K.

1
달성하고자하는 것에 대해 더 많은 것을 게시하십시오. 전염병과 같은 RBAR을 피하십시오 (시간의 99 %). simple-talk.com/sql/t-sql-programming/…
granadaCoder

1
RBAR 세트 기반 양호.
granadaCoder

무엇이 무엇인지 알려 주면 --Do something with Id here루프 나 커서 없이이 문제를 해결하는 방법을 보여줄 수 있습니다. 대부분의 경우 SQL Server가 작동하도록 최적화 된 방식이므로 집합 기반 솔루션을 사용하려고합니다. 한 번에 한 줄씩 반복하고 처리하는 것이 확실히 그 자리에 있지만, 그렇지 않은 것 같습니다.
Aaron Bertrand

답변:


343

을 사용하려는 것 같습니다 CURSOR. 대부분의 경우 세트 기반 솔루션을 사용하는 CURSOR것이 가장 좋지만 a 가 최고의 솔루션 인 경우가 있습니다. 실제 문제에 대해 더 많이 알지 못하면 그 이상을 도울 수 없습니다.

DECLARE @PractitionerId int

DECLARE MY_CURSOR CURSOR 
  LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR 
SELECT DISTINCT PractitionerId 
FROM Practitioner

OPEN MY_CURSOR
FETCH NEXT FROM MY_CURSOR INTO @PractitionerId
WHILE @@FETCH_STATUS = 0
BEGIN 
    --Do something with Id here
    PRINT @PractitionerId
    FETCH NEXT FROM MY_CURSOR INTO @PractitionerId
END
CLOSE MY_CURSOR
DEALLOCATE MY_CURSOR

41
왼쪽과 오른쪽으로 커서를 사용하지 마십시오. 시간의 <1 %가 필요합니다. RBAR (행으로 인한 행) 솔루션은 일반적으로 성능이 좋지 않으며 두통을 유발합니다. 당신이 새로운 경우에,이 수업을 일찍 배우십시오.
granadaCoder

136

PractitionerId 열이 고유하다고 가정하면 다음 루프를 사용할 수 있습니다

DECLARE @PractitionerId int = 0
WHILE(1 = 1)
BEGIN
  SELECT @PractitionerId = MIN(PractitionerId)
  FROM dbo.Practitioner WHERE PractitionerId > @PractitionerId
  IF @PractitionerId IS NULL BREAK
  SELECT @PractitionerId
END

1
사실 너무 간단합니다. 루프 내에서 항상 MIN (PractitionerId)을 선택하고 있습니다. 루프를 종료하는 조건은 무엇입니까? 나에게 무한 루프처럼 보인다.
bluelabel

7
출구 루프 스크립트 @bluelabel는 다음 조건을 가지고 PractitionerId IS NULL BREAK IF
알렉산드르 Fedorenko

16

선택 수와 선택 최대 값은 실제 테이블 대신 테이블 변수에서 가져와야합니다.

DECLARE @i int
DECLARE @PractitionerId int
DECLARE @numrows int
DECLARE @Practitioner TABLE (
    idx smallint Primary Key IDENTITY(1,1)
    , PractitionerId int
)

INSERT @Practitioner
SELECT distinct PractitionerId FROM Practitioner

SET @i = 1
SET @numrows = (SELECT COUNT(*) FROM @Practitioner)
IF @numrows > 0
    WHILE (@i <= (SELECT MAX(idx) FROM @Practitioner))
    BEGIN

        SET @PractitionerId = (SELECT PractitionerId FROM @Practitioner WHERE idx = @i)

        --Do something with Id here
        PRINT @PractitionerId

        SET @i = @i + 1
    END

15

이것은 일반적으로 (거의 항상) 커서보다 성능이 좋으며 더 간단합니다.

    DECLARE @PractitionerList TABLE(PracticionerID INT)
    DECLARE @PractitionerID INT

    INSERT @PractitionerList(PracticionerID)
    SELECT PracticionerID
    FROM Practitioner

    WHILE(1 = 1)
    BEGIN

        SET @PracticionerID = NULL
        SELECT TOP(1) @PracticionerID = PracticionerID
        FROM @PractitionerList

        IF @PracticionerID IS NULL
            BREAK

        PRINT 'DO STUFF'

        DELETE TOP(1) FROM @PractitionerList

    END

5

idx선택한 테이블에 열 이 실제로 존재하지 않는다는 점을 제외하고는 모든 것이 작동한다고 말할 수 있습니다. 아마 당신은에서 선택하려고했다 @Practitioner:

WHILE (@i <= (SELECT MAX(idx) FROM @Practitioner))

위의 코드에서 다음과 같이 정의 되었기 때문입니다.

DECLARE @Practitioner TABLE (
    idx smallint Primary Key IDENTITY(1,1)
    , PractitionerId int
)

3

버전에서 다음 줄이 잘못되었습니다.

WHILE (@i <= (SELECT MAX(idx) FROM @Practitioner))

(@가 누락 됨)

테이블이 더 다르도록 명명 규칙을 변경하는 것이 좋습니다.


2

커서는 보통 끔찍한 악으로 여겨지지만 이것이 FAST_FORWARD 커서의 경우라고 생각합니다.


2

모든 테이블에 대해 FOREACHwith CURSOR를 실행하는 절차를 만들었습니다 .

사용 예 :

CREATE TABLE #A (I INT, J INT)
INSERT INTO #A VALUES (1, 2), (2, 3)
EXEC PRC_FOREACH
    #A --Table we want to do the FOREACH
    , 'SELECT @I, @J' --The execute command, each column becomes a variable in the same type, so DON'T USE SPACES IN NAMES
   --The third variable is the database, it's optional because a table in TEMPB or the DB of the proc will be discovered in code

결과는 각 행에 대해 2 개의 선택입니다. 의 구문 UPDATE과 구분 FOREACH은 힌트로 작성됩니다.

이것은 proc 코드입니다.

CREATE PROC [dbo].[PRC_FOREACH] (@TBL VARCHAR(100) = NULL, @EXECUTE NVARCHAR(MAX)=NULL, @DB VARCHAR(100) = NULL) AS BEGIN

    --LOOP BETWEEN EACH TABLE LINE            

IF @TBL + @EXECUTE IS NULL BEGIN
    PRINT '@TBL: A TABLE TO MAKE OUT EACH LINE'
    PRINT '@EXECUTE: COMMAND TO BE PERFORMED ON EACH FOREACH TRANSACTION'
    PRINT '@DB: BANK WHERE THIS TABLE IS (IF NOT INFORMED IT WILL BE DB_NAME () OR TEMPDB)' + CHAR(13)
    PRINT 'ROW COLUMNS WILL VARIABLE WITH THE SAME NAME (COL_A = @COL_A)'
    PRINT 'THEREFORE THE COLUMNS CANT CONTAIN SPACES!' + CHAR(13)
    PRINT 'SYNTAX UPDATE:

UPDATE TABLE
SET COL = NEW_VALUE
WHERE CURRENT OF MY_CURSOR

CLOSE CURSOR (BEFORE ALL LINES):

IF 1 = 1 GOTO FIM_CURSOR'
    RETURN
END
SET @DB = ISNULL(@DB, CASE WHEN LEFT(@TBL, 1) = '#' THEN 'TEMPDB' ELSE DB_NAME() END)

    --Identifies the columns for the variables (DECLARE and INTO (Next cursor line))

DECLARE @Q NVARCHAR(MAX)
SET @Q = '
WITH X AS (
    SELECT
        A = '', @'' + NAME
        , B = '' '' + type_name(system_type_id)
        , C = CASE
            WHEN type_name(system_type_id) IN (''VARCHAR'', ''CHAR'', ''NCHAR'', ''NVARCHAR'') THEN ''('' + REPLACE(CONVERT(VARCHAR(10), max_length), ''-1'', ''MAX'') + '')''
            WHEN type_name(system_type_id) IN (''DECIMAL'', ''NUMERIC'') THEN ''('' + CONVERT(VARCHAR(10), precision) + '', '' + CONVERT(VARCHAR(10), scale) + '')''
            ELSE ''''
        END
    FROM [' + @DB + '].SYS.COLUMNS C WITH(NOLOCK)
    WHERE OBJECT_ID = OBJECT_ID(''[' + @DB + '].DBO.[' + @TBL + ']'')
    )
SELECT
    @DECLARE = STUFF((SELECT A + B + C FROM X FOR XML PATH('''')), 1, 1, '''')
    , @INTO = ''--Read the next line
FETCH NEXT FROM MY_CURSOR INTO '' + STUFF((SELECT A + '''' FROM X FOR XML PATH('''')), 1, 1, '''')'

DECLARE @DECLARE NVARCHAR(MAX), @INTO NVARCHAR(MAX)
EXEC SP_EXECUTESQL @Q, N'@DECLARE NVARCHAR(MAX) OUTPUT, @INTO NVARCHAR(MAX) OUTPUT', @DECLARE OUTPUT, @INTO OUTPUT

    --PREPARE TO QUERY

SELECT
    @Q = '
DECLARE ' + @DECLARE + '
-- Cursor to scroll through object names
DECLARE MY_CURSOR CURSOR FOR
    SELECT *
    FROM [' + @DB + '].DBO.[' + @TBL + ']

-- Opening Cursor for Reading
OPEN MY_CURSOR
' + @INTO + '

-- Traversing Cursor Lines (While There)
WHILE @@FETCH_STATUS = 0
BEGIN
    ' + @EXECUTE + '
    -- Reading the next line
    ' + @INTO + '
END
FIM_CURSOR:
-- Closing Cursor for Reading
CLOSE MY_CURSOR

DEALLOCATE MY_CURSOR'

EXEC SP_EXECUTESQL @Q --MAGIA
END

1

나는 이것을 할 수있는 매우 효과적인 읽을 수있는 방법을 생각해 냈습니다.

    1. create a temp table and put the records you want to iterate in there
    2. use WHILE @@ROWCOUNT <> 0 to do the iterating
    3. to get one row at a time do, SELECT TOP 1 <fieldnames>
        b. save the unique ID for that row in a variable
    4. Do Stuff, then delete the row from the temp table based on the ID saved at step 3b.

코드는 다음과 같습니다. 죄송합니다, 질문에있는 이름 대신 내 변수 이름을 사용합니다.

            declare @tempPFRunStops TABLE (ProformaRunStopsID int,ProformaRunMasterID int, CompanyLocationID int, StopSequence int );    

        INSERT @tempPFRunStops (ProformaRunStopsID,ProformaRunMasterID, CompanyLocationID, StopSequence) 
        SELECT ProformaRunStopsID, ProformaRunMasterID, CompanyLocationID, StopSequence from ProformaRunStops 
        WHERE ProformaRunMasterID IN ( SELECT ProformaRunMasterID FROM ProformaRunMaster WHERE ProformaId = 15 )

    -- SELECT * FROM @tempPFRunStops

    WHILE @@ROWCOUNT <> 0  -- << I dont know how this works
        BEGIN
            SELECT TOP 1 * FROM @tempPFRunStops
            -- I could have put the unique ID into a variable here
            SELECT 'Ha'  -- Do Stuff
            DELETE @tempPFRunStops WHERE ProformaRunStopsID = (SELECT TOP 1 ProformaRunStopsID FROM @tempPFRunStops)
        END

1

다음은 더 나은 솔루션 중 하나입니다.

DECLARE @i int
            DECLARE @curren_val int
            DECLARE @numrows int
            create table #Practitioner (idx int IDENTITY(1,1), PractitionerId int)
            INSERT INTO #Practitioner (PractitionerId) values (10),(20),(30)
            SET @i = 1
            SET @numrows = (SELECT COUNT(*) FROM #Practitioner)
            IF @numrows > 0
            WHILE (@i <= (SELECT MAX(idx) FROM #Practitioner))
            BEGIN

                SET @curren_val = (SELECT PractitionerId FROM #Practitioner WHERE idx = @i)

                --Do something with Id here
                PRINT @curren_val
                SET @i = @i + 1
            END

여기에 테이블에 일부 값을 추가했기 때문에 처음에는 비어 있습니다.

루프 본문에서 무엇이든 액세스하거나 할 수 있으며 테이블 정의 내에서 idx를 정의하여 idx에 액세스 할 수 있습니다.

              BEGIN
                SET @curren_val = (SELECT PractitionerId FROM #Practitioner WHERE idx = @i)

                --Do something with Id here

                PRINT @curren_val
                SET @i = @i + 1
            END
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.