SQL - max of sum revenue by month by country - sql

I'm trying to find the record revenue months by for each country. Below query provides me revenue by month for each country.
select d.calendar_year_month as 'Record_month',
c.country_name as 'country'
,sum(Net_qty*(unit_charge+unit_shipping_charge)) as 'Revenue'
from sensu_reporting.commercial_analysts.customer_science_transactions CST (nolock)
join Sensu.dbo.Country_D C (nolock) on cst.country_code = c.Country_Code
join sensu.dbo.Date_D d (nolock) on cst.Order_Date_Key = d.Date_Key
where cst.site_key in ('95')
and cst.order_date_key >= 20180101
group by d.calendar_year_month, c.country_name
I tried using:
select a.country,
a.record_month,
max(a.revenue) as 'Record_Revenue'
from(
select d.calendar_year_month as 'Record_month',
c.country_name as 'country'
,sum(Net_qty*(unit_charge+unit_shipping_charge)) as 'Revenue'
from sensu_reporting.commercial_analysts.customer_science_transactions CST (nolock)
join Sensu.dbo.Country_D C (nolock) on cst.country_code = c.Country_Code
join sensu.dbo.Date_D d (nolock) on cst.Order_Date_Key = d.Date_Key
where cst.site_key in ('95')
and cst.order_date_key >= 20180101
group by d.calendar_year_month, c.country_name)
a
group by country, record_month
However, this provides me the same data as the initial query. What am I doing wrong, and how do I amend my query such that it gives me only the month with the highest revenue per country?

Since I didn't know your table structure this is an simplified example with only one table (id, calendar_year_month, countrycode, revenue). I used a subquery to identify the max revenue.
select calendar_year_month, country, revenue
from reporting r
where revenue = (select max(revenue) from reporting r2 where r.country = r2.country )
group by calendar_year_month, country
And the result I got was like this
201802 NO 1500
201802 SE 3000
201803 DE 7000
201803 NO 1500
Notice there are two rows for NO.
I hope this is translatable to your table structure.

Is this what you want?
with r as (
select d.calendar_year_month as Record_month,
c.country_name as country,
sum(Net_qty*(unit_charge+unit_shipping_charge)) as Revenue
from sensu_reporting.commercial_analysts.customer_science_transactions CST join
Sensu.dbo.Country_D C
on cst.country_code = c.Country_Code join
sensu.dbo.Date_D d
on cst.Order_Date_Key = d.Date_Key
where cst.site_key in ('95') and cst.order_date_key >= 20180101
group by d.calendar_year_month, c.country_name
)
select r.*
from (select r.*,
row_number() over (partition by record_month order by revenue desc) as seqnum
from r
) r
where seqnum = 1;

Related

Put Condition to CASE expression (SQL)

