【问题标题】:Convert Access Crosstab/PIVOT query to T-SQL将 Access Crosstab/PIVOT 查询转换为 T-SQL
【发布时间】:2016-05-04 21:32:52
【问题描述】:

我需要转换为在 SQL Server 下工作的访问查询:

TRANSFORM Sum([T_Leads]![OrderType]='New Order')-1 & " / " & Sum([T_Leads]![OrderType]='Change Order')-1 
AS [New / Change]   
SELECT Employees.EmployeeName as Name, Count(T_Leads.OrderType) AS Total
FROM Employees INNER JOIN T_Leads ON Employees.EmployeeID = T_Leads.EmployeeID
WHERE (((T_Leads.Date)>Date()-7))
and [Employees.LeadRotation] <> "Inactive"
GROUP BY Employees.EmployeeName
ORDER BY T_Leads.Date
PIVOT T_Leads.Date;

输出显示当前领导的员工列表(非“非活动”)。对于列标题,显示前 7 天的日期(如果在那一天提交了潜在客户),并且每个日期下显示两个总计。一个代表收到的新订单总数,另一个代表变更订单总数。我找不到任何生成日期列并显示两个值的示例 在每一列下。

Access 查询当前在 GridView 中产生如下输出:

+-------------+-------+----------+----------+----------+----------+-----------+
| Name        | Total | 4/5/2016 | 4/6/2016 | 4/7/2016 | 4/8/2016 | 4/11/2016 |  
+-------------+-------+----------+----------+----------+----------+-----------+
| Doe, Jane   |  9    | 0/1      | 0/2      | 0/3      | /        | 0/3       |
+-------------+-------+----------+----------+----------+----------+-----------+
| Guy, Some   | 4     | 0/1      | 0/1      | /        | /        | 0/2       | 
+-------------+-------+----------+----------+----------+----------+-----------+
| Doe, John   | 10    | 0/1      | 1/1      | 2/1      | 0/3      | 0/1       |

样本数据:

| EmployeeID  | Customer         | Date       | OrderType|
+-------------+------------------+------------+----------+
| 1           | Fake Customer    | 2016-05-14 | New      | 
+-------------+------------------+------------+----------+
| 2           | Some Company     | 2016-05-13 | Change   | 
+-------------+------------------+------------+----------+
| 3           | Stuff Inc.       | 2016-05-14 | New      | 
+-------------+------------------+------------+----------+
| 3           | Cool Things      | 2016-05-12 | Change   |

