SQL: Aggregate different months from same table - sql

Lets say I have a table with sales at dates for products. Alas, I cant format a table in here, therefore as code:
table1:
Product|Date|Sales
-------|----|-----
ProdA |1.1.|100
ProdB |1.1.| 50
ProdC |1.1.| 75
ProdA |2.1.|110
ProdB |2.1.| 60
ProdC |2.1.| 60
.... |... |...
I need a new table with the sales sum for each month:
Product| Jan| Feb|...
-------|----|----|...
ProdA |1234|1400|...
ProdB | 234| 400|...
ProdC | 524| 640|...
... |... |... |...
I try to use an SQL-query with CASE. Text in [] is a abbreviation, the real expression is a to_char(to_month(..)) construct that works.
SELECT
Product,
CASE WHEN [date == 1] THEN SUM(Sales) END AS Jan,
CASE WHEN [date == 2] THEN SUM(Sales) END AS Feb,
CASE WHEN [date == 3] THEN SUM(Sales) END AS Mar,
...
FROM
table1
GROUP BY
Product
I got an 00979. 00000 - "not a GROUP BY expression" error.
I know I could work around by building tables for every month and add some again together, but that is low-performance. I also want to understand, why the construct does not work ?
PS:
- [Edit1]: Its an Oracle DB

try to give agreegation before case statement sum(case when end)
SELECT
Product,
sum(CASE WHEN date = 1 THEN Sales END) AS Jan,
SUM(CASE WHEN date = 2 THEN Sales END) AS Feb,
SUM(CASE WHEN date = 3 THEN Sales END) AS Mar,
...
FROM
table1
GROUP BY
Product

Related

dynamic grouping by month in SQL

