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

Posted

技术标签:

【中文标题】SQL Server 如何使用 WHILE 查询从多个结果中输出一个表结果【英文标题】:SQL Server How to output one table result from multiple results with a WHILE query 【发布时间】:2016-05-29 23:29:20 【问题描述】:

来自这个答案:Is there a way to loop through a table variable in TSQL without using a cursor?

我正在使用方法

WHILE EXISTS(SELECT * FROM #Temp)

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

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

我想要实现的目标,我在一个领域有这个:

1234,1432,1235

我有一个将字段拆分为记录的过程(它适用于 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 有:

str
---
1234
1432
1235

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

我希望它输出如下内容:

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

当前的 While 循环像这样输出它,这是我想要的:

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;

【问题讨论】:

你的输出语句在哪里? 尝试在您的选择语句中的唯一字段上添加 order by @inquisitive_mind 没有 INTO #temp2 它的输出就像“所以现在它放这个:”的正下方一样: 您必须将修改后的列数据插入另一个临时表,然后在 WHILE 之外打印出来。 【参考方案1】:

您的查询有语法错误,但我尝试了下面的查询并且工作正常

-- this is only to populate my data table
Select object_id Id, name Into #Temp From sys.tables

select * into #temp2 from #Temp where 1=2

Declare @Id int

WHILE EXISTS(SELECT * FROM #Temp)
Begin
    Select Top 1 @Id = Id
    From #Temp
    ORDER BY Id -- this order is important

    -- use insert...into, NOT select...into
    insert into #temp2 
    select * 
    from #Temp
    where Id = @Id

    Delete #Temp Where Id = @Id
End

顺便说一句,您不能在循环中使用 SELECT...INTO,因为第二次迭代会引发错误。 您需要在循环外创建#temp2 并使用 INSERT...INTO 而不是 SELECT...INTO

【讨论】:

WHILE 之外的 #temp2 中缺少 select *? @inquisitive_mind,不,那部分只是为了填充我的示例数据表。我认为缺少的部分是 ORDER BY。我添加了一些cmets,请看一下 如果这是解决方案,请标记为答案。谢谢 只是一个想法。 @FLICKER 你也可以只做一个空选择来创建#temp2 表,即SELECT * FROM #Temp INTO #temp2 WHERE 1 = 2 这样你就不必在创建#temp2 表之前知道#Temp 的架构 @Kidiskidvogingogin,对,这是一种从现有表创建临时表的方法。我编辑我的帖子【参考方案2】:

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

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

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 的查询上完成的,生成的查询将类似于以下内容:

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;

【讨论】:

谢谢你的解释,我也试试这个,希望它能在sql server 2000上工作 临时表应该放在哪里? @sojim2 我不明白为什么它不能在 SQL Server 2000 中工作。我已经有一段时间没有使用那个版本了,但我记得当时这样做了。关于临时表,你指的是#Temp吗?如果是这样,那么在这种方法中您根本不需要它。从技术上讲,由于STATIC 选项而创建的用于保存游标查询结果的系统创建/隐藏表相当于#Temp。如果有帮助,我会更新我的答案中的示例代码,以注意每个部分与原始查询的匹配位置。我只是想提供一个工作示例。 但是#temp 有我需要处理的数据吗?还是我不明白什么?我只是用处理代码(没有我需要处理的数据的#temp表)尝试了代码,它给出了一些错误,例如“无效的列名'object_id'也sys.objects可能不适用于sql server 2000跨度> @sojim2 我刚刚更新了 SQL Server 2000 的正确系统表(即dbo.sysobjects)。关于#Temp,您首先从哪里获得用于填充#Temp 的数据?你为什么首先创建#Temp?如果要完成其他帖子的建议,那是浪费的一部分。无论用于填充 #Temp 的查询都是在 CURSOR 定义中使用的查询(在 FOR 之后)。

以上是关于SQL Server 如何使用 WHILE 查询从多个结果中输出一个表结果的主要内容,如果未能解决你的问题,请参考以下文章

带有相关子查询的 While 循环的 SQL Server 性能调整

sqlserver怎么循环

如何从 PowerShell 运行 SQL Server 查询?

sql server中do while循环怎么写

使用 SQL-Server 如何从 TFS 查询所有活动/活动项目

如何在 SQL Server 中组织无限 while 循环?