Showing Monthly Totals from Multiple Columns in PostgreSQL - sql

I am looking to see the number of firstexam patients seen per month for a given date range this year vs last year, and compare that to the total number of patients seen per month for the same date range.
I am able to set up the firstexam patients as follows:
select case EXTRACT(month FROM patient_info.firstexam)
when 1 then '01 - January'
when 2 then '02 - February'
when 3 then '03 - March'
when 4 then '04 - April'
when 5 then '05 - May'
when 6 then '06 - June'
when 7 then '07 - July'
when 8 then '08 - August'
when 9 then '09 - September'
when 10 then '10 - October'
when 11 then '11 - November'
when 12 then '12 - December'
end as month,
sum(case when patient_info.firstexam >= '2013-01-01' AND patient_info.firstexam <= '2013-12-31' then 1 else 0 end) thisyear,
sum(case when patient_info.firstexam >= '2012-01-01' AND patient_info.firstexam <= '2012-12-31' then 1 else 0 end) lastyear
from patient_info WHERE (patient_info.firstexam >= '2013-01-01' AND patient_info.firstexam <= '2013-12-31' OR patient_info.firstexam >= '2012-01-01' AND patient_info.firstexam <= '2012-12-31')
GROUP BY month
ORDER BY month
This gives me three columns: month, thisyear, lastyear.
Please note: I entered numerical month before month name because I could not get the months to appear in chronological order otherwise. Any hints to not show the number before the month would be appreciated.
I would like to add two more columns for total patients - something like:
select case EXTRACT(month FROM patient_info.lastexam)
when 1 then '01 - January'
when 2 then '02 - February'
when 3 then '03 - March'
when 4 then '04 - April'
when 5 then '05 - May'
when 6 then '06 - June'
when 7 then '07 - July'
when 8 then '08 - August'
when 9 then '09 - September'
when 10 then '10 - October'
when 11 then '11 - November'
when 12 then '12 - December'
end as month,
sum(case when patient_info.lastexam >= '2013-01-01' AND patient_info.lastexam <= '2013-12-31' then 1 else 0 end) totalthisyear,
sum(case when patient_info.lastexam >= '2012-01-01' AND patient_info.lastexam <= '2012-12-31' then 1 else 0 end) totallastyear
from patient_info WHERE (patient_info.lastexam >= '2013-01-01' AND patient_info.lastexam <= '2013-12-31' OR patient_info.lastexam >= '2012-01-01' AND patient_info.lastexam <= '2012-12-31')
GROUP BY month
ORDER BY month
with the results in 5 columns: month, thisyear, totalthisyear, lastyear, totallastyear
but can't seem to figure out exactly how this could be done. The order of the columns is not important.
It could be: month, thisyear, lastyear, totalthisyear, totallastyear

SQL Fiddle
select
to_char(('2012-' || m || '-01')::date, 'Month'),
thisyear, lastyear, totalthisyear, totallastyear
from (
select
extract(month from m) as m,
sum(case
when firstexam between '2013-01-01' and '2013-12-31' then firstexam_count
else 0 end
) as thisyear,
sum(case
when firstexam between '2012-01-01' and '2012-12-31' then firstexam_count
else 0 end
) as lastyear,
sum(case
when lastexam between '2013-01-01' and '2013-12-31' then lastexam_count
else 0 end
) as totalthisyear,
sum(case
when lastexam between '2012-01-01' and '2012-12-31' then lastexam_count
else 0 end
) as totallastyear
from
generate_series (
'2012-01-01'::date, '2013-12-31', '1 month'
) g(m)
left join (
select count(*) as firstexam_count, date_trunc('month', firstexam) as firstexam
from patient_info
where firstexam between '2012-01-01' and '2013-12-31'
group by 2
) pif on firstexam = m
left join (
select count(*) as lastexam_count, date_trunc('month', lastexam) as lastexam
from patient_info
where lastexam between '2012-01-01' and '2013-12-31'
group by 2
) pil on lastexam = m
group by 1
) s
order by m

Related

Adding custom column to calendar table containing the year number of the following year on and after 2nd Sunday of December of each year

