dynamic grouping by month in SQL - 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.

Related

SQL query: get total values for each month

I have a table that stores, number of fruits sold on each day. Stores number of items sold on particular date.
CREATE TABLE data
(
code VARCHAR2(50) NOT NULL,
amount NUMBER(5) NOT NULL,
DATE VARCHAR2(50) NOT NULL,
);
Sample data
code |amount| date
------+------+------------
aple | 1 | 01/01/2010
aple | 2 | 02/02/2010
orange| 3 | 03/03/2010
orange| 4 | 04/04/2010
I need to write a query, to list out, how many apple and orange sold for jan and february?
--total apple for jan
select sum(amount) from mg.drum d where date >='01/01/2010' and cdate < '01/02/2020' and code = 'aple';
--total apple for feb
select sum(amount) from mg.drum d where date >='01/02/2010' and cdate < '01/03/2020' and code = 'aple';
--total orange for jan
select sum(amount) from mg.drum d where date >='01/01/2010' and cdate < '01/02/2020' and code = 'orange';
--total orange for feb
select sum(amount) from mg.drum d where date >='01/02/2010' and cdate < '01/03/2020' and code = 'orange';
If I need to calculate for more months, more fruits, its tedious.is there a short query to write?
Can I combine at least for the months into 1 query? So 1 query to get total for each month for 1 fruit?
You can use conditional aggregation such as
SELECT TO_CHAR("date",'MM/YYYY') AS "Month/Year",
SUM( CASE WHEN code = 'apple' THEN amount END ) AS apple_sold,
SUM( CASE WHEN code = 'orange' THEN amount END ) AS orange_sold
FROM data
WHERE "date" BETWEEN date'2020-01-01' AND date'2020-02-29'
GROUP BY TO_CHAR("date",'MM/YYYY')
where date is a reserved keyword, cannot be a column name unless quoted.
Demo
select sum(amount), //date.month
from mg.drum
group by //date.month
//data.month Here you can give experssion which will return month number or name.
If you are dealing with months, then you should include the year as well. I would recommend:
SELECT TRUNC(date, 'MON') as yyyymm, code,
SUM(amount)
FROM t
GROUP BY TRUNC(date, 'MON'), code;
You can add a WHERE clause if you want only some dates or codes.
This will return a separate row for each row that has data. That is pretty close to the results from your four queries -- but this does not return 0 values.
select to_char(date_col,'MONTH') as month, code, sum(amount)
from mg.drum
group by to_char(date_col,'MONTH'), code

Postgres Crosstab query Dynamic pivot

Does any one know how to create the following crosstab in Postgres?
For example I have the following table:
Store Month Sales
A Mar-2020 100
A Feb-2020 200
B Mar-2020 400
B Feb-2020 500
A Jan-2020 400
C Apr-2020 600
I would like the query to return the following crosstab, the column headings should not be hardcoded values but reflect the values in "month" column from the first table:
Store Jan-2020 Feb-2020 Mar-2020 Apr-2020
A 400 200 100 -
B - 500 400 -
C - - - 600
Is this possible?
Postgres does have a crosstab function, but I think using the built in filtering functionality is simple in this case:
select store,
sum(sales) filter (where month = 'Jan-2020') as Jan_2020,
sum(sales) filter (where month = 'Feb-2020') as Feb_2020,
sum(sales) filter (where month = 'Mar-2020') as Mar_2020,
sum(sales) filter (where month = 'Apr-2020') as Apr_2020
from t
group by store
order by store;
Note: This puts NULL values in the columns with no corresponding value, rather than -. If you really wanted a hyphen, you would need to convert the value to a string -- and that seems needlessly complicated.
Try this with CASE expression inside SUM(), here is the db-fiddle.
select
store,
sum(case when month = 'Jan-2020' then sales end) as "Jan-2020",
sum(case when month = 'Feb-2020' then sales end) as "Feb-2020",
sum(case when month = 'Mar-2020' then sales end) as "Mar-2020",
sum(case when month = 'Apr-2020' then sales end) as "Apr-2020"
from myTable
group by
store
order by
store
Output:
+---------------------------------------------------+
|store Jan-2020 Feb-2020 Mar-2020 Apr-2020|
+---------------------------------------------------+
| A 400 200 100 null |
| B null 500 400 null |
| C null null null 600 |
+---------------------------------------------------+
If you want to replace null values with 0 in the output then use coalesce()
e.g.
coalesce(sum(case when month = 'Jan-2020' then sales end), 0)