【问题讨论】:

    标签: sql sql-server tsql ms-access


    【解决方案1】:
    IF OBJECT_ID('tmpEmployees_Test', 'U') IS NOT NULL DROP TABLE tmpEmployees_Test;
    CREATE TABLE tmpEmployees_Test (EmployeeID INT, EmployeeName VARCHAR(255));
    INSERT tmpEmployees_Test (EmployeeID, EmployeeName)
    VALUES (1, 'Doe, Jane'), (2, 'Doe, John'), (3, 'Guy, Some');
    
    
    IF OBJECT_ID('tmpOrders_Test', 'U') IS NOT NULL DROP TABLE tmpOrders_Test;
    CREATE TABLE tmpOrders_Test (EmployeeID INT, Customer VARCHAR(255), Date DATE, OrderType VARCHAR(255));
    INSERT tmpOrders_Test (EmployeeID, Customer, Date, OrderType)
    VALUES (1, 'Fake Customer', '2016-05-14', 'New')
            , (2, 'Some Company', '2016-05-13', 'Change')
            , (3, 'Stuff Inc.', '2016-05-14', 'New')
            , (3, 'Cool Things', '2016-05-12', 'Change')
            , (3, 'Amazing Things', '2016-05-12', 'Change');
    
    
    DECLARE @columns NVARCHAR(MAX), @sql NVARCHAR(MAX);
    SET @columns = N'';
    SELECT @columns += N', p.' + QUOTENAME(Name) 
      FROM (SELECT distinct CONVERT(nvarchar(30) , p.Date , 101) as Name FROM dbo.tmpOrders_Test AS p where [Date] > GETDATE()-7
      ) AS x;
    -- Kept it for formatting Purpose
    DECLARE @columns1 NVARCHAR(MAX)
    SET @columns1 = N'';
    SELECT @columns1 += N', ISNULL(p.' + QUOTENAME(Name) + ',''/'') AS ' + QUOTENAME(Name) 
      FROM (SELECT distinct CONVERT(nvarchar(30) , p.Date , 101) as Name FROM dbo.tmpOrders_Test AS p where [Date] > GETDATE()-7
      ) AS x;
    
    
    SET @sql = N'
    SELECT EmployeeName, Count(*) as Total  ' +  @columns1  + '
    FROM
    (
        SELECT  EmployeeID, EmployeeName' + ''+ @columns1 + '' + '
        FROM
        (
          SELECT    o.employeeID,EmployeeName, CAST(COUNT(case WHEN OrderType = ''New'' then 1  end) as varchar(5)) + ''/'' + 
                    CAST(COUNT(case WHEN OrderType = ''Change'' then 1  end) as varchar(5)) as OrderType, CONVERT(nvarchar(30) , p.Date , 101) as Date
           FROM     dbo.tmpOrders_Test AS p
                        INNER JOIN dbo.tmpEmployees_Test AS o
                         ON p.EmployeeID = o.EmployeeID
           GROUP BY EmployeeName, Date, o.employeeID
        ) AS j
        PIVOT
        (
          Max(OrderType) FOR Date IN ('
          + STUFF(REPLACE(@columns, ', p.[', ',['), 1, 1, '')
          + ')
        ) AS p) as p JOIN tmpOrders_Test as m on p.employeeID = m.employeeID
    where [Date] > GETDATE()-7
    GROUP BY EmployeeName ' + @columns + '
    ';
    
    PRINT @sql;
    EXEC sp_executesql @sql;
    

    这个正在使用动态 Pivot。您可能希望在应用程序或报告端而不是复杂的 sql 上执行此业务逻辑。

    【讨论】:

    • 这正是我想要的。谢谢!我在将它与我的数据一起使用时遇到的一个问题。对于 Total 列,我得到的是与 Employee 关联的潜在客户总数,而不是过去 7 天的潜在客户总数。
    • 我刚刚添加了可以解决全部问题的日期过滤器。由于样本数据量很小,我没有注意到。虽然,我没有测试它,但应该可以工作。
    【解决方案2】:

    您需要动态生成数据透视列,然后为每个列执行 case 语句。以下是您如何做到这一点的示例:

    IF OBJECT_ID('tmpEmployees_Test', 'U') IS NOT NULL DROP TABLE tmpEmployees_Test;
    CREATE TABLE tmpEmployees_Test (EmployeeID INT, EmployeeName VARCHAR(255));
    INSERT tmpEmployees_Test (EmployeeID, EmployeeName)
    VALUES (1, 'Doe, Jane'), (2, 'Doe, John'), (3, 'Guy, Some');
    
    IF OBJECT_ID('tmpOrders_Test', 'U') IS NOT NULL DROP TABLE tmpOrders_Test;
    CREATE TABLE tmpOrders_Test (EmployeeID INT, Customer VARCHAR(255), Date DATE, OrderType VARCHAR(255));
    INSERT tmpOrders_Test (EmployeeID, Customer, Date, OrderType)
    VALUES (1, 'Fake Customer', '2016-05-14', 'New')
            , (2, 'Some Company', '2016-05-13', 'Change')
            , (3, 'Stuff Inc.', '2016-05-14', 'New')
            , (3, 'Cool Things', '2016-05-12', 'Change')
            , (3, 'Amazing Things', '2016-05-12', 'Change');
    
    DECLARE @startDate DATE = '2016-05-14', @cols VARCHAR(MAX) = '', @cols2 VARCHAR(MAX) = '';
    
    SELECT @cols += ', CONVERT(VARCHAR(255), SUM(CASE WHEN O.Date = ''' + CONVERT(VARCHAR(255), DATEADD(DD, X.Y, @startDate)) + ''' AND O.OrderType = ''New'' THEN 1 ELSE 0 END)) + ''/'' + CONVERT(VARCHAR(255), SUM(CASE WHEN O.Date = ''' + CONVERT(VARCHAR(255), DATEADD(DD, X.Y, @startDate)) + ''' AND O.OrderType = ''Change'' THEN 1 ELSE 0 END)) ' + QUOTENAME(CONVERT(VARCHAR(255), DATEADD(DD, X.Y, @startDate), 103)) + CHAR(10) + CHAR(9) + CHAR(9)
            , @cols2 += ', CASE WHEN ' + QUOTENAME(CONVERT(VARCHAR(255), DATEADD(DD, X.Y, @startDate), 103)) + ' = ''0/0'' THEN ''/'' ELSE ' + QUOTENAME(CONVERT(VARCHAR(255), DATEADD(DD, X.Y, @startDate), 103))  + ' END ' + QUOTENAME(CONVERT(VARCHAR(255), DATEADD(DD, X.Y, @startDate), 103)) + CHAR(10) + CHAR(9)
    FROM (VALUES (0),(-1),(-2),(-3),(-4),(-5),(-6)) X(Y)
    JOIN tmpOrders_Test O ON O.Date = DATEADD(DD, X.Y, @startDate)
    GROUP BY X.Y
    ORDER BY X.Y;
    
    DECLARE @SQL VARCHAR(MAX) = '
    WITH T AS (
        SELECT E.EmployeeID
            , COUNT(*) Total
            ' + @cols + '
        FROM tmpEmployees_Test E
        JOIN tmpOrders_Test O ON O.EmployeeID = E.EmployeeID
        WHERE O.Date BETWEEN ''' + CONVERT(VARCHAR(255), DATEADD(dd, -6, @startDate)) + ''' AND ''' + CONVERT(VARCHAR(255), @startDate) + '''
        GROUP BY E.EmployeeID)
    SELECT E.EmployeeName
        , Total
        ' + @cols2 + '
    FROM T
    JOIN tmpEmployees_Test E ON E.EmployeeID = T.EmployeeID;'
    
    --PRINT @SQL;
    EXEC(@SQL);
    

    这反映了您期望的输出(据我所知),即使它看起来有点乱。不过,我不认为你可以产生你想要的输出而不让它有点混乱。

    注意:动态 SQL 中的 CTE 只是去掉所有的 '0/0' 并使其成为 '/',这似乎是最简单的方法。

    【讨论】:

      【解决方案3】:

      目前,您的交叉表对于简单的数据透视转换并不简单,因为您有两个条件聚合,然后将它们连接成一个字符串值。另外,您有一个总组数。

      在 TSQL 中,这个查询会有点复杂。考虑以下使用两个派生表数据透视聚合查询并使用CAST 连接在一起的方法。

      来源 (临时表 - 感谢@ZLK)

      CREATE TABLE #tmpOrders (EmployeeID INT, Customer VARCHAR(255), 
                               Date DATE, OrderType VARCHAR(255));
      INSERT #tmpOrders (EmployeeID, Customer, Date, OrderType)
      VALUES (1, 'Fake Customer', '2016-05-14', 'New'),
             (2, 'Some Company', '2016-05-13', 'Change'),
             (3, 'Stuff Inc.', '2016-05-14', 'New'),
             (3, 'Cool Things', '2016-05-12', 'Change'),
             (3, 'Amazing Things', '2016-05-12', 'Change');
      

      查询 (两个枢轴的内连接)

      SELECT pvt1.Name, pvt1.Total,
             CAST(pvt1.[2016-05-12] AS VARCHAR(2)) + '/' 
                  +  CAST(pvt2.[2016-05-12] AS VARCHAR(2)) As [05/12/2016], 
             CAST(pvt1.[2016-05-13] AS VARCHAR(2)) + '/' 
                  +  CAST(pvt2.[2016-05-13] AS VARCHAR(2)) As [05/13/2016], 
             CAST(pvt1.[2016-05-14] AS VARCHAR(2)) + '/' 
                  +  CAST(pvt2.[2016-05-14] AS VARCHAR(2)) As [05/14/2016]        
      FROM     
          (SELECT t.Customer as Name, t.[Date],
                  COUNT(*) As Total, 
                  SUM(CASE WHEN t.OrderType = 'New' THEN 1 ELSE NULL END) AS NewOrders
      
            FROM #tmpOrders t
            WHERE (t.[Date] > GETDATE()-7)    
            GROUP BY t.Customer, t.[Date]) AS src1     
            PIVOT 
                (
                  COUNT(src1.[NewOrders]) 
                  FOR src1.[Date] IN ([2016-05-12], [2016-05-13], [2016-05-14])
                ) AS pvt1
      
      INNER JOIN 
          (SELECT t.Customer as Name, t.[Date],
                  COUNT(*) As Total, 
                  SUM(CASE WHEN t.OrderType = 'Change' THEN 1 ELSE NULL END) AS ChangeOrders
            FROM #tmpOrders t
            WHERE (t.[Date] > GETDATE()-7)    
            GROUP BY t.Customer, t.[Date]) AS src1     
            PIVOT 
                (
                  COUNT(src1.[ChangeOrders]) 
                  FOR src1.[Date] IN ([2016-05-12], [2016-05-13], [2016-05-14])
                ) AS pvt2        
      ON pvt1.Name = pvt2.Name
      

      输出

      --Name          Total   05/12/2016  05/13/2016  05/14/2016
      --Amazing Things    1         0/1          0/0         0/0
      --Cool Things       1         0/1          0/0         0/0
      --Fake Customer     1         0/0          0/0         1/0
      --Some Company      1         0/0          0/1         0/0
      --Stuff Inc.        1         0/0          0/0         1/0
      

      请注意:您在翻译中丢失的一件事是 Access 交叉表查询的动态质量,它将输出所有现有值(直到 255 列限制)。在 SQL Server 中,对于常规数据透视,您需要提前声明此类值或使用存储的过程/函数动态返回所有值。

      【讨论】:

        猜你喜欢
        • 2012-12-06
        • 1970-01-01
        • 1970-01-01
        • 2013-10-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-29
        相关资源
        最近更新 更多