I am trying to get customer spending data by range from the orders table, but the script seems to always combine all the tables rows per customer id regardless of however I try to specify date ranges. Any guidance in how I can accomplish my goal here is very appreciated.
EX:
CREATE TABLE customers
(
idCustomer INT IDENTITY(100,1),
name VARCHAR(100),
PRIMARY KEY (idCustomer)
);
INSERT INTO customers
VALUES ('Bob'), ('Barb');
CREATE TABLE orders
(
idOrder INT IDENTITY(1,1),
idCustomer INT,
orderTotal DECIMAL(19,2),
orderDate DATETIME2
PRIMARY KEY (idOrder)
);
INSERT INTO orders
VALUES (100, 25.25, '2018-4-15'),
(100, 37.00, '2018-6-15'),
(100, 175.00, '2019-3-1'),
(100, 232.33, '2019-8-3'),
(101, 18.56, '2018-1-17'),
(101, 3004.50, '2018-5-12'),
(101, 2.98, '2019-6-1'),
(101, 15.00, '2019-11-3')
SELECT
c.idCustomer,
c.name,
COUNT(ord2018.idOrder) AS '#Orders 2018',
SUM(ord2018.orderTotal) AS 'Total Spent 2018'
COUNT(ord2019.idOrder) AS '#Orders 2019',
SUM(ord2019.orderTotal) AS 'Total Spent 2019'
COUNT(ordersAll.idOrder) AS '#Orders Lifetime',
SUM(ordersAll.orderTotal) AS 'Total Spent Lifetime'
FROM
customers c
JOIN
orders ord2018 ON ord2018.idCustomer = c.idCustomer
AND ord2018.orderDate > '2017-12-31'
AND ord2018.orderDate < '2019-1-1'
JOIN
orders ord2019 ON ord2019.idCustomer = c.idCustomer
AND ord2019.orderDate > '2018-12-31'
AND ord2019.orderDate < '2020-1-1'
JOIN
orders ordersAll on ordersAll.idCustomer = c.idCustomer
GROUP BY
c.idCustomer, c.name
I want to see something like this:
idCustomer | Name | #Orders 2018 | Total Spent 2018 | #Orders 2019 | Total Spent 2019 | #Orders Lifetime | Total Spent Lifetime |
---|---|---|---|---|---|---|---|
100 | Bob | 2 | 62.25 | 2 | 407.33 | 4 | 469.58 |
101 | Barb | 2 | 3023.06 | 2 | 17.98 | 4 | 3041.04 |
But I am getting duplicate values across the total columns which appear to just be the sum of all records in orders table per customer.
Thanks in advance.
For the defined problem and with the years you know about from the sample data hard-coded:
;WITH agg AS
(
SELECT idCustomer, y = YEAR(orderDate),
OrderCount = COUNT(*),
TotalSpent = COALESCE(SUM(orderTotal),0)
FROM dbo.orders
GROUP BY idCustomer, DATEPART(YEAR, orderDate)
)
SELECT agg.idCustomer, c.name,
OrderCount2018 = MAX(CASE WHEN y = 2018 THEN OrderCount END),
TotalSpent2018 = MAX(CASE WHEN y = 2018 THEN TotalSpent END),
OrderCount2019 = MAX(CASE WHEN y = 2019 THEN OrderCount END),
TotalSpent2019 = MAX(CASE WHEN y = 2019 THEN TotalSpent END),
LifetimeCount = SUM(OrderCount),
LifetimeSpent = SUM(TotalSpent)
FROM agg
INNER JOIN dbo.customers AS c
ON c.idCustomer = agg.idCustomer
GROUP BY agg.idCustomer, c.name;
However you want the query to be dynamic, so you can't be hard-coding years and column names. To do this dynamically:
DECLARE @MinYear int, @MaxYear int;
SELECT @MinYear = MIN(YEAR(orderDate)), @MaxYear = MAX(YEAR(orderDate))
FROM dbo.orders;
DECLARE @sql nvarchar(max) = N';WITH agg AS
(
SELECT idCustomer, y = YEAR(orderDate),
OrderCount = COUNT(*),
TotalSpent = COALESCE(SUM(orderTotal),0)
FROM dbo.orders
GROUP BY idCustomer, DATEPART(YEAR, orderDate)
)
SELECT agg.idCustomer, c.name';
;WITH y(y) AS (SELECT @MinYear UNION ALL
SELECT y+1 FROM y WHERE y < @MaxYear),
z(y) AS (SELECT CONVERT(char(4), y) FROM y)
SELECT @sql += N',
OrderCount' + y + N' = MAX(CASE WHEN y = ' + y + N' THEN OrderCount END),
TotalSpent' + y + N' = MAX(CASE WHEN y = ' + y + N' THEN TotalSpent END)'
FROM z;
SET @sql += N',
LifetimeCount = SUM(OrderCount),
LifetimeSpent = SUM(TotalSpent)
FROM agg
INNER JOIN dbo.customers AS c
ON c.idCustomer = agg.idCustomer
GROUP BY agg.idCustomer, c.name;';
SELECT @sql;
EXEC sys.sp_executesql @sql;
Output in both cases:
idCustomer | name | OrderCount2018 | TotalSpent2018 | OrderCount2019 | TotalSpent2019 | LifetimeCount | LifetimeSpent |
---|---|---|---|---|---|---|---|
100 | Bob | 2 | 62.25 | 2 | 407.33 | 4 | 469.58 |
101 | Barb | 2 | 3023.06 | 2 | 17.98 | 4 | 3041.04 |