Is there anyway that I can filter the customers whose total bets on in-play football is more than 50% of their total bets everywhere? This is what I have in mind.
There are 2 tables, where the sportsbook_bets contains all bets from all customers (a customer may place multiple bets).
Sample Data:
Customer table
customer_id
1
2
...
sportsbook_bet table
customer_id
sport_name
1
football
1
football
1
hockey
1
basketball
2
tennis
2
football
2
hockey
2
basketball
...
...
Based on the tables above, the query should return customer 1 since the total bets placed on football is at least 50% of the total bet everywhere; where customer 2 only have 25% (thus doesn't get queried).
SELECT c.customer_id
FROM customers c INNER JOIN sportsbook_bets s
ON c.customer_id=s.customer_id
WHERE 50 < ROUND((SUM(CASE WHEN s.sport_name = 'football' AND s.in_play_yn='Y' THEN 1 ELSE 0 END)/COUNT(s.bet_id))*100,0);
When performing a filter using an aggregate, need to use HAVING not WHERE.
Customers with >= 50% Bets on Football
SELECT c.customer_id
FROM customers c
INNER JOIN sportsbook_bets s
ON c.customer_id=s.customer_id
GROUP BY c.customer_id
HAVING COUNT(CASE WHEN s.sport_name = 'football' AND s.in_play_yn='Y' THEN 1 END)/1.0/COUNT(*) >= .50
If you want to see a row per bet for those the desired customers, could use this query instead:
WITH cte_CustomerBets AS (
SELECT
c.customer_id
,s.sports_name
/*Add any other columns you need as well*/
,COUNT(CASE WHEN s.sport_name = 'football' AND s.in_play_yn='Y' THEN 1 END) OVER (PARTITION BY CustomerID) AS TotalSportsBetsPerCustomer
,COUNT(*) OVER (PARTITION BY CustomerID) AS TotalBetsPerCustomer
FROM customers c
INNER JOIN sportsbook_bets s
ON c.customer_id=s.customer_id
GROUP BY c.customer_id
HAVING COUNT(CASE WHEN s.sport_name = 'football' AND s.in_play_yn='Y' THEN 1 END)/1.0/COUNT(*) >= .50
)
SELECT *
FROM cte_CustomerBets
WHERE TotalSportsBetsPerCustomer/1.0/TotalBetsPerCustomer >= .5
I'm going to assume that you need some other columns from your customer table, otherwise you don't need that table to get customer_id, as that column is already in sportsbook_bets table.
One solution is to use a subqueries:
select distinct
C.customer_id
From customers C
Join sportsbook_bets S On S.customer_id = C.customer_id
where (Select count(FB.bet_id) From sportsbook_bets FB where FB.customer_id = C.customer_id and FB.sport_name = 'football'and FB.in_play_yn = 'Y') >
((Select count(TB.bet_id) From sportsbook_bets TB where TB.customer_id = C.customer_id) / 50.0)
Other way that I like more it's to separate your calculations, for this you can use the APPLY operator:
Select distinct
C.customer_id
From customers C
Join sportsbook_bets S On S.customer_id = C.customer_id
Cross Apply
(
Select TotalBets = count(SB.bet_id)
From sportsbook_bets SB
where SB.customer_id = S.customer_id
) TB
Cross Apply
(
Select FootballBets = count(SB.bet_id)
From sportsbook_bets SB
where SB.customer_id = S.customer_id
and SB.sport_name = 'football'
and SB.in_play_yn = 'Y'
) FB
where FB.FootballBets > TB.TotalBets / 50.0
For readability, you can separate the two components -- total bets and in-play football bets -- into two queries. This may not perform as quickly as other solutions here, but it may be easier to understand and maintain.
WITH totalbets AS (
SELECT c.customer_id
, count(s.bet_id) as TotalBets
FROM customers c
INNER JOIN sportsbook_bets s ON c.customer_id = s.customer_id
GROUP BY c.customer_id
),
ipfbets AS (
SELECT customer_id
, count(bet_id) as IPFBets
FROM sportsbook_bets
WHERE sport_name = 'football'
and in_play_yn = 'Y'
GROUP BY customer_id
)
SELECT tb.customer_id
FROM totalbets tb
INNER JOIN ipfbets ipfb ON tb.customer_id = ipfb.customer_id
WHERE (1.0 * ipfb.IPFBets) / tb.TotalBets > 0.5

Joining 3 tables in SQL showing all 3 tables

I am a basic user of SQL but need to join 3 tables together to sow a) sales b) returns and c) profit
I currently have the following code
select * from (
select SUM(Return_Amount) , 'Return' as type, monthname(Return_Date) as month_
from returns
group by month_
union
select SUM(Order_Total_Cost) , 'Sales' as type, monthname(Order_Date) as month_
from sales
group by month_
union
select SUM(profit) as profit_ , 'Profit' as type, month_
from(
select sell_price-cost_price as profit , monthname(order_date) month_
from sales
join order_item
on order_item.order_No = sales.order_No
join returns
on returns.order_no = sales.order_No
join supplier
on supplier.Product_ID = order_item.Product_ID
) B group by month_
) A order by month_;
This is showing as below:
387 Return August
182 Sales August
867 Profit August
733 Return July
109 Sales July
646 Profit July
596 Return June
I want it to show with Return, Sales & Profit as separate columns instead of all types listed in one.
Any help would be greatly appreciated.
Thanks
Why not join them?
SELECT x.month_
, x.returns_
, y.sales_
, z.profit_
FROM
(SELECT SUM(Return_Amount) AS returns_
, monthname(Return_Date) AS month_
FROM RETURNS
GROUP BY month_) x
INNER JOIN
(SELECT SUM(Order_Total_Cost) AS sales_
, monthname(Order_Date) AS month_
FROM sales
GROUP BY month_) y ON x.month_ = y.month_
INNER JOIN
(SELECT SUM(profit) AS profit_
, month_
FROM
(SELECT sell_price-cost_price AS profit
, monthname(order_date) month_
FROM sales
INNER JOIN order_item ON order_item.order_No = sales.order_No
INNER JOIN RETURNS ON returns.order_no = sales.order_No
INNER JOIN supplier ON supplier.Product_ID = order_item.Product_ID) B
GROUP BY month_) z ON x.month_ = z.month_
You could turn the three unioned queries to subqueries and join them. This assumes that each subquery always produces one record per month.
Also: you probably want to join and month and year, in case your data spans over multiple years (which eventually happens in any live dataset). I would also recommend using numeric years and month (as returned by year() and month()), which would produce more efficient joins than month names.
select
r.month_name,
r.total_return_amount,
s.total_sales,
p.total_profit
from
(
select
year(return_date) yr,
month(return_date) mh,
monthname(return_date) month_name
sum(return_amount) total_return_amount,
from returns
group by
year(return_date),
month(return_date),
monthname(return_date)
) r
inner join (
select
year(order_date) yr,
month(order_date) mh,
sum(order_total_cost) total_sales
from sales
group by
year(order_date),
month(order_date)
) s on s.yr = r.yr and s.mh = r.mh
inner join (
select
year(order_date) yr,
month(order_date) mh,
sum(sell_price - cost_price) as total_profit ,
from sales
inner join order_item on order_item.order_no = sales.order_no
inner join returns on returns.order_no = sales.order_no
inner join supplier on supplier.product_id = order_item.product_id
group by
year(order_date),
month(order_date)
) p on p.yr = r.yr and p.mh = r.mh

