Postgresql How to Calculate between 2 date depends on another table - sql

let's say i have two table like this :
workday_emp
emp_id work_start work_end
1 "2021-04-06" "2021-04-14"
2 "2021-04-27" "2021-05-04"
3 "2021-04-30" "2021-05-07"
holiday_tbl
id name date
1 "holiday 1" "2021-04-07"
2 "holiday 2" "2021-04-28"
3 "holiday 3" "2021-04-29"
i want to show table like this with a query:
emp_id work_start work_end day_holiday
1 "2021-04-06" "2021-04-14" 1
2 "2021-04-27" "2021-05-04" 2
3 "2021-04-30" "2021-05-07" 1
the question is, how to calculate how many "day_holiday" between "work_start" and "work_end" depends to "holiday_tbl" table?

Please try this. For Employee 3 holiday count will 0 not 1 because his work_day starts at april30 but last holiday was apr29.
-- PostgreSQL(v11)
SELECT w.emp_id, w.work_start, w.work_end
, (SELECT COUNT(id)
FROM holiday_tbl
WHERE holiday_date BETWEEN w.work_start AND w.work_end) day_holiday
FROM workday_emp w
Please check from url https://dbfiddle.uk/?rdbms=postgres_11&fiddle=1948691b58ba841b2765d7de383f8df8

This should do the job:
SELECT emp_id, work_start, work_end, COUNT(ht.holiday) holiday_cnt
FROM workday_emp we LEFT JOIN
(
SELECT date holiday
FROM holiday_tbl
) ht ON ht.holiday BETWEEN we.work_start AND we.work_end
GROUP BY 1, 2, 3
ORDER BY 1, 2;
db<>fiddle

Related

Access query count by months

I have 2 tables -
"club members" table with 3 fields For example:
"Member_ID", "Date_Of_Birth"
1 13/4/1980
2 20/4/1990
3 30/12/1970
4 20/11/1960
"months list" table with the 1 field for example:
"Month"
4-2017
5-2017
...
11-2017
12-2017
...
4-2018
...
11-2018
12-2018
I wish to generate a query that displays
Month , Number_Of_Birthdays
for example:
4-2017 2
5-2017 0
...
11-2017 1
12-2017 1
...
4-2018 2
...
11-2018 1
12-2018 1
How can I do it in access?
Thank you
use inner join and aggregate function count
As you edit your Question so try below query,remember date formating is a major issue here so keep it same format
select ML.Month,Number_Of_Birthdays_Of_Club_Memebers from
(
select format(Member_Date_Of_Birth,"mm-yyyy") as month_number,count(Member_ID) as Number_Of_Birthdays_Of_Club_Memebers
from club_members
group by format(Member_Date_Of_Birth,"mm-yyyy")
)as T1
inner join months_list as ML
T1.month_number=ML.Month
Use this if you want count the members as per join date.
Select b.Month_Date, Count(a.*) as Total_Members
From Club_Members as a INNER Join Month_List as b
ON a.Member_Join_Date=b.Month_Date
Group BY B.month_Date
Try this :
SELECT Month_Date, sum(MemberCount)
FROM (
SELECT c.Member_ID, c.Member_Join_Date, m.Month_Date,CASE WHEN c.Member_Join_Date < m.Month_Date THEN 1 ELSE 0 END MemberCount
FROM club_members c, months_list m
) s
GROUP BY Month_Date

SQL with nested condition