I am using CASE statements to group data into Month columns, like this:
SUM(CASE WHEN MONTH(date) = 1 THEN ROUND(value) END) as jan,
SUM(CASE WHEN MONTH(date) = 2 THEN ROUND(value) END) as feb,
SUM(CASE WHEN MONTH(date) = 3 THEN ROUND(value) END) as mar
Is it possible to NOT have to define the different CASE groupings?
I want to define the data range in the WHERE statement, and then have the report group by month, for whatever range I define. For example, maybe my report starts with July'20, and not Jan.
Is this possible in an SQL query?
Thanks
edit - example output:
+-------+-------+------+-------+-------+
| | July | Aug | Sep | etc |
+-------+-------+------+-------+-------+
| value | 435 € | 24 € | 234 € | 453 € |
+-------+-------+------+-------+-------+
edit - possible solution/workaround:
if I do the following, it can be considered "semi-dynamic". I still need to define the month "buckets", but they can be trigged by the starting date (the month({ d '2021-01-01' }) part can also later be replaced with a variable, so that is also fixed in the code.
SUM (CASE WHEN MONTH(date) = month({ d '2021-01-01' }) THEN value END) as month_1,
SUM (CASE WHEN MONTH(date) = month({ d '2021-01-01' })+1 THEN value END) as month_2,
SUM (CASE WHEN MONTH(date) = month({ d '2021-01-01' })+2 THEN value END) as month_3,
etc
the main downside is that I have to hard-code the number of month groupings. So i'd be happy to hear of a better solution!
You can create a dynamic sql statement to generate the result into a temporary table and then return the result from the temporary table. Here is an example:
declare sqlStr string;
sqlStr = 'SELECT 1 AS column1, 2 AS column2 INTO #temp FROM system.iota';
execute immediate sqlStr;
select * from #temp;
The logic to generate the required grouping is up to you. You will need to use the while loop to construct the statement text.

SQL Server 2008: How do you SUM based on two conditions?

If I work at a grocery store and need to make orders for inventory, we make orders multiple times a month rather than one large order.
Item
ETA
QTY
Apples
5/6/21
10
Apples
6/12/21
15
Apples
6/30/21
10
Bananas
6/12/21
15
Bananas
7/5/21
20
Cereal
5/15/21
10
Cereal
5/30/21
50
Cereal
7/15/21
20
Is there a way to create a table that sums the QTY, if the item is the same and if the ETA month is the same to know how much of each Item is expected to arrive in a given month?
Ideally, the result I'm looking for is something that looks like this
Item
May
June
July
Apples
10
25
0
Bananas
0
15
20
Cereal
60
0
20
I would need the code to first check to see what month the item is expected to arrive in, and then if there are more than one lines that have the same item and ETA month, SUM the QTY.
I have tried doing CASE WHEN statements but always end up with syntax errors
SELECT
CASE WHEN ETA BETWEEN '2021-05-01' AND '2021-05-31'
AND WHERE Item IN
(SELECT Item
FROM ['Inventory']
GROUP BY Item HAVING COUNT(*)>1)
THEN SUM(QTY)
END AS MAY_QTY
FROM [dbo].['Inventory'];
You just use conditional aggregation:
select item,
sum(case when month(eta) = 5 then qty else 0 end) as may,
sum(case when month(eta) = 6 then qty else 0 end) as jun,
sum(case when month(eta) = 7 then qty else 0 end) as jul
from inventory i
group by item;
I would caution you that using months without a year may lead to problems. That is also true of using unsupported software -- SQL Server 2008 is no longer supported.
First you should group the data by item and month, and then use pivot to convert rows to columns.
select
item,
isnull(may,0) as May,
isnull(june,0) as June,
isnull(july,0) as July
from
(
select item, datename(month, ETA) as _month, sum(qty) as qty
from Inventory
group by item, datename(month, ETA)
) d
pivot
(
sum(qty)
for _month in (may, june, july)
) piv;

SQL Query two values for each record

I'm trying to query a customers table to get the total number of accounts per rep grouped by whether they were created this year or before.
CUSTOMER NAME
ACCOUNT REP
DATE CREATED
The query I'm trying to return would look like.
REP | NEW_ACCOUNTS | OLD_ACCOUNTS | TOTAL
-----------------------------------------
Tom | 100 | 12 | 112
Ted | 15 | 1 | 16
The query I've written looks as follows.
SELECT REP, CASE WHEN YEAR(GETDATE()) > YEAR(DATE_CREATED) THEN 1 ELSE 0 END AS ThisYear
FROM CUSTOMERS
GROUP BY REP, DATE_CREATED
Unfortunately, this is giving me
REP | ThisYear
-----------------------------------------
Tom | 1
Ted | 0
Tom | 0
Ted | 1
Ted | 1
I think you want conditional aggregation:
SELECT REP,
SUM(CASE WHEN YEAR(GETDATE()) = YEAR(DATE_CREATED) THEN 1 ELSE 0 END) AS NEW_ACCOUNTS,
SUM(CASE WHEN YEAR(GETDATE()) > YEAR(DATE_CREATED) THEN 1 ELSE 0 END) AS OLD_ACCOUNTS,
COUNT(*) as TOTAL
FROM CUSTOMERS
GROUP BY REP;
This assumes that creation dates are not in the future -- a reasonable assumption.
If you want one row per REP, then the only column in the GROUP BY should be REP.
You can want conditional aggregation :
SELECT REP,
SUM(CASE WHEN YEAR(GETDATE()) = YEAR(DATE_CREATED) THEN 1 ELSE 0 END) AS NEW_ACCOUNTS,
SUM(CASE WHEN YEAR(GETDATE()) > YEAR(DATE_CREATED) THEN 1 ELSE 0 END) AS OLD_ACCOUNTS, COUNT(*) AS TOTAL
FROM CUSTOMERS
GROUP BY REP;

sql query to get difference of sum over two columns spread across two tables grouped by month [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 4 years ago.
Improve this question
I want to write a query to get difference of sum over two columns spread across two tables grouped by month.
Schema:
TableA
mass numeric,
weight numeric
sampleDt date
TableB
mass numeric,
weight numeric,
sampleDt date
Sample Data for Table A
100|200|2017-01-03
10 |20 |2017-01-05
200|400|2017-12-23
Sample Data for Table B
10 | 20 | 2017-01-20
10 | 20 | 2017-01-21
100 | 200 | 2017-12-12
2 | 4 | 2017-06-12
Expected Output
Month,Year |AMassTotal |AWeightTotal |BMassTotal |BWeightTotal |AMassTotal-BMassTotal
Jan,17 | 110 | 220 | 20 | 40 | 90
Jun,17 | 0 | 0 | 2 | 4 | -2
Dec,17 | 200 | 400 |100 |200 | 100
Use a full outer join and a group by:
select to_char(sampledt, 'yyyy-mm') as month_year,
coalesce(sum(a.mass),0) as a_mass_total,
coalesce(sum(a.weight),0) as a_weight_total,
coalesce(sum(b.mass),0) as b_mass_total,
coalesce(sum(b.weight),0) as b_weight_total,
coalesce(sum(a.mass),0) - coalesce(sum(b.mass),0) as mass_total_diff
from table_a a
full join table_b b using (sampledt)
group by to_char(sampledt, 'yyyy-mm');
If you want year and month in separate columns, you can use:
select extract(year from sampledt) as year,
extract(month from sampledt) as month,
coalesce(sum(a.mass),0) as a_mass_total,
coalesce(sum(a.weight),0) as a_weight_total,
coalesce(sum(b.mass),0) as b_mass_total,
coalesce(sum(b.weight),0) as b_weight_total,
coalesce(sum(a.mass),0) - coalesce(sum(b.mass),0) as mass_total_diff
from table_a a
full join table_b b using (sampledt)
group by extract(year from sampledt), extract(month from sampledt)
order by 1,2;
Except for the to_char() function the above is ANSI standard SQL.
Online example: http://rextester.com/YAN23912
For MySQL
SELECT TB.`Month,Year`,
IFNULL(AMassTotal,0),
IFNULL(AWeightTotal,0),
IFNULL(BMassTotal,0),
IFNULL(BWeightTotal,0),
(IFNULL(AMassTotal,0)-IFNULL(BMassTotal,0)) AS 'AMassTotal-BMassTotal'
FROM
(
SELECT DATE_FORMAT(sampleDt,'%M,%Y') AS `Month,Year`,
SUM(CASE WHEN mass IS NULL THEN 0 ELSE mass END) AS AMassTotal,
SUM(CASE WHEN weight IS NULL THEN 0 ELSE weight END) AS AWeightTotal
From TableA
GROUP BY DATE_FORMAT(sampleDt,'%M,%Y')
) AS TA
RIGHT JOIN
(
SELECT DATE_FORMAT(sampleDt,'%M,%Y') AS `Month,Year`,
SUM(CASE WHEN mass IS NULL THEN 0 ELSE mass END) AS BMassTotal,
SUM(CASE WHEN weight IS NULL THEN 0 ELSE weight END) AS BWeightTotal
FROM TableB
GROUP BY DATE_FORMAT(sampleDt,'%M,%Y')
) AS TB
ON TA.`Month,Year`=TB.`Month,Year`
Live Demo
http://sqlfiddle.com/#!9/2a6e24/22
Try this:
This works in SQL Server:
SELECT CONVERT(CHAR(3), sampleDt, 0)+','+CAST(DATEPART(YEAR,sampleDt) AS VARCHAR) [Month,Year]
,ISNULL(SUM(CASE WHEN D.Tab=1 THEN mass END),0) AMassTotal
,ISNULL(SUM(CASE WHEN D.Tab=1 THEN weight END),0) AWeightTotal
,ISNULL(SUM(CASE WHEN D.Tab=2 THEN mass END),0) BMassTotal
,ISNULL(SUM(CASE WHEN D.Tab=2 THEN weight END),0) BWeightTotal
,ISNULL(SUM(CASE WHEN D.Tab=1 THEN mass END)-SUM(CASE WHEN D.Tab=2 THEN mass END),0) [AMassTotal-BMassTotal]
FROM(
SELECT 1 AS Tab,* FROM TableA
UNION ALL
SELECT 2,* FROM TableB
)D
GROUP BY LEFT(DATEPART(MONTH,sampleDt),3)+DATEPART(YEAR,sampleDt)
select CONVERT(CHAR(3), GETDATE(), 0)
SQL Fiddle Demo: SQL Fiddle Demo

SQL statement to generate a table of totals by month

I am very unfamiliar with advanced SQL.
Lets say I have the following table (in Access - using Jet 4.0 OLEDBAdapter in VB.NET).
Table - Items
ID Date Account Amount
----- ------ ------- ------
1 1/1/2013 Cash 10.00
2 2/1/2013 Cash 20.00
3 1/2/2013 Cash 30.00
4 2/2/2013 Cash 40.00
5 1/1/2013 Card 50.00
6 2/1/2013 Card 60.00
7 1/2/2013 Card 70.00
8 2/2/2013 Card 80.00
And I want to generate the following - totals for each account per month
Table - Totals
Account Jan Feb
----- ----- ------
Cash 30.00 70.00
Card 110.00 150.00
Is this possible using one SQL statement. I can do it in two but it is very slow.
Edit - the closest I have got is this - but it doesn't generate columns
SELECT accFrom, Sum(amount)
FROM Items
WHERE Year(idate) = '2012'
GROUP BY Month(idate), accFrom
Using your sample data, this is the output I got from the query below with Access 2010.
Account 2013-01 2013-02
------- ------- -------
Card $120.00 $140.00
Cash $40.00 $60.00
My totals don't match your expected output. I suspect your date values were d-m-yyyy format, but my US locale interpreted them as m-d-yyyy. It's better to present dates in yyyy-m-d format to avoid that confusion.
Anyway this query formats the dates as yyyy-mm, and then pivots to generate the columns for each year-month combination. So it will accommodate a growing date range without requiring you to modify the query. And, as the date range grows, you could eventually add a WHERE clause to limit to columns to a convenient subset.
TRANSFORM Sum(i.Amount) AS SumOfAmount
SELECT i.Account
FROM Items AS i
GROUP BY i.Account
PIVOT Format(i.Date,'yyyy-mm');
Since there are exactly 12 months in a year, you do not need to pivot; just calculate the sum for each month:
SELECT Account,
Sum(IIF(Month(Date)=01, Amount, 0)) AS Jan,
Sum(IIF(Month(Date)=02, Amount, 0)) AS Feb,
Sum(IIF(Month(Date)=03, Amount, 0)) AS Mar,
Sum(IIF(Month(Date)=04, Amount, 0)) AS Apr,
Sum(IIF(Month(Date)=05, Amount, 0)) AS May,
Sum(IIF(Month(Date)=06, Amount, 0)) AS Jun,
Sum(IIF(Month(Date)=07, Amount, 0)) AS Jul,
Sum(IIF(Month(Date)=08, Amount, 0)) AS Aug,
Sum(IIF(Month(Date)=09, Amount, 0)) AS Sep,
Sum(IIF(Month(Date)=10, Amount, 0)) AS Oct,
Sum(IIF(Month(Date)=11, Amount, 0)) AS Nov,
Sum(IIF(Month(Date)=12, Amount, 0)) AS "Dec"
FROM Items
WHERE Year(Date) = 2013
GROUP BY Account
Goose is right, you'll need to pivot on your Date column and use SUM() as the aggregate.
The syntax will look something similar to:
SELECT account, [1/1/2013] as jan, [2/1/2013] as feb, ... -- each month you want to select
FROM
(
SELECT date, account, amount FROM items
)
PIVOT
(
SUM(amount) FOR date IN
(
[1/1/2013], [2/1/2013], ... -- each date you want to have its own column
)
) AS pvt