SQL Complex Filter + Pivot

时间:2017-11-11 06:38:35

标签: sql sql-server tsql

我有以下表格。

用户

userID     fullName
5          Mr. Joe
7          Mr. Bean

供应

id     supplyDesc     isActive
1      Ballpen         1
2      Adhesive Tape   1
3      White Paper     1

SupplyRequests

    id  requestCode     forFrom      forTo        userID
    1   SPR-2017-1      12-01-2017   02-28-2018   5
    2   SPR-2017-2      12-01-2017   02-28-2018   7
    3   SPR-2017-3      01-01-2018   03-31-2018   7

SupplyRequestDetails

    id  rqID   supplyID  storeID    qty   isActive
    1   1      1         1          1000  1
    2   1      2         1          2000  1
    3   1      1         12         2000  1
    4   1      2         12         3000  1
    5   1      1         13         3000  1
    6   1      2         13         4000  1
    7   2      1         10         100   1
    8   2      2         10         200   1
    9   2      1         11         200   1
    10  2      2         11         300   1
    11  3      1         10         1     1
    12  3      2         10         2     1
    13  3      1         11         2     1
    14  3      2         11         3     1

要求:

在给定日期范围的情况下,获取所有用户的每个供应项目的相应总请求数量以及他们请求的唯一分支总数。必须是请求详细信息和供应表中的有效供应项目。

注意 耗材可能会不时变化

到目前为止我尝试了什么:

declare @forFrom varchar(50) = '12/01/2017'
declare @forTo varchar(50) = '02/28/2018'

