sql sales data grouped by year in separate columns in postgresql - sql

I have two input tables:
analysis (an_id, an_name, an_cost, an_price, an_group)
orders (ord_id, ord_datetime, ord_an) # orders of analysis (sales)
For every analysis_id I need to show the amount of orders for years 2020 and 2019.
Expected output:
an
year2019
year2020
1
32
41
2
29
28
3
42
37
4
26
35
5
32
34
logic in my query:
step1 - get orders table data only for years 2019,2020 - use CTE and extract()
step2 - aggregate by year
My query:
WITH helper AS (
SELECT an_id,
ord_id,
EXTRACT(year from ord_datetime) as year
FROM analysis a
INNER JOIN orders o ON o.ord_an = a.an_id
WHERE EXTRACT(year FROM ord_datetime) in (2019.0,2020.0)
)
SELECT an_id,
CASE WHEN year = 2019.0 THEN COUNT(ord_id) ELSE 0 END AS year2019,
CASE WHEN year = 2020.0 THEN COUNT(ord_id) ELSE 0 END AS year2020
FROM helper
GROUP BY year, an_id
ORDER BY an_id
The current output of my query:
an_id
year2019
year2020
1
32
0
1
0
41
2
29
0
2
0
28
3
42
0

The issue in your query may be inside your GROUP BY clause, because you're grouping on the year too. Instead consider the following approach, where you invert the position of the COUNT aggregate function and the CASE statement:
SELECT a.an_id,
COUNT(CASE WHEN EXTRACT(year FROM o.ord_datetime) = 2019 THEN o.ord_id END) AS year2019,
COUNT(CASE WHEN EXTRACT(year FROM o.ord_datetime) = 2020 THEN o.ord_id END) AS year2020
FROM analysis a
INNER JOIN orders o
ON o.ord_an = a.an_id
GROUP BY a.an_id
Note: the ELSE part of your CASE statement is not necessary, as long as values will be defaulted to NULL (and not counted in by the COUNT).

Related

Display 12 months of data from the past 5 years

I am currently creating a script that will pull 5 years of invoice data and will summarize the invoice amounts by month of that year for a specific customer. Example
Year jan feb mar
2011 800 900 700
2012 700 800 900, and so forth
I am having issues getting my output to be like this though. My current code
select MAX(cust) as customer,year(invoicedate) as y, month(invoicedate) as m, sum(amount) as summary
from #tquery
group by year(dinvoice), month(dinvoice)
having MAX(ccustno) ='WILLAMETTE'
order by y asc,m asc
select * from #tquery
gives me this. which i just need to find a way to reformat it.
customer year month amount
WILLAMETTE 2012 11 500
WILLAMETTE 2012 12 600
WILLAMETTE 2013 1 600
No need to go through a Pivot. It is only 12 columns. A conditional aggregation would be more efficient
Select Customer = cust
,Year = year(invoicedate)
,Jan = sum(case when month(invoicedate) = 1 then amount else 0 end)
,Feb = sum(case when month(invoicedate) = 2 then amount else 0 end)
...
,Dec = sum(case when month(invoicedate) =12 then amount else 0 end)
From #tquery
Group by ccustno,year(dinvoice)
Order By 1,2
You must using PIVOT to reformat rows to column
select customer
,y
,"1","2","3","4","5","6","7","8","9","10","11","12"
from (select cust as customer,year(invoicedate) as y, month(invoicedate) as m,amount
from #tquery
where ccustno ='WILLAMETTE'
)
t
pivot (sum (amount) for m in ("1","2","3","4","5","6","7","8","9","10","11","12")) p
order by y
;

more than one AVG column with diffrent conditions

I have a table as follows:
id year value
1 2012 10
2 2013 7
3 2013 7
4 2014 8
5 2014 10
6 2015 6
7 2011 12
I need to write a query which gives the AVG value of the last 4 years from today. Meaning that if today is 2016 then the AVG is on 2015,2014,2013.
Basicly this could be done with 3 queries:
Select avg(value) as a
from tab
where year=2015
and
Select avg(value) as b
from tab
where year=2014
and
Select avg(value) as c
from tab
where year=2013
The results based on the given values should be:
2013 7
2014 9
2015 6
Since all of them is on the same table... How can I do that in one query (postgresql)?
it should be without a WHERE.
Something like:
Select avg(with condition) as a, avg(with condition) as b, avg(with condition) as c
from tab
You can group by year and constrict to the years you want in your where clause
select avg(value), year
from tab
where year in (2013,2014,2015)
group by year
The query above will give you 3 separate rows. If you prefer a single row then you can use conditional aggregation instead of a group by
select
avg(case when year = 2013 then value end) as avg_2013,
avg(case when year = 2014 then value end) as avg_2014,
avg(case when year = 2015 then value end) as avg_2015,
from tab
where year in (2013,2014,2015)
select
avg(case when year = date_part('year', NOW()) then value end) as avg_2016,
avg(case when year = ((date_part('year', NOW())) - 1 ) then value end) as avg_2015,
avg(case when year = ((date_part('year', NOW())) - 2 ) then value end) as avg_2014,
avg(case when year = ((date_part('year', NOW())) - 3 ) then value end) as avg_2013
from tab