SQL Query to return Month days as Columns and Total Hours as Row

i am working on an Attendance report were I need to create a SQL Query from one table to return the attendance of employees net hours worked over the month.
Day of the month should be as a column and in the rows should be the total Hours of employee.
The Table is having 6 Columns ( Employee Name, Dept , Position, Time In , Time Out and Total Hours
Picture for Selecting * From the Attendance Table
i want to return the values as the following:
EmployeeName | 1st | 2nd | 3rd | 4th | ...... |30 June
Emp 1 | 10:30 | | 10:40 | | 10:10 | | 10:21 |
The Days column should be returned in a parameter so i can add it to crystal report.
Table Structure
if you can advise please.
Thanks in advance
You can use CASE statment like this:
SELECT EmployeeName,
(CASE WHEN EXTRACT(YEAR FROM DATE) = 2017 AND EXTRACT(MONTH FROM DATE) = 6 AND EXTRACT(DAY FROM DATE) = 1 then totalHours ELSE NULL END) AS "01/06",
(CASE WHEN EXTRACT(YEAR FROM DATE) = 2017 AND EXTRACT(MONTH FROM DATE) = 6 AND EXTRACT(DAY FROM DATE) = 2 then totalHours ELSE NULL END) AS "02/06",
.
.
.
(CASE WHEN EXTRACT(YEAR FROM DATE) = 2017 AND EXTRACT(MONTH FROM DATE) = 6 AND EXTRACT(DAY FROM DATE) = 30 then totalHours ELSE NULL END) AS "30/06"
FROM Attendance
So, for each day a new column will be created.
I used something like this
CREATE TABLE `AxsLog` (
`id` integer NOT NULL UNIQUE,
`Logon` text NOT NULL DEFAULT current_timestamp,
`Logoff` text NOT NULL DEFAULT current_timestamp,
`Duration` text NOT NULL DEFAULT 0,
`SysDat` text NOT NULL DEFAULT current_timestamp,
PRIMARY KEY(`id`) );
You can easily add an FK column for each row in your user table.
Keep the logon id for each entry, then update that line on logoff
UPDATE AxsLog
Set Duration= (SELECT sum( strftime('%s', logoff) - strftime('%s', logon) )
/60 FROM AxsLog WHERE id= 1 )
WHERE id= 1 ;
To build a report, use something like this. This query only gives a total per month.
select total(Duration)
FROM AxsLog where substr(sysdat,6,2) = 'month'
your requirement can be fulfill by using crosstab report or if u want to achieve in sql then use pivot

SQL: Aggregate different months from same table

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

How to sum total amount for every month in a year?

I have a database in SQL Server 2012 and there is a table with dates in D.M.YYYY format like below:
ID | Date(date type) | Amount(Numeric)
1 3.4.2013 16.00
1 12.4.2013 13.00
1 2.5.2013 9.50
1 18.5.2013 10.00
I need to sum the total amount for every month in a given year. For example:
ID | Month | TotalAmount
1 1 0.00
...
1 4 29.00
1 5 19.50
I thought what I needed was to determine the number of days in a month, so I created a function which is described in determine the number of days, and it worked. After that I tried to compare two dates(date type) and got stuck; there are some examples out there, but all of them about datetime.
Is this wrong? How can I accomplish this?
I think you just want an aggregation:
select id, month(date) as "month", sum(amount) as TotalAmount
from t
where year(date) = 2013
group by id, month(date)