Comparing Selected Date Range With Current & Previous Month - sql

I would like to provide the user to option to select a range of date in current month and results should be comparison of same date range for current & previous month.
Eg. selected date 1-12-2022 to 15-12-2022
Result:
Count X 1-11-2022 to 15-11-2022
Count X 1-12-200 to 15-12-2022
Can this be achieved through date_part function?

Suppose you have a column with type date named ts in your table:
SELECT
count(*) FILTER ( WHERE ts BETWEEN cast(:lower AS DATE) - INTERVAL '1 month' AND cast(:upper AS DATE) - INTERVAL '1 month') previous,
count(*) FILTER ( WHERE ts BETWEEN cast(:lower AS DATE) AND cast(:upper AS DATE) ) selected,
count(*) FILTER ( WHERE ts BETWEEN cast(:lower AS DATE) + INTERVAL '1 month' AND cast(:upper AS DATE) + INTERVAL '1 month' ) next
FROM your_table;
You just need to provide the lower and upper bound values as dates (e.g. '01-12-2022'.
This will give you 3 columns -- previous, selected and next -- with the corresponding row-counts.
BTW: the upper bound is exclusive.

Related

SQL Query to group dates and includes different dates in the aggregation

I have a table with two columns, dates and number of searches in each date. What I want to do is group by the dates, and find the sum of number of searches for each date.
The trick is that for each group, I also want to include the number of searches for the date exactly the following week, and the number of searches for the date exactly the previous week.
So If I have
Date
Searches
2/3/2023
2
2/10/2023
4
2/17/2023
1
2/24/2023
5
I want the output for the 2/10/2023 and 2/17/2023 groups to be
Date
Sum
2/10/2023
7
2/17/2023
10
How can I write a query for this?
You can use a correlated query for this:
select date, (
select sum(searches)
from t as x
where x.date between t.date - interval '7 day' and t.date + interval '7 day'
) as sum_win
from t
Replace interval 'x day' with the appropriate date add function for your RDBMS.
If your RDBMS supports interval in window functions then a much better solution would be:
select date, sum(searches) over (
order by date
range between interval '7 day' preceding and interval '7 day' following
) as sum_win
from t
Assuming weekly rows
CREATE TABLE Table1
([Dates] date, [Searches] int)
;
INSERT INTO Table1
([Dates], [Searches])
VALUES
('2023-02-03 00:00:00', 2),
('2023-02-10 00:00:00', 4),
('2023-02-17 00:00:00', 1),
('2023-02-24 00:00:00', 5)
;
;with cte as (
select dates
, searches
+ lead(searches) over(order by dates)
+ lag(searches) over(order by dates) as sum_searches
from table1)
select * from cte
where sum_searches is not null;
dates
sum_searches
2023-02-10
7
2023-02-17
10
fiddle

How do I set up a rolling 7 day 75th percentile in SQL?

I have a table that has the following columns:
Event Date
Location
Employee Id
Task Name
Volume Per Hour
Using PostgreSQL, I need to calculate the 75th percentile of Volume Per Hour for a given location and task name across all employee ids and event dates assuming a rolling 7 day window. For example, if the event date is 11/16/2020, I would take the 75th percentile of volume per hour for all the individual dates and employee ids between 11/09/2020 and 11/16/2020.
Can someone help me with this problem?
Sample Data:
Sample Output:
You should be able to achieve this by using generate_series and percentile_disc
with data_example as
(
SELECT * FROM (VALUES
(date '2020-11-16','ABC',1,'Inbound',10),
(date '2020-11-16','ABC',2,'Inbound',20),
(date '2020-11-15','ABC',1,'Inbound',30),
(date '2020-11-17','ABC',1,'Inbound',10)
) AS t (event_date,location,emp_id,task_name,volume)
)
,dates as
(
select generate_series(
(date '2020-11-10')::timestamp,
(date '2020-11-25')::timestamp,
interval '1 day'
) as event_date
)
select d.event_date
, d.event_date - INTERVAL '7 day' AS window_start
,location
,task_name
,percentile_disc(0.75) within group (order by de.volume) perc_volume
,count(1) cnt
from dates d
join data_example de
on de.event_date between d.event_date- INTERVAL '7 day' and d.event_date
group by 1,2,3,4
order by 1,2,3,4;

PostgreSQL generate month and year series based on table field and fill with nulls if no data for a given month

I want to generate series of month and year from the next month of current year(say, start_month) to 12 months from start_month along with the corresponding data (if any, else return nulls) from another table in PostgreSQL.
SELECT ( ( DATE '2019-03-01' + ( interval '1' month * generate_series(0, 11) ) )
:: DATE ) dd,
extract(year FROM ( DATE '2019-03-01' + ( interval '1' month *
generate_series(0, 11) )
)),
coalesce(SUM(price), 0)
FROM items
WHERE s.date_added >= '2019-03-01'
AND s.date_added < '2020-03-01'
AND item_type_id = 3
GROUP BY 1,
2
ORDER BY 2;
The problem with the above query is that it is giving me the same value for price for all the months. The requirement is that the price column be filled with nulls or zeros if no price data is available for a given month.
Put the generate_series() in the FROM clause. You are summarizing the data -- i.e. calculating the price over the entire range -- and then projecting this on all months. Instead:
SELECT gs.yyyymm,
coalesce(SUM(i.price), 0)
FROM generate_series('2019-03-01'::date, '2020-02-01', INTERVAL '1 MONTH'
) gs(yyyymm) LEFT JOIN
items i
ON gs.yyyymm = DATE_TRUNC('month', s.date_added) AND
i.item_type_id = 3
GROUP BY gs.yyyymm
ORDER BY gs.yyyymm;
You want generate_series in the FROM clause and join with it, somewhat like
SELECT months.m::date, ...
FROM generate_series(
start_month,
start_month + INTERVAL '11 months',
INTERVAL '1 month'
) AS months(m)
LEFT JOIN items
ON months.m::date = items.date_added

Choose next date that isn't included in a set

I have a view that lists certain events taking place the next day
SELECT column1, column2...
FROM table1
WHERE date = CAST(CURRENT_DATE + INTERVAL '1 DAY' AS DATE)
Nevertheless, I have a table of 'forbidden dates': I cannot use this specific set of dates, so, in case the next day is in that forbidden list, it must jump to the next one. Sort of like this:
SELECT column1, column2...
FROM table1
WHERE
CASE
WHEN CAST(CURRENT_DATE + INTERVAL '1 DAY' AS DATE) IN (SELECT DISTINCT date FROM forbidden_date)
THEN CAST(CURRENT_DATE + INTERVAL '2 DAY' AS DATE)
ELSE
CAST(CURRENT_DATE + INTERVAL '1 DAY' AS DATE)
END = date
The problem is that "what if the second next day is also in the forbidden list? and so on and on?"
I actually could control all this from a script, but I'm just really curious if I could make it through with just a query
Use generate_series to return all dates from the next 365 days and chose the first one that isn't a forbidden date:
SELECT column1, column2...
FROM table1
WHERE date = (
select min(d)
from generate_series(current_date + 1, current_date + 365, '1 day') as dates(d)
where d not in (select date from forbidden_dates))

Get value zero if data is not there in PostgreSQL

I have a table employee in Postgres:
Query:
SELECT DISTINCT month_last_date,number_of_cases,reopens,csat
FROM employee
WHERE month_last_date >=(date('2017-01-31') - interval '6 month')
AND month_last_date <= date('2017-01-31')
AND agent_id='analyst'
AND name='SAM';
Output:
But if data is not in table for other month I want column value as 0.
Generate all dates you are interested in, LEFT JOIN to the table and default to 0 with COALESCE:
SELECT DISTINCT -- see below
i.month_last_date
, COALESCE(number_of_cases, 0) AS number_of_cases -- see below
, COALESCE(reopens, 0) AS reopens
, COALESCE(csat, 0) AS csat
FROM (
SELECT date '2017-01-31' - i * interval '1 mon' AS month_last_date
FROM generate_series(0, 5) i -- see below
) i
LEFT JOIN employee e ON e.month_last_date = i.month_last_date
AND e.agent_id = 'analyst' -- see below
AND e.name = 'SAM';
Notes
If you add or subtract an interval of 1 month and the same day does not exist in the target month, Postgres defaults to the latest existing day of that moth. So this works as desired, you get the last day of each month:
SELECT date '2017-12-31' - i * interval '1 mon' -- note 31
FROM generate_series(0,11) i;
But this does not, you'd get the 28th of each month:
SELECT date '2017-02-28' - i * interval '1 mon' -- note 28
FROM generate_series(0,11) i;
The safe alternative is to subtract 1 day from the first day of the next month, like #Oto demonstrated. Related:
Daily average for the month (needs number of days in month)
Here are two optimized ways to generate a series of last days of the month - up to and including a given month:
1.
SELECT (timestamp '2017-01-01' - i * interval '1 month')::date - 1 AS month_last_date
FROM generate_series(-1, 10) i; -- generate 12 months, off-by-1
Input is the first day of the month - or calculate it from a given date or timestamp with date_trunc():
SELECT date_trunc('month', timestamp '2017-01-17')::date AS this_mon1
Subtracting an interval from a date produces a timestamp. After the cast back to date we can simply subtract an integer to subtract days.
2.
SELECT m::date - 1 AS month_last_date
FROM generate_series(timestamp '2017-02-01' - interval '11 month' -- for 12 months
, timestamp '2017-02-01'
, interval '1 mon') m;
Input is the first day of the next month - or calculate it from any given date or timestamp with:
SELECT date_trunc('month', timestamp '2017-01-17' + interval '1 month')::date AS next_mon1
Related:
How do I determine the last day of the previous month using PostgreSQL?
Create list with first and last day of month for given period
Not sure you actually need DISTINCT. Typically, (agent_id, month_last_date) would be defined unique, then remove DISTINCT ...
Be sure to use the LEFT JOIN correctly. Join conditions go into the join clause, not the WHERE clause:
Explain JOIN vs. LEFT JOIN and WHERE condition performance suggestion in more detail
Finally, default to 0 with COALESCE where NULL values are filled in by the LEFT JOIN.
Note that COALESCE cannot distinguish between actual NULL values from the right table and NULL values filled in for missing rows. If your columns are not defined NOT NULL, there may be ambiguity to address.
As I see, you need generate last days of all last 6 months, before certain date. (before "2017-01-31" in this case).
If I correctly understand, then you can use this query, which generates all of these days
SELECT (date_trunc('MONTH', mnth) + INTERVAL '1 MONTH - 1 day')::DATE
FROM
generate_series('2017-01-31'::date - interval '6 month', '2017-01-31'::date, '1 month') as mnth;
You just need LEFT JOIN this query to your existing query, and you get desirable result
Please note that this will returns 7 record (days), not 6.