How to subtract result of 2 queries grouped by a field

I have a table in this form:
id year type amount
1 2015 in 10
2 2015 out 5
3 2016 in 20
4 2016 out 1
...
The followin query will give me the sum of the amount of type = 'in' grouped by year:
SELECT year, sum(amount)
FROM table
WHERE type = in
GROUP BY year
How am I going to get the following result?
year sum(in) sum(out) "in-out"
2015 10 5 5
2016 20 1 19
sum(in) is the sum of the 'amount' where type='in'.
Use a CASE statement to handle the values of type.
SELECT year,
SUM(CASE WHEN type = 'in' THEN amount ELSE 0 END) AS sum_in,
SUM(CASE WHEN type = 'out' THEN amount ELSE 0 END) AS sum_out,
SUM(CASE WHEN type = 'in' THEN amount ELSE -amount END) AS in_out
FROM table
GROUP BY year;

Return zero value for all the month in series with count zero

This is my query:
SELECT STAFF.stf_first_name + '' + STAFF.stf_last_name As Name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date) As Month,
SUM(case when RES_HAB_DATA.reshabdata_duration > 0
then (RES_HAB_DATA.reshabdata_duration/15) else 0 end) As ServiceDeliveryTime,
MONTH(RES_HAB_DATA.reshabdata_data_date) As MonthNumber
FROM RES_HAB_DATA
JOIN RES_HAB ON RES_HAB_DATA.reshab_id = RES_HAB.reshab_id
JOIN STAFF ON RES_HAB_DATA.staff_id = STAFF.stf_id
WHERE RES_HAB.serv_id = 30
AND RES_HAB_DATA.reshabdata_data_date >= '1/1/2015'
GROUP BY STAFF.stf_last_name,
STAFF.stf_first_name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date),
MONTH(RES_HAB_DATA.reshabdata_data_date)
ORDER BY MonthNumber
Which produces result set as:
Name Month ServiceDeliveryTime MonthNumber
----------------------------------------------------------------------------
mb January 52 1
MikeCasey January 10 1
MikeCasey February 4 2
PrecisionCareSupport February 0 2
MikeCasey March 4 3
PrecisionCareSupport March 0 3
MikeCasey April 8 4
PrecisionCareSupport April 0 4
MikeCasey May 16 5
MikeCasey July 4 7
PrecisionCareSupport July 1 7
PrecisionCareSupport August 0 8
MikeCasey September 10 9
MikeCasey October 12 10
I am generating a chart and would like to generate series for that chart but the series should be formed in a way that each series label must have all the tick values(zero if missing respective month). In Simple words,I want resultset as:
Name Month ServiceDeliveryTime MonthNumber
----------------------------------------------------------------------------
mb January 52 1
mb February 0 2
mb March 0 3
mb April 0 4
- - 0 5
Upto December then series will continue for Client MikeCasey upto December and so on...for all the series Labels.If any of the tick is missing for that client there will be value zero for that month.
How Can I produce this result set ? I want some uniform solution because there can be number of such queries for different charts.
Mr Shaw, try this
;WITH
(SELECT STAFF.stf_first_name + '' + STAFF.stf_last_name As Name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date) As Month,
SUM(case when RES_HAB_DATA.reshabdata_duration > 0
then (RES_HAB_DATA.reshabdata_duration/15) else 0 end) As ServiceDeliveryTime,
MONTH(RES_HAB_DATA.reshabdata_data_date) As MonthNumber
FROM RES_HAB_DATA
JOIN RES_HAB ON RES_HAB_DATA.reshab_id = RES_HAB.reshab_id
JOIN STAFF ON RES_HAB_DATA.staff_id = STAFF.stf_id
WHERE RES_HAB.serv_id = 30
AND RES_HAB_DATA.reshabdata_data_date >= '1/1/2015'
GROUP BY STAFF.stf_last_name,
STAFF.stf_first_name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date),
MONTH(RES_HAB_DATA.reshabdata_data_date)
) AS mytable
SELECT
myTableName.Name
,mytableMonth.Month
,ISNULL(mytable.ServiceDeliveryTime,0)
,mutableMonth.MonthNumber
FROM
(SELECT DISTINCT Name from mytable) mytableName
CROSS JOIN (SELECT DISTINCT Month,MonthNumber FROM mytable) mytableMonth
LEFT INNER JOIN mytable ON mytableName.Name = mytable.Name AND mytableMonth.Month = mytable.Month AND mytableMonthNumber = mytable.MonthNumber
ORDER BY mytableName.Name, mytableMonth.MonthNumber
I have taken all distinct months and names from your data and done a cross join.
;WITH mytable(Name,Month,ServiceDeliveryTime,MonthNumber) AS
(
SELECT STAFF.stf_first_name + '' + STAFF.stf_last_name As Name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date) As Month,
SUM(case when RES_HAB_DATA.reshabdata_duration > 0
then (RES_HAB_DATA.reshabdata_duration/15) else 0 end) As ServiceDeliveryTime,
MONTH(RES_HAB_DATA.reshabdata_data_date) As MonthNumber
FROM RES_HAB_DATA
JOIN RES_HAB ON RES_HAB_DATA.reshab_id = RES_HAB.reshab_id
JOIN STAFF ON RES_HAB_DATA.staff_id = STAFF.stf_id
WHERE RES_HAB.serv_id = 30
AND RES_HAB_DATA.reshabdata_data_date >= '1/1/2015'
GROUP BY STAFF.stf_last_name,
STAFF.stf_first_name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date),
MONTH(RES_HAB_DATA.reshabdata_data_date)
)
SELECT
myTableName.Name
,mytableMonth.Month_Name
,ISNULL(mytable.ServiceDeliveryTime,0) as ServiceDeliveryTime
,mytableMonth.id
FROM
(SELECT DISTINCT Name from mytable) mytableName
CROSS JOIN (SELECT DISTINCT Month_Name,id FROM MyMonths) mytableMonth
LEFT JOIN mytable ON mytableName.Name = mytable.Name AND mytableMonth.Month_Name = mytable.Month AND mytable.MonthNumber = mytable.MonthNumber
ORDER BY mytableName.Name, mytableMonth.id
MyMonths table is already created table with id as MonthNumber and Month_Name as Month.
Cheers!

