SQL Server 如何使用 WHILE 查询从多个结果中输出一个表结果

来自这个答案:有没有办法在不使用游标的情况下遍历 TSQL 中的表变量?

我正在使用该方法

WHILE EXISTS(SELECT * FROM #Temp)

问题是它输出了多个表,如果可能的话,我想输出为一个表.

The problem is that it's outputting multiple tables, if possible I'd like to output as a single table.

Declare @Id int

WHILE EXISTS(SELECT * FROM #Temp)
Begin

    Select Top 1 @Id = Id From #Temp

    --Do some processing here

    Delete #Temp Where Id = @Id

End

所以现在它输出这个:

x  y
-- --
1  a

x  y
-- --
1  b

但我希望它输出这个:

x  y
-- --
1  a
2  b

我正在努力实现的目标,我在一个领域有这个目标:

What I'm trying to achieve, I have this in a field:

1234,1432,1235

1234,1432,1235

我有一个将字段拆分为记录的过程(它适用于 sql server 2000):

I have a process that splits the field into records(it works with sql server 2000):

DECLARE @String VARCHAR(100)
    SELECT @String = str FROM  field --with the 1234,1432,1235

    SELECT SUBSTRING(',' + @String + ',', Number + 1,
    CHARINDEX(',', ',' + @String + ',', Number + 1) - Number -1)AS str
    INTO #temp
    FROM master..spt_values
    WHERE Type = 'P'
    AND Number <= LEN(',' + @String + ',') - 1
    AND SUBSTRING(',' + @String + ',', Number, 1) = ','
    GO

所以现在,#temp 有:

So now, #temp has:

str
---
1234
1432
1235

所以我需要遍历每条记录来查询我需要的信息.

So I need to go through each record to query the information I need.

我希望它输出如下内容:

And I'd like it to output something like this:

str   name   age
---   ----   ---
1234  Bob    23
1432  Jay    41
1235  Tim    12

当前的 While 循环输出如下,我不想要:

The current While loop outputs it like this, which I don't want:

str   name   age
---   ----   ---
1234  Bob    23

str   name   age
---   ----   ---
1432  Jay    41

str   name   age
---   ----   ---
1235  Tim    12

最终工作结果:

SET NOCOUNT ON;

DECLARE @String VARCHAR(1000);
SELECT @String = Tnn FROM (SELECT 
 CO.USER_2 AS Tnn
FROM 
    [VMFG].[dbo].[CUSTOMER_ORDER] AS CO 
    LEFT JOIN DBO.Tnn_Header AS Tnn ON Tnn.TnnNumber = CO.USER_2 AND Tnn.StatusID = '5' WHERE CO.ID = 'ORDERID') AS Place --with the 1234,1432,1235

DECLARE @Id nvarchar(50),
        @Discount nvarchar(50), 
        @Spin nvarchar(50), 
        @Commission_Hmm nvarchar(50), 
        @Commission nvarchar(50), 
        @TnnID nvarchar(50);

DECLARE @Output TABLE (
TnnNumber nvarchar(50),
        Discount nvarchar(50), 
        Spin nvarchar(50), 
        Commission_Hmm nvarchar(50), 
        Commission nvarchar(50), 
        TnnID nvarchar(50));

DECLARE crs CURSOR STATIC LOCAL READ_ONLY FORWARD_ONLY
FOR  SELECT SUBSTRING(',' + @String + ',', Number + 1,
     CHARINDEX(',', ',' + @String + ',', Number + 1) - Number -1) AS [ID]
     FROM master..spt_values
     WHERE Type = 'P'
     AND Number <= LEN(',' + @String + ',') - 1
     AND SUBSTRING(',' + @String + ',', Number, 1) = ',';

OPEN crs;

FETCH NEXT
FROM  crs
INTO  @Id;

WHILE (@@FETCH_STATUS = 0)
BEGIN
    -- do some processing..
SELECT 
@Id = TH.TnnNumber,
@Discount = CASE WHEN COUNT(DISTINCT TL.DiscountCodeID) > 1 THEN 'Varies, View Tnn' ELSE CAST(MAX(DC.Value) AS VARCHAR(60)) END,
@Spin = CASE WHEN TS.SpinID > 4 THEN 'Has Specifics, View Tnn' ELSE TS.Value END,
@Commission_Hmm = CASE WHEN COUNT(DISTINCT TL.Commission_Hmm) > 1 THEN 'Varies, View Tnn' ELSE CAST(MAX( ISNULL(str(TL.Commission_Hmm,12),'Default Comm')) AS VARCHAR(60)) END,
@Commission = CASE WHEN COUNT(DISTINCT TL.Commission) > 1 THEN 'Varies, View Tnn' ELSE CAST(MAX(ISNULL(str(TL.Commission,12),'Default Comm')) AS VARCHAR(60)) END,
@TnnID = TL.TnnID 

FROM DBO.Tnn_Header AS TH
LEFT JOIN DBO.Tnn_LINE AS TL ON TH.TnnID = TL.TnnID
LEFT JOIN DBO.Tnn_Spin AS TS ON TH.SpinID = TS.SpinID
LEFT JOIN DBO.Tnn_DiscountCode AS DC ON TL.DiscountCodeID = DC.DiscountCodeID 

WHERE TnnNumber = @id

GROUP BY 
TH.TnnNumber,
TS.SpinID,
TS.Value,
TL.TnnID
-- end do some processing..
    INSERT INTO @Output (TnnNumber, Discount, Spin, Commission_Hmm, Commission, TnnID)
    VALUES (@Id, @Discount, @Spin, @Commission_Hmm, @Commission, @TnnID);

    FETCH NEXT
    FROM  crs
    INTO  @Id;
END;

CLOSE crs;
DEALLOCATE crs;

SELECT TnnNumber, Discount, Spin, Commission_Hmm, Commission, TnnID
FROM   @Output;

推荐答案

遵循这些糟糕的建议是在浪费时间和精力.如果您绝对必须(特别强调必须)采用逐行方法(CURSOR 或 WHILE 循环),那么最好使用 CURSOR.它是一种更高效且不易出错的内置结构.您只需要使用正确的选项,例如使其成为 STATICLOCALREAD_ONLYFORWARD_ONLY.如果游标查询仅命中临时表和/或表变量,则不需要 STATIC.

You are wasting your time and energy following such bad advice. If you absolutely must (extra emphasis on the must) take a row-by-row approach (CURSOR or WHILE loop), then you are better off with a CURSOR. It is a built-in construct that is more efficient, and less error-prone. You just need to use the right options, such as making it STATIC, LOCAL, READ_ONLY, and FORWARD_ONLY. You don't need STATIC if the cursor query is only hitting temporary tables and/or table variables.

人们会对此争论不休,并说您必须不惜一切代价避免使用游标!",但他们还没有进行测试以证明这种流行的概念实际上只是一个神话.如果他们已经进行了似乎证实了这一点的测试,那么他们还没有设置适当的选项,主要是 STATIC,它将游标查询的结果转储到临时表中.如果没有这个选项,获取新行将重新检查基表以确保它们仍然存在,并且那就是性能下降的地方(I/O 加上锁定).这也是为什么在仅查询临时表和/或表变量时通常不需要 STATIC 选项的原因.我所说的重新检查"是什么意思?只需查看 @@FETCH_STATUS 的文档即可.返回值不仅仅包括成功"(0)和没有更多行"(-1):还有一个返回值,(-2),意思是获取的行丢失".

People will argue with this and say that "you must avoid cursors at all cost!", but they haven't done the tests to see that such a popular notion is really just a myth. And if they have done tests that appear to confirm it, then they haven't set the appropriate options, mostly STATIC, which dumps the result of the cursor query into a temp table. Without this option, fetching new rows will re-check the base tables to make sure that they still exist, and that is where the performance hit is (the I/O plus the locking). And that is also why you typically don't need the STATIC option when querying only temporary tables and/or table variables. What do I mean by "re-checking"? Just look at the documentation for @@FETCH_STATUS. The return values don't just cover "success" (0) and "no more rows" (-1): there is a return value, (-2), that means "The row fetched is missing".

SET NOCOUNT ON;
DECLARE @Id INT,
        @Name sysname,
        @Type VARCHAR(5);

--  the Table Variable replaces #Temp2 in the original query
DECLARE @Output TABLE (Id INT NOT NULL, Name sysname, [Type] VARCHAR(5));

-- the CURSOR replaces #Temp in the original query
DECLARE crs CURSOR STATIC LOCAL READ_ONLY FORWARD_ONLY
FOR  SELECT [object_id], name, [type]
     FROM   sys.objects -- dbo.sysobjects for SQL 2000 -- ATable in the original query
    ORDER BY [object_id] ASC;

OPEN crs;

FETCH NEXT
FROM  crs
INTO  @Id, @Name, @Type;

WHILE (@@FETCH_STATUS = 0)
BEGIN
    INSERT INTO @Output (Id, Name, [Type])
    VALUES (@Id, @Name, @Type);

    -- do some processing..

    FETCH NEXT -- replaces the DELETE and re-SELECT in the original query
    FROM  crs
    INTO  @Id, @Name, @Type;
END;

CLOSE crs;
DEALLOCATE crs;

SELECT Id, Name, [Type]
FROM   @Output;

更新

假设迭代是在一个拆分 INT 的 CSV 的查询上完成的,结果查询将类似于以下内容:

Given the iteration is being done over a query that splits a CSV of INTs, the resulting query would look similar to the following:

SET NOCOUNT ON;

DECLARE @String VARCHAR(1000);
SELECT @String = str FROM [Table]; --with the 1234,1432,1235

DECLARE @Id INT,
        @Name NVARCHAR(50),
        @Age  TINYINT;

DECLARE @Output TABLE (Id INT NOT NULL, Name NVARCHAR(50), Age TINYINT);

DECLARE crs CURSOR STATIC LOCAL READ_ONLY FORWARD_ONLY
FOR  SELECT SUBSTRING(',' + @String + ',', Number + 1,
     CHARINDEX(',', ',' + @String + ',', Number + 1) - Number -1) AS [ID]
     FROM master..spt_values
     WHERE Type = 'P'
     AND Number <= LEN(',' + @String + ',') - 1
     AND SUBSTRING(',' + @String + ',', Number, 1) = ',';

OPEN crs;

FETCH NEXT
FROM  crs
INTO  @Id;

WHILE (@@FETCH_STATUS = 0)
BEGIN
    -- do some processing..
    -- Logic to set value of @Name
    -- Logic to set value of @Age

    INSERT INTO @Output (Id, Name, Age)
    VALUES (@Id, @Name, @Age);

    FETCH NEXT
    FROM  crs
    INTO  @Id;
END;

CLOSE crs;
DEALLOCATE crs;

SELECT Id, Name, Age
FROM   @Output;

相关文章