EDIT: added third requirement after playing with solution from Tim Biegeleisen
EDIT2: modified Robbie's DOB to be before his parent's marriage date
I am trying to create a query that will look at two tables and determine the difference in dates based on a percentage. I know, super confusing... Let me try and explain using the tables below:
Bob and Mary are married on 2010-01-01 and expect 4 kids (Parent table)
I want to know how many years it took until they met 50% of their expected kids (i.e. 2/4 kids). Using the Child table to see the DOB of their 4 kids, we know that Frankie is the second child which meets our 50% threshold so we use Frankie's DOB and subtract it from Frankie's parent's marriage date and end up with 3 years!
If the goal isn't reached then display no value e.g. Mick and Jo only had 1 child so far so they haven't yet reached their goal
Hoping this is doable using BigQuery standard SQL.
Parent table
id married_couple married_at expected_kids
--------------------------------------
1 Bob and Mary 2010-01-01 4
2 Mick and Jo 2010-01-01 4
Child table
id child_name parent_id date_of_birth
--------------------------------------
1 Eddie 1 2012-01-01
2 Frankie 1 2013-01-01
3 Robbie 1 2005-01-01
4 Duncan 1 2015-01-01
5 Rick 2 2014-01-01
Expected SQL result
parent_id half_goal_reached(years)
--------------------------------------
1 3
2
Below both soluthions for BigQuery Standard SQL
First one is more in classic sql way, the second one is more of BigQuery style (I think)
First Solution: with analytics function
#standardSQL
SELECT
parent_id,
IF(
MAX(pos) = MAX(CAST(expected_kids / 2 AS INT64)),
MAX(DATE_DIFF(date_of_birth, married_at, YEAR)),
NULL
) AS half_goal_reached
FROM (
SELECT c.parent_id, c.date_of_birth, expected_kids, married_at,
ROW_NUMBER() OVER(PARTITION BY c.parent_id ORDER BY c.date_of_birth) AS pos
FROM `child` AS c
JOIN `parent` AS p
ON c.parent_id = p.id
)
WHERE pos <= CAST(expected_kids / 2 AS INT64)
GROUP BY parent_id
Second Solution: with use of ARRAY
#standardSQL
SELECT
parent_id,
DATE_DIFF(dates[SAFE_ORDINAL(CAST(expected_kids / 2 AS INT64))], married_at, YEAR) AS half_goal_reached
FROM (
SELECT
parent_id,
ARRAY_AGG(date_of_birth ORDER BY date_of_birth) AS dates,
MAX(expected_kids) AS expected_kids,
MAX(married_at) AS married_at
FROM `child` AS c
JOIN `parent` AS p
ON c.parent_id = p.id
GROUP BY parent_id
)
Dummy Data
You can test / play with both solutions using below dummy data
#standardSQL
WITH `parent` AS (
SELECT 1 id, 'Bob and Mary' married_couple, DATE '2010-01-01' married_at, 4 expected_kids UNION ALL
SELECT 2, 'Mick and Jo', DATE '2010-01-01', 4
),
`child` AS (
SELECT 1 id, 'Eddie' child_name, 1 parent_id, DATE '2012-01-01' date_of_birth UNION ALL
SELECT 2, 'Frankie', 1, DATE '2013-01-01' UNION ALL
SELECT 3, 'Robbie', 1, DATE '2014-01-01' UNION ALL
SELECT 4, 'Duncan', 1, DATE '2015-01-01' UNION ALL
SELECT 5, 'Rick', 2, DATE '2014-01-01'
)
Try the following query, whose logic is too verbose to explain it well. I join the parent and child tables, bringing into line the parent id, number of years elapsed since marriage, running number of children, and expected number of children. With this information in hand, we can easily find the first row whose running number of children matches or exceeds half of the expected number.
SELECT parent_id, num_years AS half_goal_reached
FROM
(
SELECT parent_id, num_years, cnt, expected_kids,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY num_years) rn
FROM
(
SELECT
t2.parent_id,
YEAR(t2.date_of_birth) - YEAR(t1.married_at) AS num_years,
(SELECT COUNT(*) FROM child c
WHERE c.parent_id = t2.parent_id AND
c.date_of_birth <= t2.date_of_birth) AS cnt,
t1.expected_kids
FROM parent t1
INNER JOIN child t2
ON t1.id = t2.parent_id
) t
WHERE
cnt >= expected_kids / 2
) t
WHERE t.rn = 1;
Note that there may be issues with how I computed the yearly differences, or how I compute the threshhold for half the number of expected children. Also, if we were using a recent enterprise database we could have used an analytic function to get the running number of children instead of a correlated subquery, but I was unsure if Big Query would support that, so I used the latter.

Find Gap between dates ranges SQL Oracle

