【问题标题】:SQL Server: Using conditional group by based on parameter and STUFF()SQL Server:根据参数和 STUFF() 使用条件组
【发布时间】:2017-11-01 21:07:54
【问题描述】:

我有一段代码想变成一个函数。代码的目的是根据可变标准将某些记录分组在一起,并使用 STUFF() 创建分组串联。我希望能够切换发生分组的参数(因此还有 STUFF 的参数)。

但是,如果可选参数(例如以下示例中的 OwnerName)在选择列表中无效,则以下错误会导致我出现错误,因为它们既不包含在聚合函数中,也不包含在 GROUP BY 子句中。

考虑一个像下面这样的简化示例(现实生活中的版本有很多参数,因此我希望能够将这些都集中到一个查询中):

SELECT CarMake, CarModel, CASE WHEN @FlagOwnerName = 1 THEN OwnerName ELSE NULL END AS [OwnerName], SUM(CarValue), 
LicenseIDs = STUFF((SELECT ',' + CONVERT(VARCHAR(20),Cars2.LicenseID) AS [text()] 
    FROM DB.dbo.Cars  Cars2
    WHERE Cars2.CarMake = Cars1.CarMake
        AND Cars2.CarModel = Cars1.CarModel
        AND (@FlagOwnerName = 0 OR Cars2.OwnerName = Cars1.OwnerName)
    FOR XML PATH('')), 1, 1, '')
FROM DB.dbo.Cars Cars1
GROUP BY CarMake,
    CarModel,
    CASE WHEN @FlagOwnerName = 1 THEN OwnerName ELSE NULL END

编辑:如果我更改以下内容,那么它“似乎”返回正确的串联,除非它是 NULL,那么串联本身就是 NULL。此外,如果我尝试将值更改为 ISNULL(Cars1.OwnerName, 'Placeholder') 或类似地使用 COALESCE,它会给我同样的错误(在上面的 select 语句中无效)。

    AND (@FlagOwnerName = 0 OR Cars2.OwnerName = Cars1.OwnerName)

    AND CASE WHEN @FlagOwnerName = 1 THEN Cars1.OwnerName = Cars2.OwnerName

【问题讨论】:

  • 您的意思是要使用 CREATE FUNCTION 语句创建一个实际的 SQL Server 函数吗?或者您只是想在您的 SELECT 语句中内联执行此操作?了解您使用的 SQL Server 版本也会有所帮助。
  • 抱歉不清楚,这将是重复调用的存储过程的一部分。该数据库位于 SQL Server 2008 上。
  • 我认为您可能需要更多地解释一下您正在尝试做什么 - 您期望“LicenseIDs”的数据是什么?在 XML 上使用 STUFF 函数的目的是什么?您是否希望 Cars1 到 Cars2 是一对多的关系?
  • Cars2 到 Cars1 应该是一对一的,并且 XML 路径上的 STUFF 应该连接每个品牌和型号的所有 LicenseID。一个示例输出行是,假设每个 Toyota Camry 的价值为 20,000。当 FlagOwnerName = 0 时:丰田 |凯美瑞 | 40,000 | LICENSE1,LICENSE2 但是当 FlagOwnerName = 1 然后是 Toyota |凯美瑞 |所有者1 | 20,000 | LICENSE1(以及 Owner2 的第二行,LICENSE2)。

标签: sql-server join group-by conditional


【解决方案1】:

根据您的 cmets,我认为将 STUFF 与 FOR XML 结合使用不是解决此问题的最佳方法。通常,将多行连接成单个字符串的最佳方法是使用递归公用表表达式 (CTE)。

有一些使用 CTE 的示例(和一些替代方法)here

我已经调整了其中一个 CTE 选项,以执行与您描述的类似的操作。

首先,我建立了一个类似于您描述的数据的简单表格:

create table #cars (CarMake varchar(50), CarModel varchar(50), CarValue INT, OwnerName varchar(50), LicenseID varchar(50));

insert into #cars(CarMake, CarModel, CarValue, OwnerName, LicenseID) values ('Toyota','Camry', 12000, 'Steve','ABC123');
insert into #cars(CarMake, CarModel, CarValue, OwnerName, LicenseID) values ('Toyota','Camry', 12000, 'Bob','HED999');
insert into #cars(CarMake, CarModel, CarValue, OwnerName, LicenseID) values ('Toyota','Camry', 19000, 'Helen','WKS444');
insert into #cars(CarMake, CarModel, CarValue, OwnerName, LicenseID) values ('Ford','Mustang',30000, 'Amy','JKJL88');
insert into #cars(CarMake, CarModel, CarValue, OwnerName, LicenseID) values ('Ford','Mustang',30000, 'Billy-Bob','EZ1111');
insert into #cars(CarMake, CarModel, CarValue, OwnerName, LicenseID) values ('Aston Martin','Vantage',90000, 'Mike','HY7733');