SQL Server : query with incorrect SUM result

My SQL Server query is supposed to get a count of each customer's number of orders, and the SUM of their reward points. For most customers the result is accurate (most people only have one or two orders). For a few people, the result is wildly off.
Here's the original query:
SELECT
c.email,
c.lastlogindate,
c.custenabled,
c.maillist,
d.GroupName,
COUNT(o.orderid) AS orders,
SUM(r.points) AS total_points
FROM
((customers c
LEFT JOIN orders o ON (c.contactid = o.ocustomerid AND o.ostep = 'step 5')
)
LEFT JOIN discount_group d ON c.discount = d.id
)
LEFT JOIN
customer_rewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
GROUP BY
c.email, c.custenabled, c.maillist, c.lastlogindate, d.GroupName;
For one example, customerid 1234 has placed 21 orders, totaling 2724 points. This will report that he has placed 441 orders (21 * 21) valued at 57204 points (2724 * 21). The raw data is fine, but each order row is being duplicated by the amount of orders they placed (but not for most customers...)
If I change the query to this:
SELECT
o.orderid,
c.email,
COUNT(o.orderid) AS orders,
SUM(r.points) AS total_points
FROM
((customers c
INNER JOIN orders o ON (c.contactid = o.ocustomerid AND o.ostep = 'step 5')
)
)
INNER JOIN
customer_rewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
GROUP BY
c.email, o.orderid;
The aggregate functions are calculated properly, but it will display one result for each order placed. So it will show "Customer 1234/21 orders/2724 points", 21 times.
I did remove the 'discount_group' join in the second query, but that was just to make it easier to read and change. That hasn't had any effect on results.
Here is a solution using common table expressions to aggregate your results.
Note: this will not show customers that have 0 orders or 0 rewards points. If you would like to show these, change the INNER JOINs to LEFT JOINs
WITH cteOrders AS
(
SELECT o.ocustomerid, orderCount = count(*)
FROM orders o
WHERE o.ostep = 'step 5'
GROUP BY o.ocustomerid
)
, cteRewards as
(
SELECT cr.contactid, total_points = SUM(cr.points)
FROM customer_rewards cr
GROUP BY cr.contactid
)
SELECT
c.email,
o.orderCount as orders,
r.total_points
FROM
customers c
INNER JOIN cteOrders o ON c.contactid = o.ocustomerid
INNER JOIN cteRewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
;
Or using subqueries:
SELECT
c.email,
o.orderCount as orders,
r.total_points
FROM
customers c
INNER JOIN
(
SELECT o.ocustomerid, orderCount = count(*)
FROM orders o
WHERE o.ostep = 'step 5'
GROUP BY o.ocustomerid
) o ON c.contactid = o.ocustomerid
INNER JOIN
(
SELECT cr.contactid, total_points = SUM(cr.points)
FROM customer_rewards cr
GROUP BY cr.contactid
) r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
;

Identifying percentage in Fact Table