I have created the following calendar table:
WITH dates AS (
SELECT EXPLODE(SEQUENCE(TO_DATE('1970-01-01'), TO_DATE('2100-12-31'), INTERVAL 1 DAY)) AS calendar_date
),
calendar_table AS (
SELECT
YEAR(calendar_date) * 10000 + MONTH(calendar_date) * 100 + DAY(calendar_date) AS date_integer,
calendar_date,
YEAR(calendar_date) AS year_of_date,
QUARTER(calendar_date) AS quarter_of_year,
MONTH(calendar_date) AS month_of_year,
DAY(calendar_date) AS day_of_month,
WEEKDAY(calendar_date) + 1 AS day_of_week_start_monday,
DAYOFWEEK(calendar_date) AS day_of_week_start_sunday,
CASE
WHEN DAY(calendar_date) >= 1 AND DAY(calendar_date) <= 7 THEN 1
WHEN DAY(calendar_date) >= 8 AND DAY(calendar_date) <= 14 THEN 2
WHEN DAY(calendar_date) >= 15 AND DAY(calendar_date) <= 21 THEN 3
WHEN DAY(calendar_date) >= 22 AND DAY(calendar_date) <= 28 THEN 4
ELSE 5
END AS day_of_week_ordinal,
CASE
WHEN WEEKDAY(calendar_date) < 5 THEN TRUE
ELSE FALSE
END AS is_week_day,
CASE
WHEN WEEKDAY(calendar_date) > 4 THEN TRUE
ELSE FALSE
END AS is_weekend,
CASE
WHEN calendar_date = DATE_TRUNC('month', calendar_date)::DATE THEN TRUE
ELSE FALSE
END AS is_first_day_of_month,
CASE
WHEN calendar_date = LAST_DAY(calendar_date) THEN TRUE
ELSE FALSE
END AS is_last_day_of_month,
DAYOFYEAR(calendar_date) AS day_of_year,
WEEKOFYEAR(calendar_date) AS iso_week_of_year,
EXTRACT(YEAROFWEEK FROM calendar_date) AS iso_year_of_date,
FROM
dates
)
I am missing a custom calendar column that would abide by the following rule:
From the second Sunday (inclusive) in December of each year, the column should contain a concatenation of 'X' and the year number of the following year.
Example:
calendar_date
custom_column
2022-12-10
X2022
2022-12-11
X2023
2022-12-12
X2023
...
...
2023-12-09
X2023
2023-12-10
X2024
2023-12-11
X2024
So far, I've been able to identify the second Sunday of December in each year by combining the logic behind the columns month_of_year, day_of_week_ordinal and day_of_week_start_monday in my calendar table, but I fail to grasp any implementation (I'm sure I'm missing something simple here).
I can calculate a flag for the second Sunday in December of each year by utilising the following logic:
CASE
WHEN
month_of_year = 12
AND day_of_week_ordinal = 2
AND day_of_week_start_monday = 7 THEN TRUE
ELSE FALSE
END AS second_sunday_in_month
But I fail to see how I can get transfer this logic to what I want as the end result.
Edit: I have added a PostgreSQL fiddle as an interactive example.
Here this might be able to help you :
SELECT
*,
CASE
WHEN calendar_date between (select calendar_date from calendar_table
where month_of_year = 12 -- December
AND day_of_week_start_monday = 7 -- Sunday
AND day_of_week_ordinal = 2 ) and (select date_trunc('year',calendar_date + interval '1 year') - interval '1 day')
THEN 'X' || date_part('year', calendar_date) +1
ELSE 'X' || date_part('year', calendar_date)
END AS is_second_sunday_of_december
FROM calendar_table;
https://dbfiddle.uk/WynGf5w_
Problem is its only working on yearly basis so there might be more tweaking needed!
Update:
There you go:
CASE
WHEN
year_of_date = (select year_of_date from calendar_table where month_of_year = 12 -- December
AND day_of_week_start_monday = 7 -- Sunday
AND day_of_week_ordinal = 2)
AND calendar_date between (select calendar_date from calendar_table
where month_of_year = 12 -- December
AND day_of_week_start_monday = 7 -- Sunday
AND day_of_week_ordinal = 2 ) and (select date_trunc('year',calendar_date + interval '1 year') - interval '1 day')
THEN 'X' || date_part('year', calendar_date) + 1
else 'X' || date_part('year', calendar_date)
END AS is_second_sunday_of_december
from calendar_table
fiddle
i hope this time the link works if not there is an underscore on the end.

