Related
I have a DB for movie_rental. The Tables I have are for :
Customer Level:
Primary key: Customer_id(INT)
first_name(VARCHAR)
last_name(VARCHAR)
Movie Level:
Primary key: Film_id(INT)
title(VARCHAR)
category(VARCHAR)
Rental Level:
Primary key: Rental_id(INT).
The other columns in this table are:
Rental_date(DATETIME)
customer_id(INT)
film_id(INT)
payment_date(DATETIME)
amount(DECIMAL(5,2))
Now the question is to Create a master list of customers categorized by the following:
Regulars, who rent at least once a week
Weekenders, for whom most of their rentals come on Saturday and Sundays
I am not looking for the code here but the logic to approach this problem. Have tried quite a number of ways but was not able to form the logic as to how I can look up for a customer id in each week. The code I tried is as follows:
select
r.customer_id
, concat(c.first_name, ' ', c.last_name) as Customer_Name
, dayname(r.rental_date) as day_of_rental
, case
when dayname(r.rental_date) in ('Monday','Tuesday','Wednesday','Thursday','Friday')
then 'Regulars'
else 'Weekenders'
end as Customer_Category
from rental r
inner join customer c on r.customer_id = c.customer_id;
I know it is not correct but I am not able to think beyond this.
First, you don't need the customer table for this. You can add that in after you have the classification.
To solve the problem, you need the following information:
The total number of rentals.
The total number of weeks with a rental.
The total number of weeks overall or with no rental.
The total number of rentals on weekend days.
You can obtain this information using aggregation:
select r.customer_id,
count(*) as num_rentals,
count(distinct yearweek(rental_date)) as num_weeks,
(to_days(max(rental_date)) - to_days(min(rental_date)) ) / 7 as num_weeks_overall,
sum(dayname(r.rental_date) in ('Saturday', 'Sunday')) as weekend_rentals
from rental r
group by r.customer_id;
Now, your question is a bit vague on thresholds and what to do if someone only rents on weekends but does so every week. So, I'll just make arbitrary assumptions for the final categorization:
select r.customer_id,
(case when num_weeks > 10 and
num_weeks >= num_weeks_overall * 0.9
then 'Regular' -- at least 10 weeks and rents in 90% of the weeks
when weekend_rentals >= 0.8 * num_rentals
then 'Weekender' -- 80% of rentals are on the weekend'
else 'Hoi Polloi'
end) as category
from (select r.customer_id,
count(*) as num_rentals,
count(distinct yearweek(rental_date)) as num_weeks,
(to_days(max(rental_date)) - to_days(min(rental_date)) ) / 7 as num_weeks_overall,
sum(dayname(r.rental_date) in ('Saturday', 'Sunday')) as weekend_rentals
from rental r
group by r.customer_id
) r;
The problem with the current approach is that every rental of every customer will be treated separately. I am assuming a customer might rent more than once and so, we will need to aggregate all rental data for a customer to calculate the category.
So to create the master table, you have mentioned in the logic that weekenders are customers "for whom most of their rentals come on Saturday and Sundays", whereas regulars are customers who rent at least once a week.
2 questions:-
What is the logic for "most" for weekenders?
Are these two categories mutually exclusive? From the statement it does not seem so, because a customer might rent only on a Saturday or a Sunday.
I have tried a solution in Oracle SQL dialect (working but performance can be improved) with the logic being thus: If the customer has rented more on weekdays than on weekends, the customer is a Regular, else a Weekender. This query can be modified based on the answers to the above questions.
select
c.customer_id,
c.first_name || ' ' || c.last_name as Customer_Name,
case
when r.reg_count>r.we_count then 'Regulars'
else 'Weekenders'
end as Customer_Category
from customer c
inner join
(select customer_id, count(case when trim(to_char(rental_date, 'DAY')) in ('MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY') then 1 end) as reg_count,
count(case when trim(to_char(rental_date, 'DAY')) in ('SATURDAY','SUNDAY') then 1 end) as we_count
from rental group by customer_id) r on r.customer_id=c.customer_id;
Updated query based on clarity given in comment:-
select
c.customer_id,
c.first_name || ' ' || c.last_name as Customer_Name,
case when rg.cnt>0 then 1 else 0 end as REGULAR,
case when we.cnt>0 then 1 else 0 end as WEEKENDER
from customer c
left outer join
(select customer_id, count(rental_id) cnt from rental where trim(to_char(rental_date, 'DAY')) in ('MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY') group by customer_id) rg on rg.customer_id=c.customer_id
left outer join
(select customer_id, count(rental_id) cnt from rental where trim(to_char(rental_date, 'DAY')) in ('SATURDAY','SUNDAY') group by customer_id) we on we.customer_id=c.customer_id;
Test Data :
insert into customer values (1, 'nonsensical', 'coder');
insert into rental values(1, 1, sysdate, 1, sysdate, 500);
insert into customer values (2, 'foo', 'bar');
insert into rental values(2, 2, sysdate-5, 2, sysdate-5, 800); [Current day is Friday]
Query Output (first query):
CUSTOMER_ID CUSTOMER_NAME CUSTOMER_CATEGORY
1 nonsensical coder Regulars
2 foo bar Weekenders
Query Output (second query):
CUSTOMER_ID CUSTOMER_NAME REGULAR WEEKENDER
1 nonsensical coder 0 1
2 foo bar 1 0
This is a study of cohorts. First find the minimal expression of each group:
# Weekday regulars
SELECT
customer_id
FROM rental
WHERE WEEKDAY(`date`) < 5 # 0-4 are weekdays
# Weekend warriors
SELECT
customer_id
FROM rental
WHERE WEEKDAY(`date`) > 4 # 5 and 6 are weekends
Now we know how to get a listing of customers who have rented on weekdays and weekends, inclusive. These queries only actually tell us that these were customers who visited on a day in the given series, hence we need to make some judgements.
Let's introduce a periodicity, which then allows us to gain thresholds. We'll need aggregation too, so we're going to count the weeks that are distinctly knowable by grouping to the rental.customer_id.
# Weekday regulars
SELECT
customer_id
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(`date`) < 5
GROUP BY customer_id
# Weekend warriors
SELECT
customer_id
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(`date`) > 4
GROUP BY customer_id
We also need a determinant period:
FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS weeks_in_period
Put those together:
# Weekday regulars
SELECT
customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(`date`) < 5
CROSS JOIN (
SELECT FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS total_weeks
) AS period
GROUP BY customer_id
# Weekend warriors
SELECT
customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
CROSS JOIN (
SELECT FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS total_weeks
) AS period
WHERE WEEKDAY(`date`) > 4
GROUP BY customer_id
So now we can introduce our threshold accumulator per cohort.
# Weekday regulars
SELECT
customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(`date`) < 5
CROSS JOIN (
SELECT FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS total_weeks
) AS period
GROUP BY customer_id
HAVING total_weeks = weeks_as_customer
# Weekend warriors
SELECT
customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
CROSS JOIN (
SELECT FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS total_weeks
) AS period
WHERE WEEKDAY(`date`) > 4
GROUP BY customer_id
HAVING total_weeks = weeks_as_customer
Then we can use these to subquery our master list.
SELECT
customer.customer_id
, CONCAT(customer.first_name, ' ', customer.last_name) as customer_name
, CASE
WHEN regulars.customer_id IS NOT NULL THEN 'regular'
WHEN weekenders.customer_id IS NOT NULL THEN 'weekender'
ELSE NULL
AS category
FROM customer
CROSS JOIN (
SELECT FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS total_weeks
) AS period
LEFT JOIN (
SELECT
rental.customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(rental.`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(rental.`date`) < 5
GROUP BY rental.customer_id
HAVING total_weeks = weeks_as_customer
) AS regulars ON customer.customer_id = regulars.customer_id
LEFT JOIN (
SELECT
rental.customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(rental.`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(rental.`date`) > 4
GROUP BY rental.customer_id
HAVING total_weeks = weeks_as_customer
) AS weekenders ON customer.customer_id = weekenders.customer_id
HAVING category IS NOT NULL
There is some ambiguity as far as whether cross-cohorts are to be left out (regulars who missed a week because they rented on the weekend-only at least once, for instance). You would need to work this type of inclusivity/exclusivity question out.
This would involve going back to the cohort-specific queries to introduce and tune the queries to explain that degree of further comprehension, and/or add other cohort cross-cutting subqueries that can be combined in other ways to establish better and/or more comprehensions at the top view.
However, I think what I've provided matches reasonably with what you've provided given this caveat.
Hi below is my sql query as
SELECT
o.OrderNumber,
oi.Sku,
Sum(Isnull(oi.Price * oi.Quantity,0)) as Price,
DENSE_RANK() over(partition by o.orderNumber order by oi.sku) as CouponRowId
from ac_OrderItems oi
inner join ac_Orders o on oi.OrderId = o.OrderId
Inner Join ac_OrderShipments os on oi.OrderShipmentId =os.OrderShipmentId
WHERE (oi.OrderItemTypeId IN (5))
group by o.OrderNumber, oi.Sku
and below is the record I am getting
OrderNumber Sku Price CouponRowId
90061 BLACKBERRY -5.6900 1
90061 LEMON -5.6900 2
90061 PEACH -5.6900 3
90061 SHIP100 -10.920 4
but I want my record as
OrderNumber Sku Price
90061 BLACKBERRY -5.6900
LEMON -5.6900
PEACH -5.6900
SHIP100 -10.920
I want that if order number is same in that case all detail record should come in 1st row and then after other record should only show Sku and price only and also we need to remove the DENSE_RANK() column
By seeing the query seems like Partition Clause can help you to
resolve the issue. Please look at the below query. I am not so sure
about the data model, but looks like you need to partition by using
the OrderNumber column. Please replace the query in the first CTE with your actual table which will be like the second query. I cant test it since I don't have the environment. Please test the query
;WITH CTE_Table
AS
(
SELECT 90061 'OrderNumber' , 'BLACKBERRY' Sku, -5.6900 'Price', 1 'CouponRowId' UNION
SELECT 90061, 'LEMON', -5.6900, 2 UNION
SELECT 90061, 'PEACH', -5.6900, 3 UNION
SELECT 90061, 'SHIP100', -10.920, 4 UNION
SELECT 90062 'OrderNumber' , 'BLACKBERRY' Sku, -5.6900 'Price', 1 'CouponRowId' UNION
SELECT 90062, 'LEMON', -5.6900, 2 UNION
SELECT 90062, 'PEACH', -5.6900, 3 UNION
SELECT 90062, 'SHIP100', -10.920, 4
),
CTE_New
AS
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY OrderNumber ORDER BY OrderNumber) Patrn FROM CTE_Table
)
SELECT CASE WHEN Patrn =1 THEN CONVERT(VARCHAR(10),OrderNumber) ELSE '' END OrderNumber, Sku, Price, CouponRowId FROM CTE_New
;WITH CTE_Table
AS
(
SELECT
o.OrderNumber,
oi.Sku,
Sum(Isnull(oi.Price * oi.Quantity,0)) as Price,
DENSE_RANK() over(partition by o.orderNumber order by oi.sku) as CouponRowId
from ac_OrderItems oi
inner join ac_Orders o on oi.OrderId = o.OrderId
Inner Join ac_OrderShipments os on oi.OrderShipmentId =os.OrderShipmentId
WHERE (oi.OrderItemTypeId IN (5))
group by o.OrderNumber, oi.Sku
),
CTE_New
AS
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY OrderNumber ORDER BY OrderNumber) Patrn FROM CTE_Table
)
SELECT CASE WHEN Patrn =1 THEN CONVERT(VARCHAR(10),OrderNumber) ELSE '' END OrderNumber, Sku, Price, CouponRowId FROM CTE_New
Seeing your comment this is an attempt to solve it on the query itself (I assumed CouponRowID might do the job, if OrderNumber is unique):
WITH
tmp AS (
SELECT o.OrderNumber, oi.Sku, SUM(ISNULL(oi.Price * oi.Quantity, 0)) AS Price, DENSE_RANK() OVER (PARTITION BY o.orderNumber ORDER BY oi.sku) AS CouponRowId
FROM ac_OrderItems oi
INNER JOIN ac_Orders o ON oi.OrderId=o.OrderId
INNER JOIN ac_OrderShipments os ON oi.OrderShipmentId=os.OrderShipmentId
WHERE(oi.OrderItemTypeId IN (5))
GROUP BY o.OrderNumber, oi.Sku
)
SELECT CASE WHEN tmp.CouponRowId=1 THEN tmp.OrderNumber END AS OrderNumber, tmp.Sku, tmp.Price
FROM tmp
ORDER BY tmp.OrderNumber, tmp.SKu;
Sorry....! I cant understand your explanations..
I just worked only for get your expected OP...
create table #ac_OrderItems(id varchar(20) --varchar, i used instead of int.Beside, varchar is easy to change to int.
, Sku varchar(20), Price decimal, CouponRowId int)
insert into #ac_OrderItems values(
90061 , 'BLACKBERRY' , -5.6900 , 1)
,(90061, 'LEMON', -5.6900, 2 )
,(90061, 'PEACH', -5.6900, 3 )
,(90061, 'SHIP100', -10.920, 4 )
,(90062 , 'BLACKBERRY' ,-5.6900 , 1 )
,(90062, 'LEMON', -5.6900, 2 )
,(90062, 'PEACH', -5.6900, 3 )
,(90062, 'SHIP100', -10.920, 4)
select * from #ac_OrderItems
;with
orders(id, sku, price, couponId, rn)
as
(
select *, ROW_NUMBER() over(partition by id order by id) from #ac_OrderItems
)select iif(rn = 1,id,''),sku,price,couponId from orders
-- if i used int, empty is denoted by 0. But you want '' this. So i used varchar id
let you know me, what you got.
I have a table called votes with 4 columns: id, name, choice, date.
****id****name****vote******date***
****1*****sam*******A******01-01-17
****2*****sam*******B******01-05-30
****3*****jon*******A******01-01-19
My ultimate goal is to count up all the votes, but I only want to count 1 vote per person, and specifically each person's most recent vote.
In the example above, the result should be 1 vote for A, and 1 vote for B.
Here is what I currently have:
select name,
sum(case when uniques.choice = A then 1 else 0 end) votesA,
sum(case when uniques.choice = B then 1 else 0 end) votesB
FROM (
SELECT id, name, choice, max(date)
FROM votes
GROUP BY name
) uniques;
However, this doesn't work because the subquery is indeed selecting the max date, but it's not including the correct choice that is associated with that max date.
Don't think "group by" to get the most recent vote. Think of join or some other option. Here is one way:
SELECT v.name,
SUM(v.choice = 'A') as votesA,
SUM(v.choice = 'B') as votesB
FROM votes v
WHERE v.date = (SELECT MAX(v2.date) FROM votes v2 WHERE v2.name = v.name)
GROUP BY v.name;
Here is a SQL Fiddle.
Your answer are close but need to JOIN self
Subquery get Max date by name then JOIN self.
select
sum(case when T.vote = 'A' then 1 else 0 end) votesA,
sum(case when T.vote = 'B' then 1 else 0 end) votesB
FROM (
SELECT name,Max(date) as date
FROM T
GROUP BY name
) AS T1 INNER JOIN T ON T1.date = T.date
SQLFiddle
Try this
SELECT
choice,
COUNT(1)
FROM
votes v
INNER JOIN
(
SELECT
id,
max(date)
FROM
votes
GROUP BY
name
) tmp ON
v.id = tmp.id
GROUP BY
choice;
Something like this (if you really need count only last vote of person)
SELECT
sum(case when vote='A' then cnt else 0 end) voteA,
sum(case when vote='B' then cnt else 0 end) voteB
FROM
(SELECT vote,count(distinct name) cnt
FROM (
SELECT name,vote,date,max(date) over (partition by name) maxd
FROM votes
)
WHERE date=maxd
GROUP BY vote
)
PS. MySQL v 8
select
name,
sum( case when choice = 'A' then 1 else 0 end) voteA,
sum( case when choice = 'B' then 1 else 0 end) voteB
from
(
select id, name, choice
from votes
where date = (select max(date) from votes t2
where t2.name = votes.name )
) t
group by name
Or output just one row for the total counts of VoteA and VoteB:
select
sum( case when choice = 'A' then 1 else 0 end) voteA,
sum( case when choice = 'B' then 1 else 0 end) voteB
from
(
select id, name, choice
from votes
where date = (select max(date) from votes t2
where t2.name = votes.name )
) t
Based on #d-shish solution, and since introduction (in MySQL 5.7) of ONLY_FULL_GROUP_BY, the GROUP BY statement must be placed in subquery like this :
SELECT v.`name`,
SUM(v.`choice` = 'A') as `votesA`,
SUM(v.`choice` = 'B') as `votesB`
FROM `votes` v
WHERE (
SELECT MAX(v2.`date`)
FROM `votes` v2
WHERE v2.`name` = v.`name`
GROUP BY v.`name` # << after
) = v.`date`
# GROUP BY v.`name` << before
Otherwise, it won't work anymore !
I am looking for queries, using which I can analyze a general employee database. This is for Data Analysis.
Tried this for monthly employee trend
SELECT
dt.FullDateAlternateKey as 'Date'
, count(1) as ActiveCount
FROM DimDate dt
LEFT JOIN (SELECT 'Active' as 'EmpStatus', * FROM DimEmployee) emp
-- regular active employees
ON (dt.FullDateAlternateKey between emp.StartDate and ISNULL(emp.EndDate,'9999-12-31'))
WHERE
dt.FullDateAlternateKey = EOMONTH(dt.FullDateAlternateKey)
GROUP BY
dt.FullDateAlternateKey
ORDER BY
1;
also found CTE use for finding employee hierarchy
WITH DirectReports (ManagerID, EmployeeID, Title, DeptID, Level)
AS
(
-- Anchor member definition
SELECT e.ParentEmployeeKey, e.EmployeeKey, e.Title, e.DepartmentName,
0 AS Level
FROM DimEmployee AS e
WHERE e.ParentEmployeeKey IS NULL
UNION ALL
-- Recursive member definition
SELECT e.ParentEmployeeKey, e.EmployeeKey, e.Title, e.DepartmentName,
Level + 1
FROM DimEmployee AS e
INNER JOIN DirectReports AS d
ON e.ParentEmployeeKey = d.EmployeeID
)
-- Statement that executes the CTE
SELECT ManagerID, EmployeeID, Title, DeptID, Level
FROM DirectReports
WHERE DeptID = 'Information Services' OR Level = 0
also, some good queries to analyze the sales data
-- Show each sales average for Group, Country, and Region all in one query
SELECT DISTINCT
t.SalesTerritoryGroup
, t.SalesTerritoryCountry
, t.SalesTerritoryRegion
, AVG(s.SalesAmount) OVER(PARTITION BY t.SalesTerritoryGroup ) as 'GroupAvgSales'
, AVG(s.SalesAmount) OVER(PARTITION BY t.SalesTerritoryCountry ) as 'CountryAvgSales'
, AVG(s.SalesAmount) OVER(PARTITION BY t.SalesTerritoryRegion ) as 'RegionAvgSales'
FROM FactInternetSales s
JOIN DimSalesTerritory t ON
s.SalesTerritoryKey = t.SalesTerritoryKey
WHERE
YEAR(s.OrderDate) = 2013
ORDER BY
1,2,3
Use additional aggregations to understand more about product sales such as the distribution of sales etc..
SELECT
cat.EnglishProductCategoryName 'Category'
, sub.EnglishProductSubcategoryName 'SubCategory'
, count(1) 'Count' -- How many sales where there?
, sum(s.SalesAmount) 'Sales' -- How much sales did we have?
, avg(s.SalesAmount) 'Avg_SalesAmount' -- What was the Avg sale amount?
, min(s.SalesAmount) 'Min_SaleAmount' -- What was the Min sale amount?
, max(s.SalesAmount) 'Max_SaleAmount' -- What was the Max sale amount
FROM FactInternetSales s
LEFT JOIN DimProduct p ON s.ProductKey = p.ProductKey
LEFT JOIN DimProductSubcategory sub ON p.ProductSubcategoryKey = sub.ProductSubcategoryKey
LEFT JOIN DimProductCategory cat ON sub.ProductCategoryKey = cat.ProductCategoryKey
-- must use group by in order for aggregation to work properly
GROUP BY
cat.EnglishProductCategoryName -- column aliases aren't allowed
, sub.EnglishProductSubcategoryName
ORDER BY
cat.EnglishProductCategoryName
, sub.EnglishProductSubcategoryName
-- Calculate the customer acquisition funnel
SELECT
c.FirstName
, c.LastName
, c.DateFirstPurchase
, DATEDIFF(d,c.DateFirstPurchase,getdate()) as 'DaysSinceFirstPurchase' -- How long have they been a customer?
FROM DimCustomer c
ORDER BY 3 DESC
-- Calculate a Monthly average of customer tenure
SELECT
EOMONTH(c.DateFirstPurchase) as 'MonthOfFirstPurchase' -- What month did they become a customer?
, DATEDIFF(d,EOMONTH(c.DateFirstPurchase),getdate()) as 'DaysSinceFirstPurchase' -- How long have they been a customer?
, COUNT(1) as 'CustomerCount' -- How manY customers are there for this month?
FROM DimCustomer c
GROUP BY EOMONTH(c.DateFirstPurchase)
ORDER BY 2 DESC
-- Show the top product Sub Categories for each year
SELECT
count(DISTINCT s.SalesOrderNumber) 'OrderCount' -- use 1 instead of a field for faster performance
, RANK() OVER (PARTITION BY YEAR(s.OrderDate) ORDER BY sum(s.SalesAmount) DESC) 'SalesRank'
, sum(s.SalesAmount) 'TotalSales'
, cat.EnglishProductCategoryName 'Category'
, sub.EnglishProductSubcategoryName 'SubCategory'
, YEAR(s.OrderDate) 'Year'
FROM FactInternetSales s
INNER JOIN DimProduct p ON s.ProductKey = p.ProductKey
INNER JOIN DimProductSubcategory sub ON p.ProductSubcategoryKey = sub.ProductSubcategoryKey
INNER JOIN DimProductCategory cat ON sub.ProductCategoryKey = cat.ProductCategoryKey
-- must use group by in order for aggregation to work properly
GROUP BY
cat.EnglishProductCategoryName -- column aliases aren't allowed
, sub.EnglishProductSubcategoryName
, YEAR(s.OrderDate)
ORDER BY YEAR(s.OrderDate), SUM(s.SalesAmount) DESC;
-- first, create weekly sales totals
SELECT SUM(s.SalesAmount) 'WeeklySales'
, DATEPART(ww, s.OrderDate) as 'WeekNum'
FROM FactInternetSales s
WHERE YEAR(s.OrderDate) = 2013
GROUP BY
DATEPART(ww, s.OrderDate)
ORDER BY
DATEPART(ww, s.OrderDate) ASC
-- use that subquery as our source and calculate the moving average
SELECT
AVG(WeeklySales) OVER (ORDER BY WeekNum ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as AvgSales
, WeeklySales as 'TotalSales'
, WeekNum
FROM (
SELECT SUM(s.SalesAmount) 'WeeklySales'
, DATEPART(ww, s.OrderDate) as 'WeekNum'
FROM FactInternetSales s
WHERE YEAR(s.OrderDate) = 2013
GROUP BY
DATEPART(ww, s.OrderDate)
) AS s
GROUP BY
WeekNum, WeeklySales
ORDER BY
WeekNum ASC
-- Running Total
SELECT
SUM(MonthlySales) OVER (PARTITION BY SalesYear ORDER BY SalesMonth ROWS UNBOUNDED PRECEDING) as YTDSales
, MonthlySales as 'MonthlySales'
, SalesYear
, SalesMonth
FROM (
SELECT SUM(s.SalesAmount) 'MonthlySales'
, MONTH(s.OrderDate) as 'SalesMonth'
, year(s.OrderDate) as 'SalesYear'
FROM FactInternetSales s
GROUP BY
MONTH(s.OrderDate)
, year(s.OrderDate)
) AS s
GROUP BY
SalesMonth, SalesYear, MonthlySales
ORDER BY
SalesYear, SalesMonth ASC
-- Get Prev Year Sales
WITH MonthlySales (YearNum, MonthNum, Sales)
AS
(
SELECT d.CalendarYear, d.MonthNumberOfYear, SUM(s.SalesAmount)
FROM DimDate d
JOIN FactInternetSales s ON d.DateKey = s.OrderDateKey
GROUP BY d.CalendarYear, d.MonthNumberOfYear
)
-- Get Current Year and join to CTE for previous year
SELECT
d.CalendarYear
, d.MonthNumberOfYear
, ms.Sales PrevSales
, SUM(s.SalesAmount) CurrentSales
FROM DimDate d
JOIN FactInternetSales s ON d.DateKey = s.OrderDateKey
JOIN MonthlySales ms ON
d.CalendarYear-1 = ms.YearNum AND
d.MonthNumberOfYear = ms.MonthNum
GROUP BY
d.CalendarYear
, d.MonthNumberOfYear
, ms.Sales
ORDER BY
1 DESC, 2 DESC
-- Now calculate the % change Year over Year
WITH MonthlySales (YearNum, MonthNum, Sales)
AS
(
SELECT d.CalendarYear, d.MonthNumberOfYear, SUM(s.SalesAmount)
FROM DimDate d
JOIN FactInternetSales s ON d.DateKey = s.OrderDateKey
GROUP BY d.CalendarYear, d.MonthNumberOfYear
)
-- Get Current Year and join to CTE for previous year
SELECT
d.CalendarYear
, d.MonthNumberOfYear
, ms.Sales PrevSales
, SUM(s.SalesAmount) CurrentSales
, (SUM(s.SalesAmount) - ms.Sales) / SUM(s.SalesAmount) 'PctGrowth'
FROM DimDate d
JOIN FactInternetSales s ON d.DateKey = s.OrderDateKey
JOIN MonthlySales ms ON
d.CalendarYear-1 = ms.YearNum AND
d.MonthNumberOfYear = ms.MonthNum
GROUP BY
d.CalendarYear
, d.MonthNumberOfYear
, ms.Sales
ORDER BY
1 DESC, 2 DESC
I have a table orders like this:
customer_id order_date
10 2012-01-01
11 2012-01-02
10 2012-01-02
12 2012-01-03
11 2012-01-04
12 2012-02-01
11 2012-02-04
13 2012-02-05
14 2012-02-06
How can I get a cumulative average over time (per month) like this:
order date count orders count customers (customer_id)
2012-01 1 1 (12)
2012-01 2 2 (10,11)
2012-02 1 2 (13,14)
2012-02 2 2 (10,12
2012-02 3 2 (11)
showing how the number of customers vs. number of orders per customer develops over time.
The following query gives me the wanted information - but not over time. How can I iterate the query over time?
SELECT number_of_orders, count(*) as amount FROM (
SELECT o.customer_id, count(*) as number_of_orders
FROM orders o
GROUP BY o.customer_id) as t1
GROUP BY number_of_orders
Update:
have now build the following PHP code to generate what I need, wonder if that could be done using cumulative counts like on http://www.freeopenbook.com/mysqlcookbook/mysqlckbk-chp-12-sect-14.html
$year = 2011;
for ($cnt_months = 1; $cnt_months <= 12; $cnt_months++) {
$cnt_months_str = ($cnt_months < 10) ? '0'.$cnt_months : $cnt_months;
$raw_query = "SELECT number_of_orders, count(*) as amount
FROM (
SELECT
o.customer_id,
count(*) as number_of_orders
FROM orders o
where Date_Format( o.order_date, '%Y%m' ) >= " . $year . "01 and Date_Format( o.order_date, '%Y%m' ) <= " . $year . $cnt_months_str . "
GROUP BY o.customer_id) as t1
GROUP BY number_of_orders";
$query = db_query($raw_query);
while ($row = db_fetch_array($query)) {
$data[$cnt_months_str][$row['number_of_orders']] = array($row['number_of_orders'], (int)$row['amount']);
}
}
A good starting point is
SELECT
order_date,
COUNT(*) AS distinctOrders,
COUNT(DISTINCT customer_id) AS distinctCustomers,
GROUP_CONCAT(DISTINCT customer_id ASC) AS customerIDs
FROM orders
GROUP BY order_date ASC
This will give you the order_date, the number of orders on that date, the number of customers on that date, and the list of customer ids on that date.
Just looking at a way to tally up on a month by month basis. So taking this forward I've used a subquery to tally up as it goes
SELECT
ordersPerDate.*,
IF(
MONTH(ordersPerDate.order_date)=#thisMonth,
#runningTotal := #runningTotal+ordersPerDate.distinctOrders,
#runningTotal := 0
) AS ordersInThisMonth,
#thisMonth := MONTH(ordersPerDate.order_date)
FROM
(
SELECT
#thisMonth := 0,
#runningTotal := 0
) AS variableInit,
(
SELECT
order_date,
COUNT(*) AS distinctOrders,
COUNT(DISTINCT customer_id) AS distinctCustomers,
GROUP_CONCAT(DISTINCT customer_id ASC) AS customerIDs
FROM orders
GROUP BY order_date ASC
) AS ordersPerDate
And finally to clean it up, wrapped it in yet another subquery just to return the rows desired rather than the internal variables
Grouping on individual days
SELECT
collatedData.order_date,
collatedData.ordersInThisMonth AS count_orders,
collatedData.distinctCustomers AS count_customers,
collatedData.customerIDs AS customer_ids
FROM (
SELECT
ordersPerDate.*,
IF(
MONTH(ordersPerDate.order_date)=#thisMonth,
#runningTotal := #runningTotal+ordersPerDate.distinctOrders,
#runningTotal := 0
) AS ordersInThisMonth,
#thisMonth := MONTH(ordersPerDate.order_date)
FROM
(
SELECT
#thisMonth := 0,
#runningTotal := 0
) AS variableInit,
(
SELECT
order_date,
COUNT(*) AS distinctOrders,
COUNT(DISTINCT customer_id) AS distinctCustomers,
GROUP_CONCAT(DISTINCT customer_id) AS customerIDs
FROM orders
GROUP BY order_date ASC
) AS ordersPerDate
) AS collatedData
And now finally, following additional information from the OP, the end product
Grouping on calendar months
// Top level will sanitise the output
SELECT
collatedData.orderYear,
collatedData.orderMonth,
collatedData.distinctOrders,
collatedData.ordersInThisMonth AS count_orders,
collatedData.distinctCustomers AS count_customers,
collatedData.customerIDs AS customer_ids
FROM (
// This level up will iterate through calculating running totals
SELECT
ordersPerDate.*,
IF(
(ordersPerDate.orderYear,ordersPerDate.orderMonth) = (#thisYear,#thisMonth),
#runningTotal := #runningTotal+ordersPerDate.distinctOrders*ordersPerDate.distinctCustomers,
#runningTotal := 0
) AS ordersInThisMonth,
#thisMonth := ordersPerDate.orderMonth,
#thisYear := ordersPerDate.orderYear
FROM
(
SELECT
#thisMonth := 0,
#thisYear := 0,
#runningTotal := 0
) AS variableInit,
(
// Next level up will collate this to get per year, month, and per number of orders
SELECT
ordersPerDatePerUser.orderYear,
ordersPerDatePerUser.orderMonth,
ordersPerDatePerUser.distinctOrders,
COUNT(DISTINCT ordersPerDatePerUser.customer_id) AS distinctCustomers,
GROUP_CONCAT(ordersPerDatePerUser.customer_id) AS customerIDs
FROM (
// Inner query will get the number of orders for each year, month, and customer
SELECT
YEAR(order_date) AS orderYear,
MONTH(order_date) AS orderMonth,
customer_id,
COUNT(*) AS distinctOrders
FROM orders
GROUP BY orderYear ASC, orderMonth ASC, customer_id ASC
) AS ordersPerDatePerUser
GROUP BY
ordersPerDatePerUser.orderYear ASC,
ordersPerDatePerUser.orderMonth ASC,
ordersPerDatePerUser.distinctOrders DESC
) AS ordersPerDate
) AS collatedData
SELECT
substr(order_date,1,7) AS order_period,
count(*) AS number_of_orders,
count(DISTINCT orders.customer_id) AS number_of_customers,
GROUP_CONCAT(DISTINCT orders.customer_id) AS customers
FROM orders
GROUP BY substr(order_date,1,7)