然后,我使用 CTE 构建了一个数据集,其中汽车执照和值由品牌/型号附加/聚合。变量 @FlagOwnerName 控制是在最终的 SELECT 语句中使用来自 CTE 的值还是来自源表的基值:

DECLARE @FlagOwnerName bit = 1;

WITH cte (CarMake, CarModel, CarValueTotal, Car_Val, LicenseList, License_ID, length_)
AS 
( 
    SELECT 
        CarMake, CarModel, 0, 0, CAST( '' AS VARCHAR(8000) ), CAST( '' AS VARCHAR(8000) ), 0
    FROM #cars
    GROUP BY CarMake, CarModel
    UNION ALL 
    SELECT c.CarMake, c.CarModel, cte.CarValueTotal + c.CarValue, c.CarValue, 
            CAST(cte.LicenseList + CASE WHEN length_ = 0 THEN '' ELSE ', ' END + c.LicenseID AS VARCHAR(8000) ),  
            CAST( LicenseID AS VARCHAR(8000)), 
            length_ + 1
    FROM cte 
    INNER JOIN #cars c ON cte.CarMake = c.CarMake AND cte.CarModel = c.CarModel
    WHERE c.LicenseID > cte.License_ID 
)
SELECT 
    cars.CarMake, 
    cars.CarModel, 
    CASE WHEN @FlagOwnerName = 1 THEN cars.OwnerName ELSE 'ALL' END as OwnerName,
    CASE WHEN @FlagOwnerName = 1 THEN cars.CarValue ELSE totals.CarValueTotal END as CarValue,
    CASE WHEN @FlagOwnerName = 1 THEN cars.LicenseID ELSE totals.LicenseList END as LicenseID
FROM  #cars cars
INNER JOIN 
(
    SELECT CarMake, CarModel, LicenseList, CarValueTotal
    FROM ( 
            SELECT CarMake, CarModel, LicenseList, CarValueTotal,
            RANK() OVER ( PARTITION BY CarMake, CarModel ORDER BY length_ DESC )
            FROM CTE 
        ) D ( CarMake, CarModel, LicenseList, CarValueTotal, rank )
    WHERE rank = 1 
) totals ON cars.CarMake = totals.CarMake AND cars.CarModel = totals.CarModel
GROUP BY 
    cars.CarMake, 
    cars.CarModel,
    CASE WHEN @FlagOwnerName = 1 THEN cars.OwnerName ELSE 'ALL' END,
    CASE WHEN @FlagOwnerName = 1 THEN cars.CarValue ELSE totals.CarValueTotal END,
    CASE WHEN @FlagOwnerName = 1 THEN cars.LicenseID ELSE totals.LicenseList END

所以当@FlagOwnerName = 1 我们得到:

CarMake         CarModel    OwnerName   CarValue  LicenseID
Aston Martin    Vantage     Mike        90000     HY7733
Ford            Mustang     Amy         30000     JKJL88
Ford            Mustang     Billy-Bob   30000     EZ1111
Toyota          Camry       Bob         12000     HED999
Toyota          Camry       Helen       19000     WKS444
Toyota          Camry       Steve       12000     ABC123

当@FlagOwnerName = 0 我们得到:

CarMake         CarModel    OwnerName   CarValue  LicenseID
Aston Martin    Vantage     ALL         90000     HY7733
Ford            Mustang     ALL         60000     EZ1111, JKJL88
Toyota          Camry       ALL         43000     ABC123, HED999, WKS444

请注意,在您的评论中,您暗示您不希望在 @FlagOwnerName = 0 时返回 OwnerName,虽然这在存储过程中是可能的(即基于参数执行不同的查询)我不推荐它。最好返回一组一致的列,如果您在其顶部使用报告工具,那么您可以在其中包含一些逻辑以根据参数值隐藏列。

【讨论】:

    猜你喜欢
    • 2018-08-22
    • 2018-03-30
    • 1970-01-01
    • 1970-01-01
    • 2017-12-11
    • 1970-01-01
    • 1970-01-01
    • 2021-06-28
    • 2023-03-06
    相关资源
    最近更新 更多