I am new in programming and could not find an answer.
I have following dimensions(tables) and fact table:
Customer: CustomerId, HomeRegion
Regions: RegionId, RegionName
MyTime: id, MyHour
Fact table: CustomerId, RegionId, TimeId, FactId
I must have report as: HomeRegion, Hour, RegionName, UserPercentage.
As shown in the example, only 3.67% people whose home region is A move to B at 9am and so on.
I should create simular one.
The problem is obtainig UserPercentage. Here is the code I did so far.
SELECT c.HomeRegion, mt.myhour as Time, r.RegionName as CurrentRegion,
(SELECT COUNT(*)
/*number of users who move from their home
region to CurrentRegion at specific time*/
)/COUNT(c.CustomerId)*100 as UserPercentage
FROM dbo.FactTable ft
inner join dbo.Customer c
ON ft.CustomerId = c.CustomerId
inner join dbo.myTime mt
ON ft.TimeId = mt.ID
inner join dbo.Regions r
ON ft.RegionId = r.RegionId
WHERE mt.myhour = '09'
GROUP BY c.HomeRegion, mt.myhour, r.RegionName
ORDER BY c.HomeRegion, r.RegionName
Using the analytical functions
* no need to select or groupby myHour constant
* assuming one Customer should be located in 1 region at once (if not - it would be much harder to select)
select HomeRegion, CurrentRegion,
count(*) / count(*) over () as overall_share,
count(*) / count(*) over (partition by HomeRegion) as homeregion_share,
from
(SELECT c.HomeRegion, r.RegionName as CurrentRegion, c.CustomerId as CUST
FROM dbo.FactTable ft
inner join dbo.Customer c
ON ft.CustomerId = c.CustomerId
inner join dbo.myTime mt
ON ft.TimeId = mt.ID
inner join dbo.Regions r
ON ft.RegionId = r.RegionId
WHERE mt.myhour = '09'
GROUP BY c.HomeRegion, r.RegionName, c.CustomerId) uni_users
GROUP by HomeRegion, CurrentRegion
Try something like this in your comment area.
SELECT (TMP1.Count*100)/COUNT(TMP2.CustomerId) AS 'Percentage'
FROM
(
SELECT COUNT(*) AS 'Count'
FROM dbo.FactTable ft
inner join dbo.Customer c ON ft.CustomerId = c.CustomerId
inner join dbo.Regions r ON ft.RegionId = r.RegionId
WHERE
r.RegionName IN ('A','B','C','D','E') AND
c.HomeRegion IN ('A','B','C','D','E')
) AS 'TMP1', dbo.Customer AS 'TMP2'

How to calculate a total of values from other tables in a column of the select statement

I have three tables:
CustOrder: id, CreateDate, Status
DenominationOrder: id, DenID, OrderID
Denomination: id, amount
I want to create a view based upon all these tables but there should be an additional column i.e. Total should be there which can calculate the sum of the amount of each order.
e.g.
order 1 total denominations 3, total amount = 250+250+250=750
order 2 total denominations 2, total amount = 250+250=500
Is it possible?
I try to guess your table relations (and data too, you did not provide any sample):
SELECT co.id,
COUNT(do.DenID) AS `Total denominations`,
SUM(d.amount) AS `Total amount`
FROM CustOrder co
INNER JOIN DenominationOrder do ON co.id = do.OrderId
INNER JOIN Denomination d ON do.DenId = d.id
GROUP BY co.id
Try this:
SELECT o.CreateDate, COUNT(o.id), SUM(d.amount) AS 'Total Amount'
FROM CustOrder o
INNER JOIN DenominationOrder do ON o.id = do.OrderID
INNER JOIN Denomination d ON do.DenId = d.id
GROUP BY o.CreateDate
DEMO
Another way to do this, by using CTE, like this:
;WITH CustomersTotalOrders
AS
(
SELECT o.id, SUM(d.amount) AS 'TotalAmount'
FROM CustOrder o
INNER JOIN DenominationOrder do ON o.id = do.OrderID
INNER JOIN Denomination d ON do.DenId = d.id
GROUP BY o.id
)
SELECT o.id, COUNT(ot.id) AS 'Orders Count', ot.TotalAmount
FROM CustOrder o
INNER JOIN CustomersTotalOrders ot on o.id = ot.id
INNER JOIN DenominationOrder do ON ot.id = do.OrderID
INNER JOIN Denomination d ON do.DenId = d.id
GROUP BY o.id, ot.TotalAmount
This will give you:
id | Orders Count | Total Amount
-------+---------------+-------------
1 3 750
2 2 500
DEMO using CTE