l want to get the gap between dates range via SQL query lets see the situation:
l have table employees like : Every month the employee deserve payment
ID Name From_date To_date Paid_Amount`
1 ali 01/01/2002 31/01/2002 300
2 ali 01/02/2002 28/02/2002 300
3 ali 01/04/2002 30/04/2002 300
4 ali 01/05/2002 31/05/2002 300
5 ali 01/07/2002 31/07/2002 300
Now, we notice there are no payments in March and June
so, how by SQL query I can't get these months ??
Try this,
with mine(ID,Name,From_date,To_date,Paid_Amount) as
(
select 1,'ali','01/01/2002','31/01/2002',300 from dual union all
select 2,'ali','01/02/2002','28/02/2002',300 from dual union all
select 3,'ali','01/04/2002','30/04/2002',300 from dual union all
select 4,'ali','01/05/2002','31/05/2002',300 from dual union all
select 5,'ali','01/07/2002','31/07/2002',300 from dual
),
gtfirst (fromdt,todt) as (
select min(to_Date(from_Date,'dd/mm/yyyy')) fromdt,max(to_Date(to_Date,'dd/mm/yyyy')) todt from mine
),
dualtbl(first,last,fromdt,todt) as
(
select * from(select TRUNC(ADD_MONTHS(fromdt, rownum-1), 'MM') AS first,TRUNC(LAST_DAY(ADD_MONTHS(fromdt, rownum-1))) AS last,fromdt,todt from gtfirst connect by level <=12)
where first between fromdt and todt and last between fromdt and todt
)
select to_char(first,'month') no_payment_date from dualtbl where first not in (select to_Date(from_Date,'dd/mm/yyyy') from mine)
and first not in (select to_Date(to_date,'dd/mm/yyyy') from mine)
If you want to get the date difference between one payment date and the previous payment date and the ID field is sequential, then you may simply join back to the table and select the previous row.
SELECT X.From_date, Y.From_date, Y.From_date - X.From_date Difference
FROM Employees X
LEFT OUTER JOIN Employees Y ON Y.ID = X.ID - 1
If the ID field is not sequential, then you can use a similar method, but build a temporary table with a row index that you can use to join back to the previous payment.

Find duplicates within a specific period

I have a table with the following structure
ID Person LOG_TIME
-----------------------------------
1 1 2012-05-21 13:03:11.550
2 1 2012-05-22 13:09:37.050 <--- this is duplicate
3 1 2012-05-28 13:09:37.183
4 2 2012-05-20 15:09:37.230
5 2 2012-05-22 13:03:11.990 <--- this is duplicate
6 2 2012-05-24 04:04:13.222 <--- this is duplicate
7 2 2012-05-29 11:09:37.240
I have some application job that fills this table with data.
There is a business rule that each person should have only 1 record in every 7 days.
From the above example, records # 2,5 and 6 are considered duplicates while 1,3,4 and 7 are OK.
I want to have a SQL query that checks if there are records for the same person in less than 7 days.
;WITH cte AS
(
SELECT ID, Person, LOG_TIME,
DATEDIFF(d, MIN(LOG_TIME) OVER (PARTITION BY Person), LOG_TIME) AS diff_date
FROM dbo.Log_time
)
SELECT *
FROM cte
WHERE diff_date BETWEEN 1 AND 6
Demo on SQLFiddle
Please see my attempt on SQLFiddle here.
You can use a join based on DATEDIFF() to find records which are logged less than 7 days apart:
WITH TooClose
AS
(
SELECT
a.ID AS BeforeID,
b.ID AS AfterID
FROM
Log a
INNER JOIN Log b ON a.Person = b.Person
AND a.LOG_TIME < b.LOG_TIME
AND DATEDIFF(DAY, a.LOG_TIME, b.LOG_TIME) < 7
)
However, this will include records which you don't consider "duplicates" (for instance, ID 3, because it is too close to ID 2). From what you've said, I'm inferring that a record isn't a "duplicate" if the record it is too close to is itself a "duplicate".
So to apply this rule and get the final list of duplicates:
SELECT
AfterID AS ID
FROM
TooClose
WHERE
BeforeID NOT IN (SELECT AfterID FROM TooClose)
Please take a look at this sample.
Reference: SQLFIDDLE
Query:
select person,
datediff(max(log_time),min(log_time)) as diff,
count(log_time)
from pers
group by person
;
select y.person, y.ct
from (
select person,
datediff(max(log_time),min(log_time)) as diff,
count(log_time) as ct
from pers
group by person) as y
where y.ct > 1
and y.diff <= 7
;
PERSON DIFF COUNT(LOG_TIME)
1 1 3
2 8 3
PERSON CT
1 3
declare #Count int
set #count=(
select COUNT(*)
from timeslot
where (( (TimeFrom<#Timefrom and TimeTo >#Timefrom)
or (TimeFrom<#Timeto and TimeTo >#Timeto))
or (TimeFrom=#Timefrom or TimeTo=#Timeto)))

SQL: determine that an activity occurs with a given frequency

A common problem in electronic-medical-record (EMR) reporting in determining that an activity occurs with a specific frequency. In this situation, I need to determine that a note was written every 72-hours after admission.
Given:
A D
|-0-|-1-|-2-|-3-|-4-|-5-|-6-|-7-|-8-|-9-|
|---- 1 ----|---- 2 ----|---- 3 ----|-4-|
There would need to be at least one note during periods 1, 2, and 3. Because 4 isn't a full 72-hour period, it doesn't require a note. Failure to find a note in periods 1, 2, and 3 would be a FAIL.
Data:
(ENC):
ENC_ID ADMITTED DISCHARGED PERIODS PASS_FAIL
4114221 06/15/09 18:30 06/24/09 15:40 3 ?
PERIODS: TRUNC(CEIL((DISCHARGED - ADMITTED)/3))
The 'PASS_FAIL' column would indicate if the encounter had an adequate number and timing of notes.
(NOTE):
ENC_ID NOTE_ID NOTE_TIME PERIOD
4114221 1833764 06/17/09 08:42 1
4114221 1843613 06/18/09 08:14 1
4114221 1858159 06/18/09 20:15 2
4114221 1850948 06/18/09 20:15 2
4114221 1850912 06/18/09 20:18 2
4114221 1859315 06/19/09 18:35 2
4114221 1863982 06/20/09 10:29 2
4114221 1868895 06/21/09 22:00 3
4114221 1873539 06/22/09 15:42 3
PERIOD: CEIL((NOTE_TIME - ADMITTED)/3)
Is there an efficient way to solve this problem?
SELECT e.*,
CASE WHEN cnt = TRUNC(CEIL((discharged / admitted) / 3)) THEN 'pass' ELSE 'fail' END AS pass_fail
FROM (
SELECT COUNT(*) AS cnt
FROM enc ei
CROSS JOIN
(
SELECT level AS period
FROM dual
CONNECT BY
level <=
(
SELECT TRUNC(CEIL((discharged / admitted) / 3))
FROM enc
WHERE enc_id = :enc_id
)
) p
WHERE ei.enc_id = :enc_id
AND EXISTS
(
SELECT NULL
FROM note
WHERE enc_id = ei.enc_id
AND note_time >= ei.admitted + (p - 1) * 3
AND note_time < ei.admitted + p * 3
)
) c
JOIN enc e
ON e.enc_id = :enc_id
If I'm reading your question correctly NOTE is a table with the data indicated.
All you really care about is whether the periods 1, 2 & 3 exist in the notes table for each enc_id.
If this is the case it indicates that an analytic function should be used:
select e.enc_id, e.admitted, e.discharged, e.periods
, decode( n.ct
, 'pass'
, 'fail' ) as pass_fail
from enc e
left outer join ( select distinct enc_id
, count(n.period) over ( partition by n.enc_id ) as ct
from note
where period in (1,2,3)
) n
on e.enc_id = n.enc_id
This selects all period's per enc_id from note, which are the ones you want to examine. Then counts them per enc_id. The distinct is there to ensure you only get one row per enc_id in the final result.
If you only want those enc_ids that have a value in note then turn the left outer join into an inner join.
If period is not, as indicated, in the note query, you have to do a distinct on the full query rather than the sub-query and check which period each note_id is in.
I'm sorry about the horrible formatting but I wanted to try to fit it on the page.
select distinct e.enc_id, e.admitted, e.discharged, e.periods
, decode( count( distinct -- number of distinct periods
case when n.note_time between e.admitted
and e.admitted + 3 then 1
when n.note_time between e.admitted
and e.admitted + 6 then 2
when n.note_time between e.admitted
and e.admitted + 9 then 3
end ) -- per enc_id from note
over ( partition by n.enc_id )
-- if it-s 3 then pass
, 3, 'pass'
-- else fail.
, 'fail' ) as pass_fail
from enc e
left outer join note n
on e.enc_id = n.enc_id
Whatever your data-structure the benefits of both ways are that they are simple joins, one index unique scan ( I'm assuming enc.end_id is unique ) and one index range scan ( on note ).