SQL - Comparing and Grouping Data on multiple rows

I'm trying to query my database to find which products sold less in October than in either November or December.
I thought something like below would do it but I have a feeling the sub query will be returning the mininimum quantity for the whole database rather than for the specific product.
There must be some way of doing this using GROUP BY but I cant figure it out.
SELECT Category, Product
FROM Sales
WHERE SaleQuantity < (SELECT MIN(SaleQuantity)
FROM Sales
WHERE MonthNumber > 10)
AND MonthNumber = 10
Data looks like:
Category Product MonthNumber SaleQuantity
---------- ----------- ------------- -----------
11 14 10 210
11 14 11 200
11 14 12 390
15 12 10 55
15 12 11 24
17 12 12 129
19 10 10 12
Thanks.
try something like this
SELECT Category,
Product,
SUM( s.SaleQuantity ) AS saleOcotber,
SUM( ISNULL( son.SaleQuantity, 0 ) ) AS saleNovember,
SUM( ISNULL( sod.SaleQuantity, 0 ) ) AS saleDecember
FROM Sales s
LEFT OUTER JOIN Sales son ON son.Category = s.Category
AND son.Product = s.Product
AND son.MonthNumber = 11
LEFT OUTER JOIN Sales sod ON sod.Category = s.Category
AND sod.Product = s.Product
AND sod.MonthNumber = 11
WHERE s.MonthNumber = 10
GROUP BY Category,Product
WHERE SUM( s.SaleQuantity ) < SUM( ISNULL( son.SaleQuantity, 0 ) )
OR SUM( s.SaleQuantity ) < SUM( ISNULL( sod.SaleQuantity, 0 ) )
I have no tested this select but i think it will do the job if there is something not clear
please ask
Best Regards,
Iordan
PS. I presume you are using some version of MSSQL if not try to rewrite it by yourself int SQL you are using
Your table already appears to be summarised by Category, Product and MonthNumber, for SalesQuantity. If so, try this:
select distinct Category, Product
from Sales s11_12
where MonthNumber in (11,12) and
not exists (select null
from Sales s10
where s10.Category = s11_12.Category and
s10.Product = s11_12.Product and
s10.SalesQuantity >= s11_12.SalesQuantity)