Creating averages and detecting increases higher than x% in SQL - sql

I want to create the following in SQL Server 2012: (I've found that the best way to explain it is with tables).
I have the date of purchase, the customer id and the price the customer paid in a table like this:
DateOnly Customer Price
2012/01/01 1 50
2012/01/01 2 60
2012/01/01 3 80
2012/01/02 4 40
2012/01/02 5 30
2012/01/02 1 55
2012/01/03 6 80
2012/01/04 2 90
What I need to do then is to keep a register of the average price paid by a customer. Which would be as follows:
DateOnly Customer Price AveragePrice
2012/01/01 1 50 50
2012/01/01 2 60 60
2012/01/01 3 80 80
2012/01/02 4 40 40
2012/01/02 5 30 30
2012/01/02 1 55 52.5
2012/01/03 6 80 80
2012/01/04 2 90 75
And finally, I need to select the rows which have caused an increase higher than 10% in the averageprice paid by a customer.
In this case, the second order of customer 2 should be the only one to be selected, as it introduced an increase higher than 10% in the average price paid by this customer.
Hence, the resulting table should be as follows:
DateOnly Customer Price AveragePrice
2012/01/04 2 90 75
Thanks in advance for your help.

First CTE is to prepare your data = assign row_numbers to each customer's purchase, to be used in joins further.
Second CTE is recursive and it does all the work in process. First part is to get each customer's first purchase and recursive part joins on next purchase and calculates TotalPrice, AveragePrice and Increase.
At the end just select the rows with Increase more than 10%.
WITH CTE_Prep AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY DateOnly) RN
FROM Table1
)
,CTE_Calc AS
(
SELECT *, Price AS TotalPrice, CAST(Price AS DECIMAL(18,2)) AS AveragePrice, CAST (0 AS DECIMAL(18,2)) AS Increase
FROM CTE_Prep WHERE RN = 1
UNION ALL
SELECT p.*
, c.TotalPrice + p.Price AS TotalPrice
, CAST(CAST(c.TotalPrice + p.Price AS DECIMAL(18,2)) / p.RN AS DECIMAL(18,2)) AS AveragePrice
, CAST(CAST(CAST(c.TotalPrice + p.Price AS DECIMAL(18,2)) / p.RN AS DECIMAL(18,2)) / c.AveragePrice AS DECIMAL(18,2)) AS Increase
FROM CTE_Calc c
INNER JOIN CTE_Prep p ON c.RN + 1 = p.RN AND p.Customer = c.Customer
)
SELECT * FROM CTE_Calc
WHERE Increase > 1.10
SQLFiddle DEMO

Interesting problem.
You can get the average without the current purchase by subtracting the price on each row from the sum of all prices for the row. This observation -- in combination with window functions -- gives the information needed to get the rows you are looking for:
select *
from (select t.*,
avg(price) over (partition by customer) as avgprice,
sum(price) over (partition by customer) as sumprice,
count(price) over (partition by customer) as cntprice
from table1 t
) t
where (case when cntprice > 1
then (sumprice - price) / (cntprice - 1)
end) > avgprice*1.1;
Note the use of the case in the where clause. There is a potential divide by zero problem. SQL Server guarantees that the when part of the case will be evaluated before the then part (in the situation). So this is safe from that problem.

Related

Getting latest price of different products from control table

I have a control table, where Prices with Item number are tracked date wise.
id ItemNo Price Date
---------------------------
1 a001 100 1/1/2003
2 a001 105 1/2/2003
3 a001 110 1/3/2003
4 b100 50 1/1/2003
5 b100 55 1/2/2003
6 b100 60 1/3/2003
7 c501 35 1/1/2003
8 c501 38 1/2/2003
9 c501 42 1/3/2003
10 a001 95 1/1/2004
This is the query I am running.
SELECT pr.*
FROM prices pr
INNER JOIN
(
SELECT ItemNo, max(date) max_date
FROM prices
GROUP BY ItemNo
) p ON pr.ItemNo = p.ItemNo AND
pr.date = p.max_date
order by ItemNo ASC
I am getting below values
id ItemNo Price Date
------------------------------
10 a001 95 2004-01-01
6 b100 60 2003-01-03
9 c501 42 2003-01-03
Question is, is my query right or wrong? though I am getting my desired result.
Your query does what you want, and is a valid approach to solve your problem.
An alternative option would be to use a correlated subquery for filtering:
select p.*
from prices p
where p.date = (select max(p1.date) from prices where p1.itemno = p.itemno)
The upside of this query is that it can take advantage of an index on (itemno, date).
You can also use window functions:
select *
from (
select p.*, rank() over(partition by itemno order by date desc) rn
from prices p
) p
where rn = 1
I would recommend benchmarking the three options against your real data to assess which one performs better.

Need to calculate No of time customer visit the shop