Writing a sql query that captures the 2022 YTD data and compare to same period from 2021.. ex"Jan 1 to Jun 10 2022" vs "Jan 1 to Jun 10 2021"

SUM(case when TO_DATE(WO.W_MEASURE_CLAIMS_SAVING_DATE,'YYYY-MM-DD') between (sysdate -365) and sysdate then PM.M_GROSS_ANNUAL_KWH_SVG else 0 end) as CURRENT_KWH_12,
SUM(case when TO_DATE(WO.W_MEASURE_CLAIMS_SAVING_DATE,'YYYY-MM-DD') between sysdate -730 and (sysdate -365) then PM.M_GROSS_ANNUAL_KWH_SVG else 0 end) as PREVIOUS_LAST_KWH_12,
SUM(case when TO_DATE(WO.W_MEASURE_CLAIMS_SAVING_DATE,'YYYY-MM-DD') between (sysdate -365) and sysdate then PM.M_GROSS_ANNUAL_THERM_SVG else 0 end) as CURRENT_THERMS_12,
sum(case when TO_DATE(WO.W_MEASURE_CLAIMS_SAVING_DATE,'YYYY-MM-DD') between sysdate -730 and (sysdate -365) then PM.M_GROSS_ANNUAL_THERM_SVG else 0 end) as PREVIOUS_LAST_THERMS_12,
... i think the "current" data needs to be W_MEASURE_CLAIMS_SAVING_DATE >= 2022-01-01
and the [2:09 PM] Siegel, James R
and the "previous" data needs to be W_MEASURE_CLAIMS_SAVING_DATE between 2021-01-01 and today's date -365 days
but i just need help writing it
You appear to want to compare values between the start of this year until now to between the start of last year and the same date as now last year:
SUM(
CASE
WHEN WO.W_MEASURE_CLAIMS_SAVING_DATE BETWEEN TRUNC(SYSDATE, 'YY')
AND SYSDATE
THEN PM.M_GROSS_ANNUAL_KWH_SVG
END
) AS CURRENT_KWH,
SUM(
CASE
WHEN WO.W_MEASURE_CLAIMS_SAVING_DATE BETWEEN ADD_MONTHS(TRUNC(SYSDATE, 'YY'), -12)
AND ADD_MONTHS(SYSDATE, -12)
THEN PM.M_GROSS_ANNUAL_KWH_SVG
END
) as PREVIOUS_KWH

Aggregating a column based on date dynamically in SQL

