Làm thế nào để viết một foreach trong SQL Server?


192

Tôi đang cố gắng để đạt được một cái gì đó dọc theo từng dòng, nơi tôi muốn lấy Id của một câu lệnh chọn được trả về và sử dụng từng câu lệnh.

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

Hiện tại tôi có một cái gì đó trông giống như ở trên, nhưng đang gặp lỗi:

Tên cột không hợp lệ 'idx'.

Ai đó có thể


2
Cách lặp lại thông qua một kết quả được đặt bằng cách sử dụng Transact-SQL trong SQL Server: support.microsoft.com/kb/111401/nl
Anonymoose

idxlà trong @Practitionerkhông Practitioner. Có nhiều lựa chọn thay thế dựa trên tập hợp ưu việt hơn cho cách tiếp cận cho mỗi phương pháp, nếu bạn chỉ ra những gì bạn làm với giá trị hàng có lẽ có thể đề xuất một phương án thay thế.
Alex K.

1
Vui lòng gửi thêm về những gì bạn đang cố gắng thực hiện. Tránh RBAR như bệnh dịch hạch (99% thời gian). Simple-talk.com/sql/t-sql-programming/ từ
granadaCoder

1
RBAR Xấu, Tốt dựa trên thiết lập.
granadaCoder

Nếu bạn cho chúng tôi biết đó --Do something with Id herelà gì , có khả năng chúng tôi có thể chỉ cho bạn cách giải quyết vấn đề này mà không có bất kỳ vòng lặp hoặc con trỏ nào. Trong hầu hết các trường hợp, bạn muốn sử dụng giải pháp dựa trên tập hợp, vì đó là cách SQL Server được tối ưu hóa để hoạt động. Vòng lặp và xử lý một hàng tại một thời điểm chắc chắn có vị trí của nó, nhưng tôi nghi ngờ đây không phải là nó.
Aaron Bertrand

Câu trả lời:


342

Bạn dường như muốn sử dụng a CURSOR. Mặc dù hầu hết các lần tốt nhất là sử dụng giải pháp dựa trên tập hợp, có một số lần, đó CURSORlà giải pháp tốt nhất. Không biết thêm về vấn đề thực sự của bạn, chúng tôi không thể giúp bạn nhiều hơn thế:

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
Xin vui lòng không bắt đầu sử dụng con trỏ trái và phải. Họ cần <1% thời gian. Các giải pháp RBAR (hàng bằng cách kích hoạt hàng) thường là những người thực hiện kém và gây đau đầu. Nếu bạn là người mới, xin hãy cố gắng học bài học này sớm.
granadaCoder

135

Giả sử rằng cột PracticeitionerId là duy nhất, thì bạn có thể sử dụng vòng lặp sau

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
Quá đơn giản để trở thành sự thật. Bạn đang chọn MIN (PracticeitionerId) luôn trong vòng lặp. Điều kiện để thoát khỏi vòng lặp là gì? Trông giống như một vòng lặp vô hạn với tôi.
bluelabel

7
@bluelabel để thoát khỏi tập lệnh vòng lặp có điều kiện sau NẾU Học viên là NULL BREAK
Aleksandr Fedorenko

16

Số lượng chọn của bạn và chọn tối đa phải từ biến bảng của bạn thay vì bảng thực tế

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

Điều này thường (hầu như luôn luôn) hoạt động tốt hơn con trỏ và đơn giản hơn:

    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

Tôi có thể nói mọi thứ có thể hoạt động ngoại trừ cột idxkhông thực sự tồn tại trong bảng bạn đang chọn. Có lẽ bạn muốn chọn từ @Practitioner:

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

bởi vì điều đó được định nghĩa trong đoạn mã trên như thế:

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

2

Dòng sau sai trong phiên bản của bạn:

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

(Thiếu @)

Có thể là một ý tưởng để thay đổi quy ước đặt tên của bạn để các bảng khác nhau hơn.


1

Mặc dù các con trỏ thường được coi là ác quỷ khủng khiếp, tôi tin rằng đây là trường hợp của con trỏ FAST_FORWARD - điều gần nhất mà bạn có thể nhận được với FOREACH trong TSQL.


1

Tôi đã đưa ra một cách rất hiệu quả, (tôi nghĩ) có thể đọc được để làm điều này.

    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.

Đây là mã. Xin lỗi, nó sử dụng tên biến của tôi thay vì tên trong câu hỏi.

            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

Đây là một trong những giải pháp tốt hơn.

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

Ở đây tôi đã thêm một số giá trị trong bảng beacuse, ban đầu nó trống.

Chúng ta có thể truy cập hoặc chúng ta có thể làm bất cứ điều gì trong phần thân của vòng lặp và chúng ta có thể truy cập idx bằng cách định nghĩa nó trong định nghĩa bảng.

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

                --Do something with Id here

                PRINT @curren_val
                SET @i = @i + 1
            END

1

Tôi đã thực hiện một thủ tục thực hiện a FOREACHvới CURSORbất kỳ bảng nào.

Ví dụ sử dụng:

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

Kết quả là 2 lựa chọn cho mỗi hàng. Cú pháp UPDATEvà phá vỡ FOREACHđược viết trong các gợi ý.

Đây là mã 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.