declare @cols nvarchar(max), @cols2 nvarchar(max), @query NVARCHAR(MAX)
select @cols = STUFF((SELECT DISTINCT ', MAX(' + QUOTENAME(supplyDesc) + ')' + '''' + supplyDesc + ''''
                       FROM (select a.id'supplyID',
                        a.supplyDesc,
                        SUBSTRING(d.fullName, 5, LEN(MAX(d.fullName)))fullName,
                        SUM(CASE WHEN ((CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo)) THEN b.qty ELSE 0 END)totalQty
                        from Supplies a
                        left join SupplyRequestDetails b
                        on a.id = b.supplyID
                        left join SupplyRequests c
                        on b.rqID = c.id
                        left join Users d
                        on c.userID = d.id
                        where a.isActive = 1
                        or b.isActive = 1
                        group by a.id,
                            a.supplyDesc,
                            fullName
                ) as x
                FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'');

select @cols2 = STUFF((SELECT DISTINCT ',' + QUOTENAME(supplyDesc)
                       FROM (select a.id'supplyID',
                        a.supplyDesc,
                        SUBSTRING(d.fullName, 5, LEN(MAX(d.fullName)))fullName,
                        SUM(CASE WHEN ((CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo)) THEN b.qty ELSE 0 END)totalQty
                        from Supplies a
                        left join SupplyRequestDetails b
                        on a.id = b.supplyID
                        left join SupplyRequests c
                        on b.rqID = c.id
                        left join Users d
                        on c.userID = d.id
                        where a.isActive = 1
                        or b.isActive = 1
                        group by a.id,
                            a.supplyDesc,
                            fullName
                ) as x
                FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

SET @query = N'
declare @forFrom varchar(50) = ''12/01/2017''
declare @forTo varchar(50) = ''02/28/2018''
SELECT SUBSTRING(fullName, 5, LEN(fullName))fullName, ' + @cols + '
FROM (SELECT SUBSTRING(fullName, 5, LEN(fullName))fullName, ' + @cols + '
FROM (select a.id''supplyID'',
                a.supplyDesc,
                SUBSTRING(d.fullName, 5, LEN(MAX(d.fullName)))fullName,
                        SUM(CASE WHEN ((CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo)) THEN b.qty ELSE 0 END)totalQty
                from Supplies a
                left join SupplyRequestDetails b
                on a.id = b.supplyID
                left join SupplyRequests c
                on b.rqID = c.id
                left join Users d
                on c.userID = d.id
                where a.isActive = 1
                or b.isActive = 1
                group by a.id,
                    a.supplyDesc,
                    fullName
) as j
PIVOT (
    SUM(totalQty) FOR supplyDesc IN ('
    + @cols2
    + ')

) AS a
WHERE fullName is not null
GROUP BY fullName
ORDER BY fullName;';


PRINT @query
EXEC sp_executesql @query

结果有点像:

fullName     Ballpen        Adhesive Tape     White Paper
Mr.Joe       6303           9505              NULL

预期结果集:

fullName  uqStores  Ballpen        Adhesive Tape     White Paper
Mr. Joe   3         6000           4000              NULL
Mr. Bean  2         300            500               NULL

正如您可能注意到的那样,我首先尝试从“耗材”表中获取项目列表,然后尝试(但未通过)将每个项目标记给在给定条件(日期范围和活动状态)内分别请求每个项目的用户。然后,创建动态列并准备供应的枢轴。此外,我还没有弄清楚如何从这些查询中计算所有唯一的storeID。

我在想第一个问题是我是如何尝试将用户标记为每个项目的。但是,不知道如何同时使用一堆物品。

非常感谢您的帮助。

编辑:

我不得不修复一些问题。我相信我复制了错误的物品。

更新

fullName      Ballpen      Adhesive Tape      White Paper
Mr. Joe       6000         9000               NULL
Mr. Bean      300          500                NULL
PS:不知何故,我一点一点地到达解决方案。现在我只需要按要求获得独特的商店。

更新

这似乎有效,但非常混乱。

DECLARE @forFrom varchar(50) = '12/01/2017'
DECLARE @forTo varchar(50) = '02/28/2018'

declare @cols nvarchar(max), @cols2 nvarchar(max), @query NVARCHAR(MAX)
    select @cols = STUFF((SELECT DISTINCT ', MAX(' + QUOTENAME(supplyDesc) + ')' + '''' + supplyDesc + ''''
                           FROM (select DISTINCT a.id'supplyID',
                                    a.supplyDesc,
                                    COUNT(CASE WHEN ((CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo)) THEN b.storeID ELSE NULL END)numberOfStores,
                                    SUBSTRING(d.fullName, 5, LEN(MAX(CASE WHEN (CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo) THEN d.fullName ELSE NULL END)))fullName,
                                    SUM(CASE WHEN ((CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo)) THEN b.qty ELSE 0 END)totalQty
                                from Supplies a
                                left join SupplyRequestDetails b
                                on a.id = b.supplyID
                                left join SupplyRequests c
                                on b.rqID = c.id
                                left join Users d
                                on c.userID = d.id
                                where a.isActive = 1
                                or b.isActive = 1
                                group by a.id,
                                    a.supplyDesc,
                                    fullName
                    ) as x
                    FOR XML PATH(''), TYPE
                ).value('.', 'NVARCHAR(MAX)') 
            ,1,1,'');
    select @cols2 = STUFF((SELECT DISTINCT ',' + QUOTENAME(supplyDesc)
                           FROM (select DISTINCT a.id'supplyID',
                                    a.supplyDesc,
                                    COUNT(CASE WHEN ((CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo)) THEN b.storeID ELSE NULL END)numberOfStores,
                                    SUBSTRING(d.fullName, 5, LEN(MAX(CASE WHEN (CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo) THEN d.fullName ELSE NULL END)))fullName,
                                    SUM(CASE WHEN ((CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo)) THEN b.qty ELSE 0 END)totalQty
                                from Supplies a
                                left join SupplyRequestDetails b
                                on a.id = b.supplyID
                                left join SupplyRequests c
                                on b.rqID = c.id
                                left join Users d
                                on c.userID = d.id
                                where a.isActive = 1
                                or b.isActive = 1
                                group by a.id,
                                    a.supplyDesc,
                                    fullName
                    ) as x
                    FOR XML PATH(''), TYPE
                ).value('.', 'NVARCHAR(MAX)') 
            ,1,1,'')

    SET @query = N'
    declare @forFrom varchar(50) = ''' + @forFrom + '''
    declare @forTo varchar(50) = ''' + @forTo + '''
    SELECT SUBSTRING(fullName, 5, LEN(fullName))fullName, COUNT(numberOfStores)numOfStores,' + @cols + '
    FROM (select DISTINCT a.id''supplyID'',
                a.supplyDesc,
                COUNT(CASE WHEN ((CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo)) THEN b.storeID ELSE NULL END)numberOfStores,
                SUBSTRING(d.fullName, 5, LEN(MAX(CASE WHEN (CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo) THEN d.fullName ELSE NULL END)))fullName,
                SUM(CASE WHEN ((CAST(c.forFrom as datetime) >= @forFrom and CAST(c.forTo as datetime) <= @forTo)) THEN b.qty ELSE 0 END)totalQty
            from Supplies a
            left join SupplyRequestDetails b
            on a.id = b.supplyID
            left join SupplyRequests c
            on b.rqID = c.id
            left join Users d
            on c.userID = d.id
            where a.isActive = 1
            or b.isActive = 1
            group by a.id,
                a.supplyDesc,
                fullName
    ) as j
    PIVOT (
        SUM(totalQty) FOR supplyDesc IN ('
        + @cols2
        + ')

    ) AS a
    WHERE fullName is not null
    GROUP BY fullName
    ORDER BY fullName;';

    EXEC sp_executesql @query

当然有更好的方法来做到这一点。我真的很喜欢你的建议。

更新

从@sarslan的答案中得出并编辑:

DECLARE @Query NVARCHAR(MAX) = '
SELECT * FROM (
    SELECT 
        S.supplyDesc 
        , fullName
        , COUNT(DISTINCT storeID) [uqStores]
        , SUM(qty) SumQty   
    FROM 
        Supplies S
        LEFT JOIN ( SELECT U.fullName, supplyID, storeID, qty, SRD.isActive FROM SupplyRequestDetails SRD 
                        INNER JOIN SupplyRequests SR ON SRD.rqID = SR.id AND ( SR.forFrom >= @forFrom AND SR.forTo <= @forTo ) 
                        INNER JOIN Users U ON SR.userID = U.userID ) AS ST ON ST.supplyID = S.id
    WHERE ST.isActive = 1
    GROUP BY fullName, supplyDesc 
    HAVING COUNT(DISTINCT storeID) > 1
) SRC
PIVOT ( SUM (SumQty) FOR supplyDesc IN ( ' + @ColNames + ')) PVT '

比方说,例如,SupplyRequestDetails中id为1的项目将变为非活动状态。

结果是:

fullName      Ballpen      Adhesive Tape      White Paper
Mr. Bean      5000         NULL               NULL
Mr. Bean      NULL         9000               NULL
Mr. Joe       300          500                NULL

1 个答案:

答案 0 :(得分:1)

您可以使用此脚本。

declare @forFrom datetime = '12/01/2017'
declare @forTo datetime = '02/28/2018'

DECLARE @ColNames NVARCHAR(MAX)= ''
SELECT @ColNames = @ColNames + ',' + QUOTENAME (supplyDesc) FROM Supplies WHERE isActive = 1
SET @ColNames = STUFF(@ColNames,1,1,'')

DECLARE @Query NVARCHAR(MAX) = '
SELECT * FROM (
    SELECT 
        S.supplyDesc 
        , fullName
        , COUNT(DISTINCT storeID) [uqStores]
        , SUM(qty) SumQty   
    FROM 
        Supplies S
        LEFT JOIN ( SELECT U.fullName, supplyID, storeID, qty, SRD.isActive FROM SupplyRequestDetails SRD 
                        INNER JOIN SupplyRequests SR ON SRD.rqID = SR.id AND ( SR.forFrom >= @forFrom AND SR.forTo <= @forTo ) 
                        INNER JOIN Users U ON SR.userID = U.userID 
                    WHERE SRD.isActive = 1 ) AS ST ON ST.supplyID = S.id
    WHERE S.isActive = 1 
    GROUP BY fullName, supplyDesc 
    HAVING COUNT(DISTINCT storeID) > 1
) SRC
PIVOT ( SUM (SumQty) FOR supplyDesc IN ( ' + @ColNames + ')) PVT '


EXEC sp_executesql @Query, N'@forFrom  DATETIME, @forTo DATETIME' , @forFrom = @forFrom, @forTo = @forTo

结果:

fullName             uqStores    Ballpen     Adhesive Tape White Paper
-------------------- ----------- ----------- ------------- -----------
Mr. Bean             2           300         500           NULL
Mr. Joe              3           6000        9000          NULL