T-SQL - Pivot by week

2019-07-24 19:37发布

问题:

I'm currently trying to create a T-SQL, which runs through a list of deliveries in a table, and groups them by the Customer and the Depot - so each row will be

Customer, Depot, Total Value (sum of a column called Rate)

However, the customer would like the 'total value' split into the last 9 weeks - so rather than total value, we'll have columns like this:

22/01/2012  29/01/2012  05/02/2012  12/02/2012  19/02/2012  26/02/2012  04/03/2012  11/03/2012  18/03/2012

The dates would of course change for when they run the query - it'll just be the last 9 weeks. They also want a column for the Average of all these.

I understand pivot may help me but I'm a bit stumped on how to do this. Here's my current query:

SELECT d.Name AS 'Depot, s.Name AS 'Customer', SUM(c.Rates) AS 'Total Value'
FROM Deliveries AS c INNER JOIN Account AS s ON c.Customer = s.ID
INNER JOIN Depots AS d ON c.CollectionDepot = d.Letter
GROUP BY d.Name, s.Name

Many thanks!

EDIT: Here's a screenshot of the data currently - we won't need the 'total' column on the end, just there to show you. The 'Date' column is present in the Deliveries table and is called TripDate

回答1:

You would have to make use of the PIVOT Keyword which is available in your version of SQL Server. I have outlined how your query should look, of course some tweaking will be required since it is difficult to test without having a copy of your data.

  SELECT Depots.Name AS 'Depot',  Account.Name, '22/01/2012',  '29/01/2012',  '05/02/2012',  '12/02/2012',
   FROM 
(SELECT Name, 
    FROM Deliveries
    INNER JOIN Account ON Deliveries.Customer = Account.ID
    INNER JOIN Depots ON Account.CollectionDepot) AS Source
PIVOT
(
    SUM(Deliveries.Rates)
    FOR Date IN ('22/01/2012',  '29/01/2012',  '05/02/2012',  '12/02/2012')
) AS 'Pivot Table'

For reference you could use this as a guide:

http://msdn.microsoft.com/en-us/library/ms177410.aspx



回答2:

Without knowing your exact data. It hard to predict what you are getting. But I can give you a suggestion of a solution.

Table structure

CREATE TABLE Deliveries
(
    Customer INT,
    CollectionDepot INT,
    Rates FLOAT,
    TripDate DATETIME
)
CREATE TABLE Account
(
    Name VARCHAR(100),
    ID INT
)
CREATE TABLE Depots
(
    Name VARCHAR(100),
    Letter INT
)

Test data

INSERT INTO Deliveries
VALUES
    (1,1,452,GETDATE()-10),
    (1,1,800,GETDATE()-30),
    (1,1,7895,GETDATE()-2),
    (1,1,451,GETDATE()-2),
    (1,1,478,GETDATE()-89),
    (1,1,4512,GETDATE()-31),
    (1,1,782,GETDATE()-20),
    (1,1,652,GETDATE()-5),
    (1,1,752,GETDATE()-452)

INSERT INTO Account
VALUES
    ('Customer 1',1)

INSERT INTO Depots
VALUES
    ('Depot 1',1)

Table that contains the ranges and the formated date

CREATE TABLE #tmp
(
    StartDate DATETIME,
    EndDate DATETIME,
    FomatedDate VARCHAR(20)
)

Calculate the date ranges

;WITH Nbrs ( n ) AS (
        SELECT 0 UNION ALL
        SELECT 1+n FROM Nbrs WHERE n < 8 )
INSERT INTO #tmp
SELECT
    DATEADD(WEEK,-n-1,GETDATE()),
    DATEADD(WEEK,-n,GETDATE()),
    convert(varchar, DATEADD(WEEK,-n,GETDATE()), 112)
FROM
    Nbrs
ORDER BY
    -n

The date columns for the pivot

DECLARE @cols VARCHAR(MAX)
SELECT  @cols = COALESCE(@cols + ','+QUOTENAME(FomatedDate),
                     QUOTENAME(FomatedDate))
FROM 
    #tmp

Declaring some dynamic sql and executing it

DECLARE @query NVARCHAR(4000)=
N'SELECT
    *
FROM
(
    SELECT 
        Depots.Name AS Depot, 
        Account.Name AS Customer, 
        Deliveries.Rates,
        tmp.FomatedDate,
        AVG(Deliveries.Rates) OVER(PARTITION BY 1) AS Average,
        SUM(Deliveries.Rates) OVER(PARTITION BY 1) AS Total
    FROM 
        Deliveries
        JOIN Account 
            ON Deliveries.Customer = Account.ID
        JOIN Depots
            ON Deliveries.CollectionDepot = Depots.Letter
        JOIN #tmp AS tmp
            ON Deliveries.TripDate BETWEEN tmp.StartDate AND tmp.EndDate
) AS p
PIVOT
(
    AVG(rates)
    FOR FomatedDate IN ('+@cols+')
) AS pvt'

EXECUTE(@query)

And then cleaning up after myself.

DROP TABLE Deliveries
DROP TABLE Account
DROP TABLE Depots
DROP TABLE #tmp