This is more of a conceptual question. I'm trying to compare the sales that have happened this year with the last year. But the comparison should be dynamic, as in, for this year I consider the sales that have happened till yesterday, and I need to consider the sales for the previous year till the same date last year as well.
For example, today is Dec 24th, so for 2021 I aggregate the sales till Dec 23rd, and I need to do the same for 2020 as well i.e. till Dec 23rd of 2020. And for tomorrow's report the aggregate should be till Dec 24th of 2021 and Dec 24th of 2020 respectively.
My code so far:
SELECT product_category,
SUM(CASE WHEN purchase_date >= '2021-01-01' AND purchase_date < CURRENT_DATE THEN sales_revenue ELSE 0 END) AS revenue_2021,
SUM(CASE WHEN purchase_date >= '2020-01-01' AND purchase_date < '2021-01-01' THEN sales_revenue ELSE 0 END) AS revenue_2020
FROM sales_table
GROUP BY 1
ORDER BY 1
Here, for 2021, my code works. But for 2020 it would give the whole year (2020's) sum. Is there anyway I can make this dynamic for 2020 just the same way it happens for 2021?
SELECT product_category,
SUM(CASE WHEN purchase_date >= DATEADD(yy, DATEDIFF(yy, 0, CURRENT_DATE), 0)
AND purchase_date < CURRENT_DATE THEN sales_revenue ELSE 0 END) AS revenue_2021,
SUM(CASE WHEN purchase_date >= DATEADD(yy, DATEDIFF(yy, 0, CURRENT_DATE)-1, 0) AND purchase_date < dateadd(yy, -1, CURRENT_DATE) THEN sales_revenue ELSE 0 END) AS revenue_2020
FROM sales_table
WHERE
purchase_date >= DATEADD(yy, DATEDIFF(yy, 0, CURRENT_DATE)-1, 0)
GROUP BY 1
ORDER BY 1
I figured this out by myself and this seems to work.
SELECT EXTRACT(YEAR FROM DATE_TRUNC('YEAR',purchase_date) AS Year,
product_category,
SUM(sales_revenue)
FROM sales_table
WHERE DATE_PART('MONTH',purchase_date)*100 + DATE_PART('DAY',purchase_date_time)
< DATE_PART('MONTH',CURRENT_DATE)*100 + DATE_PART('DAY',CURRENT_DATE)
GROUP BY 1,2
ORDER BY 1,2

Select data grouped by time over midnight

I have a table like:
ID TIMEVALUE
----- -------------
1 06.07.15 06:43:01,000000000
2 06.07.15 12:17:01,000000000
3 06.07.15 18:21:01,000000000
4 06.07.15 23:56:01,000000000
5 07.07.15 04:11:01,000000000
6 07.07.15 10:47:01,000000000
7 07.07.15 12:32:01,000000000
8 07.07.15 14:47:01,000000000
and I want to group this data by special times.
My current query looks like this:
SELECT TO_CHAR(TIMEVALUE, 'YYYY\MM\DD'), COUNT(ID),
SUM(CASE WHEN TO_CHAR(TIMEVALUE, 'HH24MI') <=700 THEN 1 ELSE 0 END) as morning,
SUM(CASE WHEN TO_CHAR(TIMEVALUE, 'HH24MI') >700 AND TO_CHAR(TIMEVALUE, 'HH24MI') <1400 THEN 1 ELSE 0 END) as daytime,
SUM(CASE WHEN TO_CHAR(TIMEVALUE, 'HH24MI') >=1400 THEN 1 ELSE 0 END) as evening FROM Table
WHERE TIMEVALUE >= to_timestamp('05.07.2015','DD.MM.YYYY')
GROUP BY TO_CHAR(TIMEVALUE, 'YYYY\MM\DD')
and I am getting this output
day overall morning daytime evening
----- ---------
2015\07\05 454 0 0 454
2015\07\06 599 113 250 236
2015\07\07 404 139 265 0
so that is fine grouping on the same day (0-7 o'clock, 7-14 o'clock and 14-24 o'clock)
But my question now is:
How can I group over midnight?
For example count from 6-14 , 14-23 and 23-6 o'clock on next day.
I hope you understand my question. You are welcome to even improve my upper query if there is a better solution.
EDIT: It is tested now: SQL Fiddle
The key is simply to adjust the group by so that anything before 6am gets grouped with the previous day. After that, the counts are pretty straight-forward.
SELECT TO_CHAR(CASE WHEN EXTRACT(HOUR FROM timevalue) < 6
THEN timevalue - 1
ELSE timevalue
END, 'YYYY\MM\DD') AS day,
COUNT(*) AS overall,
SUM(CASE WHEN EXTRACT(HOUR FROM timevalue) >= 6 AND EXTRACT(HOUR FROM timevalue) < 14
THEN 1 ELSE 0 END) AS morning,
SUM(CASE WHEN EXTRACT(HOUR FROM timevalue) >= 14 AND EXTRACT(HOUR FROM timevalue) < 23
THEN 1 ELSE 0 END) AS daytime,
SUM(CASE WHEN EXTRACT(HOUR FROM timevalue) < 6 OR EXTRACT(HOUR FROM timevalue) >= 23
THEN 1 ELSE 0 END) AS evening
FROM my_table
WHERE timevalue >= TO_TIMESTAMP('05.07.2015','DD.MM.YYYY')
GROUP BY TO_CHAR(CASE WHEN EXTRACT(HOUR FROM timevalue) < 6
THEN timevalue - 1
ELSE timevalue
END, 'YYYY\MM\DD');
Substract 1 day from timevalue for times lower than '06:00' at first and then:
SQLFiddle demo
select TO_CHAR(day, 'YYYY\MM\DD') day, COUNT(ID) cnt,
SUM(case when '23' < tvh or tvh <= '06' THEN 1 ELSE 0 END) as midnight,
SUM(case when '06' < tvh and tvh <= '14' THEN 1 ELSE 0 END) as daytime,
SUM(case when '14' < tvh and tvh <= '23' THEN 1 ELSE 0 END) as evening
FROM (
select id, to_char(TIMEVALUE, 'HH24') tvh,
trunc(case when (to_char(timevalue, 'hh24') <= '06')
then timevalue - interval '1' day
else timevalue end) day
from t1
)
GROUP BY day
Maybe you can do it like this (with some reformatting or PIVOT):
WITH spans AS
(SELECT TIMESTAMP '2015-01-01 00:00:00' + LEVEL * INTERVAL '1' HOUR AS start_time
FROM dual
CONNECT BY TIMESTAMP '2015-01-01 00:00:00' + LEVEL * INTERVAL '1' HOUR < LOCALTIMESTAMP),
t AS
(SELECT start_time, lead(start_time, 1) OVER (ORDER BY start_time) AS end_time, ROWNUM AS N
FROM spans
WHERE EXTRACT(HOUR FROM start_time) IN (6,14,23))
SELECT N, start_time, end_time, COUNT(*) AS ID_COUNT,
DECODE(EXTRACT(HOUR FROM start_time), 6,'morning', 14,'daytime', 23,'evening') AS daytime
FROM t
JOIN YOUR_TABLE WHERE TIMEVALUE BETWEEN start_time AND end_time
GROUP BY N;
Of course, the initial time value ('2015-01-01 00:00:00' in my example) has to be lower than the least date in your table.

SQL Results group by month

I'm trying to return some results spread over a rolling 12 month period eg:
MONTH IN OUT
January 210 191
February 200 111
March 132 141
April 112 141
May 191 188
etc...
How do I spread the results over a date range, populating the first column with the month name?
IN MSSQL it would be something like:
SELECT COUNT(problem.problem_type = 'IN') AS IN,
COUNT(problem.problem_type = 'OUT') AS OUT,
DATEPART(year, DateTime) as Year,
DATEPART(month, DateTime) as Month
FROM problem
WHERE (DateTime >= dbo.FormatDateTime('2010-01-01'))
AND
(DateTime < dbo.FormatDateTime('2010-01-31'))
GROUP BY DATEPART(year, DateTime),
DATEPART(month, DateTime);
But this is against an Oracle database so DATEPART and DateTime are not available.
My Problem table is roughly:
problem_ID Problem_type IN_Date OUT_Date
1 IN 2010-01-23 16:34:29.0 2010-02-29 13:06:28.0
2 IN 2010-01-27 12:34:29.0 2010-01-29 12:01:28.0
3 OUT 2010-02-13 13:24:29.0 2010-09-29 15:04:28.0
4 OUT 2010-02-15 16:31:29.0 2010-07-29 11:03:28.0
Use:
SELECT SUM(CASE WHEN p.problem_type = 'IN' THEN 1 ELSE 0 END) AS IN,
SUM(CASE WHEN p.problem_type = 'OUT' THEN 1 ELSE 0 END) AS OUT,
TO_CHAR(datetime, 'YYYY') AS year,
TO_CHAR(datetime, 'MM') AS month
FROM PROBLEM p
WHERE p.DateTime >= TO_DATE('2010-01-01', 'YYYY-MM-DD')
AND p.DateTime < TO_DATE('2010-01-31', 'YYYY-MM-DD')
GROUP BY TO_CHAR(datetime, 'YYYY'), TO_CHAR(datetime, 'MM')
You could also use:
SELECT SUM(CASE WHEN p.problem_type = 'IN' THEN 1 ELSE 0 END) AS IN,
SUM(CASE WHEN p.problem_type = 'OUT' THEN 1 ELSE 0 END) AS OUT,
TO_CHAR(datetime, 'MM-YYYY') AS mon_year
FROM PROBLEM p
WHERE p.DateTime >= TO_DATE('2010-01-01', 'YYYY-MM-DD')
AND p.DateTime < TO_DATE('2010-01-31', 'YYYY-MM-DD')
GROUP BY TO_CHAR(datetime, 'MM-YYYY')
Reference:
TO_CHAR
TO_DATE
You probably want something like
SELECT SUM( (CASE WHEN problem_type = 'IN' THEN 1 ELSE 0 END) ) in,
SUM( (CASE WHEN problem_type = 'OUT' THEN 1 ELSE 0 END) ) out,
EXTRACT( year FROM DateTime ) year,
EXTRACT( month FROM DateTime ) month
FROM problem
WHERE DateTime >= date '2010-01-01'
AND DateTime < date '2010-01-31'
GROUP BY EXTRACT( year FROM DateTime ),
EXTRACT( month FROM DateTime )