SQL Server:基于参数和STUFF()使用条件组

时间:2017-05-31 21:58:12

标签: sql-server join group-by conditional

我有一段代码,我想把它变成一个函数。代码的目的是在变量条件上将某些记录组合在一起,并使用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

1 个答案:

答案 0 :(得分:0)

根据您的评论,我不认为使用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构建了一个数据集,其中汽车许可证和值由make / model附加/聚合。变量@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,而这在存储过程中是可能的(即基于参数执行不同的查询)我不推荐它。最好返回一组一致的列,如果你在它上面使用报告工具,那么你可以在那里包含一些逻辑来根据参数值隐藏列。