I need to calculate the number of visit of a customer to a store. Along with the item purchased. In that case , customer visit store 3 times and purchased 5 item. Now when i count the number of visit, my output is 5.
Below is the query I was trying:
select
Receipt_no,
Customer,sales_item,
Amount,
sum(Amount) over (partition by customer) as total_sales,
count(Receipt_No) over (partition by customer) as No_of_Visit
from sales
left join customer where sales.Customer = customer.Customer
And my output is
Receipt_No Customer sales_item Amount total_sales No_of_Visit
5 C1 Item1 100 1200 5
5 C1 Item3 200 1200 5
5 C1 item4 200 1200 5
34 C1 item1 300 1200 5
35 C1 item2 400 1200 5
but i want the No_of_Visit as "3"
I think you want a distinct count of receipts. That could have been:
count(distinct receipt_no) over (partition by customer) as No_of_Visit
But unfortunately SQL Server does not support distinct in window functions. You can, however, emulate it with dense_rank():
dense_rank() over (partition by customer order by receipt_no)
+ dense_rank() over (partition by customer order by receipt_no desc)
- 1 as No_of_Visit

How to calculate +ve and -ve amounts on Totals in SQL?

The below Product table, Product ID - 100 as duplicated twice, and also there are negative profits are needs to Substract while calculating the Profit wise Total.
PID | Pname | Profit
100 AB 20
100 AB 20
101 BC 30
102 CD -10
103 DE -10
Expected Result: 30
Please provide the SQL query to get this result. Thanks in advance!!!
Is this what you want?
select sum(profit)
from (select distinct t.*
from t
) t
WITH CTE AS (
SELECT ROW_NUMBER() OVER (PARTITION BY PID ORDER BY PID ) AS rn,
PID,Pname,Profit FROM TableName
)
SELECT CAST(SUM(Profit) AS INT) AS Profit FROM CTE
WHERE rn=1
Note:- First you to get the DISTINCT Record then..use sum function...

how to write a query to get product ids contributing to top 80% sales?

suppose i have a product is and sales column
product id sales
1 1000
2 10000
3 50000
4 12000
5 8000
write an sql query to get all product ids that contribute to top 80 % of sales?
For this, you want a cumulative sum. Presumably, you want the top selling such products, so:
select p.*
from (select p.*,
sum(sales) over (order by sales desc) as running_sales,
sum(sales) over () as total_sales,
from products
) p
where running_sales - sales < 0.8 * total_sales;
This returns the product that reaches or first exceeds 80% of the total sales.

pgsql -Showing top 10 products's sales and other products as 'others' and its sum of sales

I have a table called "products" where it has 100 records with sales details. My requirement is so simple that I was not able to do it.
I need to show the top 10 product names with sales and other product names as "others" and its sales. so totally my o/p will be 11 rows. 11-th row should be others and sum of sales of all remaining products. Can anyone give me the logic?
O/p should be like this,
Name sales
------ -----
1 colgate 9000
2 pepsodent 8000
3 closeup 7000
4 brittal 6000
5 ariies 5000
6 babool 4000
7 imami 3000
8 nepolop 2500
9 lactoteeth 2000
10 menwhite 1500
11 Others 6000 (sum of sales of remaining 90 products)
here is my sql query,
select case when rank<11 then prod_cat else 'Others' END as prod_cat,
total_sales,ID,rank from (select ROW_NUMBER() over (order by (sum(i.grandtotal)) desc) as rank,pc.name as prod_cat,sum(i.grandtotal) as total_sales, pc.m_product_category_id as ID`enter code here`
from adempiere.c_invoice i join adempiere.c_invoiceline il on il.c_invoice_id=i.c_invoice_id join adempiere.m_product p on p.m_product_id=il.m_product_id join adempiere.m_product_category pc on pc.m_product_category_id=p.m_product_category_id
where extract(year from i.dateacct)=extract(year from now())
group by pc.m_product_category_id) innersql
order by total_sales desc
o/p what i got is,
prod_cat total_sales id rank
-------- ----------- --- ----
BSHIRT 4511697.63 460000015 1
BT-SHIRT 2725167.03 460000016 2
SHIRT 2630471.56 1000003 3
BJEAN 1793514.07 460000005 4
JEAN 1115402.90 1000004 5
GT-SHIRT 1079596.33 460000062 6
T SHIRT 446238.60 1000006 7
PANT 405189.00 1000005 8
GDRESS 396789.02 460000059 9
BTROUSER 393739.48 460000017 10
Others 164849.41 1000009 11
Others 156677.00 1000008 12
Others 146678.00 1000007 13
As #e4c5 suggests, use UNION:
select id, prod_cat, sum(total_sales) as total_sales
with
totals as (
select --pc.m_product_category_id as id,
pc.name as prod_cat,
sum(i.grandtotal) as total_sales,
ROW_NUMBER() over (order by sum(i.grandtotal) desc) as rank
from adempiere.c_invoice i
join adempiere.c_invoiceline il on (il.c_invoice_id=i.c_invoice_id)
join adempiere.m_product p on (p.m_product_id=il.m_product_id)
join adempiere.m_product_category pc on (pc.m_product_category_id=p.m_product_category_id)
where i.dateacct >= date_trunc('year', now()) and i.dateacct < date_trunc('year', now()) + interval '1' year
group by pc.m_product_category_id, pc.name
),
rankedothers as (
select prod_cat, total_sales, rank
from totals where rank <= 10
union
select 'Others', sum(total_sales), 11
from totals where rank > 10
)
select prod_cat, total_sales
from ranked_others
order by rank
Also, I recommend using sargable conditions like the one above, which is slightly more complicated than the one you implemented, but generally worth the extra effort.