find holidays for persons work in some branches [closed] - sql

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I have 3 table of data: some people work in a branch and others work in 2 or more branch.
We define 2 case holiday: type 1 all branch is Closed and type 0 is some branch is Closed (not all).
Now I want to find person how is in holiday if all person-branch is Closed
Holiday
ID Date AllBranch
1 2019/02/01 1
2 2019/02/05 0
3 2019/02/06 0
BranchHoliday
ID HolidayID BranchID
1 2 2
2 2 3
3 3 2
PersonBranch
ID PersonID BranchID
1 10 2
2 11 2
3 11 3
4 12 2
5 12 4
The result I want is :
PersonID Hdate
10 2019/02/01
11 2019/02/01
12 2019/02/01
10 2019/02/05
11 2019/02/05
10 2019/02/06
In date (2019/02/01) AllBranch is 1 so all person is in holiday
In date (2019/02/05) AllBranch is 0 so:
PersonID-10 work only in BranchId 2 is define in BranchHoliday table so is in holiday
PersonID-11 work in 2 branch(2,3) and all 2,3 is define closed in BranchHoliday table so is in holiday
PersonID-12 work in 2 branch(2,4) and BranchId 4 is NOT define closed in BranchHoliday so is NOT in holiday
In date (2019/02/06) AllBranch is 0 so:
PersonID-10 work only in BranchId 2 and branch 2 is define in BranchHoliday table so is in holiday
PersonID-11 work in 2 branch(2,3) and only branch 3 is NOT define closed in BranchHoliday table so is in NOT holiday
PersonID-12 work in 2 branch(2,4) and branch 3 is NOT define closed in BranchHoliday table so is NOT in holiday
I need sql query and I am using SQL Server 2016

I don't know why you accepted Esteban's answer, given that your expected result is this. 6 rows only.
PersonID Hdate
10 2019/02/01
11 2019/02/01
12 2019/02/01
10 2019/02/05
11 2019/02/05
10 2019/02/06
Yet Esteban's query produces 7 rows. On February 6 holiday, it wrongly includes person #11 to allowed to have a holiday on February 6. It contradicts your problem definition:
In date (2019/02/06) AllBranch is 0 so:
* ...
* PersonID-11 work in 2 branch(2,3) and only branch 3 is NOT define closed
in BranchHoliday table so is in NOT holiday
Presumably, Person's BranchHoliday(s) should be checked(joined) against the Holiday's table, right? But Esteban's query is not doing that.
Esteban's query produces incorrect output:
Live test: http://sqlfiddle.com/#!18/86728/2
| PersonID | Date |
|----------|------------|
| 10 | 2019-02-01 |
| 11 | 2019-02-01 |
| 12 | 2019-02-01 |
| 10 | 2019-02-05 |
| 11 | 2019-02-05 |
| 10 | 2019-02-06 |
| 11 | 2019-02-06 |
And given these different data:
Holiday
ID Date AllBranch
1 2019/02/01 1
2 2019/02/05 0
3 2019/02/06 0
4 2019/04/02 0
BranchHoliday
ID HolidayID BranchID
1 2 2
2 2 3
3 3 2
4 4 2
5 4 4
Esteban's query produces incorrect output again:
Live test: http://sqlfiddle.com/#!18/10348/1
| PersonID | Date |
|----------|------------|
| 10 | 2019-02-01 |
| 11 | 2019-02-01 |
| 12 | 2019-02-01 |
| 10 | 2019-02-05 |
| 11 | 2019-02-05 |
| 12 | 2019-02-05 |
| 10 | 2019-02-06 |
| 11 | 2019-02-06 |
| 12 | 2019-02-06 |
| 10 | 2019-04-02 |
| 11 | 2019-04-02 |
| 12 | 2019-04-02 |
It should produce this:
Live test: http://sqlfiddle.com/#!17/7b6be/1
| id | date |
|----|------------|
| 10 | 2019-02-01 |
| 11 | 2019-02-01 |
| 12 | 2019-02-01 |
| 10 | 2019-02-05 |
| 11 | 2019-02-05 |
| 10 | 2019-02-06 |
| 10 | 2019-04-02 |
| 12 | 2019-04-02 |
And given these data. Note that only persons on branch #5 are allowed to have a holiday on April 2.
Holiday
ID Date AllBranch
1 2019/02/01 1
2 2019/02/05 0
3 2019/02/06 0
4 2019/04/02 0
BranchHoliday
ID HolidayID BranchID
1 2 2
2 2 3
3 3 2
4 4 5
Esteban's query produces incorrect output again, as only the persons on branch #5 are allowed to have a holiday on April 2. Yet his query includes persons that should not be allowed to have a holiday on April 2. Persons #10 and #11 are not included on branch #5, they should not be on April 2.
Live test: http://sqlfiddle.com/#!18/fd1d3/1
| PersonID | Date |
|----------|------------|
| 10 | 2019-02-01 |
| 11 | 2019-02-01 |
| 12 | 2019-02-01 |
| 10 | 2019-02-05 |
| 11 | 2019-02-05 |
| 10 | 2019-02-06 |
| 11 | 2019-02-06 |
| 10 | 2019-04-02 |
| 11 | 2019-04-02 |
It should produce this. No employees are allowed to have a holiday on April 2, even person #10 is not allowed to have a holiday on April 2, as person #10 is on branch #2 only. Only persons on branch #5 are allowed to have a holiday.
Live test: http://sqlfiddle.com/#!17/299f73/1
| id | date |
|----|------------|
| 10 | 2019-02-01 |
| 11 | 2019-02-01 |
| 12 | 2019-02-01 |
| 10 | 2019-02-05 |
| 11 | 2019-02-05 |
| 10 | 2019-02-06 |
This is the correct query (Postgres version):
Live test: http://sqlfiddle.com/#!17/6cf97/2
select h.date, p.id
from holiday h
cross join person p
join personbranch pb
on p.id = pb.personid
left join branchholiday bh
on h.id = bh.holidayid and pb.branchid = bh.branchid
group by h.date, p.id
having
every(h.allbranch)
or
-- choose only every person whose branch ids are all present in branchholiday
-- filtered by holidayid from holiday.id
every(bh.branchid is not null)
order by h.date, p.id
Output matches your problem definition's expected output:
| id | date |
|----|------------|
| 10 | 2019-02-01 |
| 11 | 2019-02-01 |
| 12 | 2019-02-01 |
| 10 | 2019-02-05 |
| 11 | 2019-02-05 |
| 10 | 2019-02-06 |
SQL Server version:
Live test: http://sqlfiddle.com/#!18/41a54/3
select p.id, h.date
from holiday h
cross join person p
join personbranch pb
on p.id = pb.personid
left join branchholiday bh
on h.id = bh.holidayid and pb.branchid = bh.branchid
group by h.date, p.id
having
count(case when h.allbranch = 1 then 1 end) = count(*)
or
-- choose only every person whose branch ids are all present in branchholiday
-- filtered by holidayid from holiday.id
count(case when bh.branchid is not null then 1 end) = count(*)
order by h.date, p.id
Output matches your problem definition's expected output:
| id | date |
|----|------------|
| 10 | 2019-02-01 |
| 11 | 2019-02-01 |
| 12 | 2019-02-01 |
| 10 | 2019-02-05 |
| 11 | 2019-02-05 |
| 10 | 2019-02-06 |
This is another SQL Server's every idiom. You can change the count(condition) = count(*) to min approach:
Live test: http://sqlfiddle.com/#!18/41a54/4
having
min(case when h.allbranch = 1 then 1 else 0 end) = 1
or
-- choose only every person whose branch ids are all present in branchholiday
-- filtered by holidayid from holiday.id
min(case when bh.branchid is not null then 1 else 0 end) = 1
Output matches your problem definition's expected output:
| id | date |
|----|------------|
| 10 | 2019-02-01 |
| 11 | 2019-02-01 |
| 12 | 2019-02-01 |
| 10 | 2019-02-05 |
| 11 | 2019-02-05 |
| 10 | 2019-02-06 |

One solution would be to break it into two parts:
First selecting the persons for the AllBranch holidays, in a second turn selecting the persons for the specific holidays, where all branches they're working in, have a defined holiday:
SELECT AllPers.PersonID, H.Date
FROM Holiday H
INNER JOIN (
SELECT DISTINCT PersonID
FROM PersonBranch
) AllPers ON H.AllBranch = 1
UNION
SELECT Sub.PersonID, H.Date
FROM Holiday H
INNER JOIN (
SELECT PB.PersonID
, SUM(IIF(BH.BranchID IS NULL, 1, 0)) AS AnyBranchOpen
FROM PersonBranch PB
LEFT JOIN BranchHoliday BH ON PB.BranchID = BH.BranchID
GROUP BY PB.PersonID
) Sub ON AnyBranchOpen = 0
WHERE H.AllBranch = 0

Related

SQL - How to combine 3 dates from 3 different tables (BigQuery) [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
Need to have one view for each agent, and want to aggregate all dates to include all metrics
Basic Information:-
In the productivity table carries the information like as follows:
| Handled_DATE | Agent_Email | Handled | Missed
|:-----------: |:-------------: |:--------:|:------:|
|2013-11-05 | Agent_01#google.com | 80 | 9
|2013-11-06 | Agent_02#google.com | 60 | 15
|2013-11-07 | Agent_03#google.com | 70 | 7
|2013-11-08 | Agent_04#google.com | 55 | 13
|2013-11-09 | Agent_01#google.com | 73 | 5
|2013-11-10 | Agent_01#google.com | 64 | 14
In the Quality table carries the information like this:
| Monitored_DATE | Agent_Email | Bussiness_Critical | Failed
|:-----------: |:-------------: |:-------------: |:-----------:|
|2013-11-05 | Agent_01#google.com | 2 | 2
|2013-11-06 | Agent_01#google.com | 1 | 1
|2013-11-07 | Agent_01#google.com | 4 | 4
|2013-11-08 | Agent_04#google.com | 1 | 1
In the Absentieesm table it carries the information like this:
| Attendance_Date | Agent_Email | Attendance | Late_min
|:-----------: |:-------------: |:----------:|:-----------:|
|2013-11-05 | Agent_01#google.com | Attend | 9
|2013-11-06 | Agent_01#google.com | Sick | 0
|2013-11-07 | Agent_01#google.com | Sick | 0
|2013-11-08 | Agent_04#google.com | 55 | 13
|2013-11-10 | Agent_01#google.com | Attend | 0
Desired Output
| DATE | Agent_Email | Handled | Missed | Business_Critical | Failed |Attendance|Late_min
|:---------- |:-------------: |:-------:|:------:|:-----------------:|:-----: |:--------:|:------:|
|2013-11-05 | Agent_01#google.com | 80 | 9 | 2 | 2 | Attend | 9
|2013-11-06 | Agent_01#google.com | 0 | 0 | 1 | 1 | sick | 0
|2013-11-07 | Agent_01#google.com | 0 | 0 | 4 | 4 | sick | 0
|2013-11-08 | Agent_01#google.com | 0 | 0 | 0 | 0 | sick | 0
|2013-11-09 | Agent_01#google.com | 73 | 5 | 0 | 0 | NA | 0
|2013-11-10 | Agent_01#google.com | 64 | 14 | 0 | 0 | Attend | 0
Try this code - let me know if it helps.
WITH Date AS
(
SELECT DISTINCT date1
FROM
(SELECT p.handled_date AS date1
FROM `productivity` AS p
UNION ALL
SELECT q.Monitored_DATE AS date1
FROM `Quality` AS q
UNION ALL
SELECT a.Attendance_Date AS date1
FROM `Absentieesm` AS a
)
),
Agent AS
(
SELECT DISTINCT Agent_Email
FROM
(SELECT p.Agent_Email AS Agent_Email
FROM `productivity` AS p
UNION ALL
SELECT q.Agent_Email AS Agent_Email
FROM `Quality` AS q
UNION ALL
SELECT a.Agent_Email AS Agent_Email
FROM `Absentieesm` AS a
)
)
SELECT DISTINCT
Date1,
Agent.Agent_Email,
IFNULL(Handled, 0) AS Handled,
IFNULL(Missed, 0) AS Missed,
IFNULL(Business_Critical, 0) AS Business_Critical,
IFNULL(Failed, 0) AS Failed,
IFNULL(Attendance, NULL) Attendance,
IFNULL(Late_min, 0) AS Late_min
FROM
Agent, Date
LEFT OUTER JOIN
`productivity` p ON p.Agent_Email = Agent.Agent_Email
AND p.Handled_DATE = Date.Date1
LEFT OUTER JOIN
`Quality` q ON q.Agent_Email = Agent.Agent_Email
AND q.Monitored_DATE = Date.Date1
LEFT OUTER JOIN
`Absentieesm` a ON a.Agent_Email = Agent.Agent_Email
AND a.Attendance_Date = Date.Date1
ORDER BY
Agent_Email, Date1
```
Join the tables with Agent_Email and respective dates as follows:
SELECT p.handled_date as date,
p.Agent_Email, p.Handled, p.Missed
q.Business_Critical, q.Failed
a.Attendance, a.Late_min
FROM Productivity as p
LEFT JOIN Quality as q
ON p.Agent_Email = q.Agent_Email
AND p.handled_date = q.Monitored_DATE
LEFT JOIN absentieesm as a
ON p.Agent_Email = a.Agent_Email
AND p.handled_date = a.Attendance_DATE
Note - Keep the table which has most of the Agents as LEFT TABLE

How do you use two aggregate functions for separate tables in a join?

Sorry if this is a noob question!
I have two tables - a movie and a comment table.
I am trying to return output of the movie name and each comment for that movie as long as that movie has more than 1 comment associated to it.
Here are my tables
test_movies=# SELECT * FROM movie;
id | name | rating | release_date | original_copy_location
----+------------------------------------+--------+--------------+------------------------
1 | Cruella | 9 | 2021-05-28 | 4
7 | Shutter Island | 9 | 2010-02-19 | 4
9 | Grown Ups | 7 | 2010-06-25 | 4
11 | Guardians of the Galaxy: Volume 1 | 8 | 2014-09-01 | 4
14 | The RIng | 8 | 2002-10-18 | 4
17 | Digimon: The Movie | 6 | 2000-01-10 | 4
19 | Star Wars Episode 1 | 5 | 1999-06-21 | 4
20 | Ghosts Of Mars | 5 | 1998-09-15 | 4
5 | Interstellar | 8 | 2014-11-07 | 1
10 | Mean Girls | 8 | 2004-04-30 | 1
12 | Captain America: The First Avenger | 7 | 2011-07-22 | 1
15 | Get Out | 6 | 2017-02-24 | 1
6 | The Dark Knight | 10 | 2008-07-18 | 2
16 | Pokemon: The First Movie | 5 | 1998-11-10 | 2
18 | The Last Dance | 8 | 2020-05-01 | 2
8 | Just Go With It | 8 | 2011-02-11 | 3
13 | The Blair Witch Project | 8 | 1999-08-29 | 3
(17 rows)
test_movies=# SELECT * FROM comments;
c_id | c_comment | c_movie | c_user
------+--------------------------------------+---------+--------
1 | testing comment 1 | 16 | 4
2 | testing comment 1 | 1 | 1
3 | testing comment 1 | 1 | 2
4 | testing comment 1 | 8 | 5
5 | testing comment 1 | 6 | 3
6 | testing comment 1 | 12 | 2
7 | testing comment 1 | 20 | 3
8 | testing comment 1 | 16 | 5
9 | testing comment 1 | 17 | 4
10 | testing comment 1 | 12 | 2
(10 rows)
Output im trying to get is this:
name | c_comment
------------------------+-------------------------------------
Cruella | testing comment 1
Curella | testing comment 1
Pokemon:The First Movie | testing comment 1
Pokemon:The First Movie | testing comment 1
Captain America | testing comment 1
Captain America | testing comment 1
The problem with my queries is that I can't figure out how to return both the movie name and comment associated with it using aggregate functions.
If I use the count in the first select statement it returns all rows:
SELECT m.name, c.c_comment FROM movie m, comments c WHERE m.id = c.c_movie GROUP BY m.name, c.c_comment HAVING COUNT(m.name) >= 1;
If I try the below subquery I get the error - ERROR: subquery must return only one column
SELECT m.name, c.c_comment FROM movie m, comments c WHERE m.id = c.c_movie AND(SELECT m.name, COUNT(c.c_movie) FROM movie m, comments c WHERE m.id =c.c_movie GROUP BY name HAVING COUNT(c.c_movie) > 1);
Still a bit new to SQL as I'm a student and having a tough time figuring this query out lol.
Thanks in advance!
Something like this could work
select m.name, c.c_comment
from movie m
join comment c
on c.c_movie = m.id
where exists (select 1 from comments cc where cc.c_movie=m.id group by c_movie having count(*)>1)
It's standard sql, but you cannot work with mysql and postgresql at the same time... 🤔
Use window functions!
select m.name, c.c_comment
from movie m join
(select c.*, count(*) over (partition by c_movie) as cnt
from comment c
) c
on c.c_movie = m.id
where cnt > 1;

Enumerating records by date

Say we have 5 records for items sold on particular dates like this
Date of Purchase Qty
2016-11-29 19:33:50.000 5
2017-01-03 20:09:49.000 4
2017-02-23 16:21:21.000 11
2016-11-29 14:33:51.000 2
2016-12-02 16:24:29.000 4
I´d like to enumerate each record by the date in order with an extra column like this:
Date of Purchase Qty Order
2016-11-29 19:33:50.000 5 1
2017-01-03 20:09:49.000 4 3
2017-02-23 16:21:21.000 11 4
2016-11-29 14:33:51.000 2 1
2016-12-02 16:24:29.000 4 2
Notice how both dates on 2016-11-29 have the same order number because I only want to order the records by the date and not by the datetime. How would I create this extra column in just plain SQL?
Using dense_rank() and ordering by the date of DateOfPurchase
select *
, [Order] = dense_rank() over (order by convert(date,DateOfPurchase))
from t
rextester demo: http://rextester.com/FAAQL92440
returns:
+---------------------+-----+-------+
| DateOfPurchase | Qty | Order |
+---------------------+-----+-------+
| 2016-11-29 19:33:50 | 5 | 1 |
| 2016-11-29 14:33:51 | 2 | 1 |
| 2016-12-02 16:24:29 | 4 | 2 |
| 2017-01-03 20:09:49 | 4 | 3 |
| 2017-02-23 16:21:21 | 11 | 4 |
+---------------------+-----+-------+

Looking for duplicate transactions within a 5 minutes over a 24 hour time period

I am looking for duplicate transactions between a 5 minute window during a 24 hour period. I am trying to find users abusing other users access. Here is what I have so far, but it is only searching the past 5 minutes and not searching the 24 hour period. It is ORACLE.
SELECT p.id, Count(*) count
FROM tranledg tl,
patron p
WHERE p.id = tl.patronid
AND tl.trandate > (sysdate-5/1440)
AND tl.plandesignation in ('1')
AND p.id in (select id from tranledg tl where tl.trandate > (sysdate-1))
GROUP BY p.id
HAVING COUNT(*)> 1
Example data:
Patron
id | Name
--------------------------
1 | Joe
2 | Henry
3 | Tom
4 | Mary
5 | Sue
6 | Marie
Tranledg
tranid | trandate | location | patronid
--------------------------
1 | 2015-03-01 12:01:00 | 1500 | 1
2 | 2015-03-01 12:01:15 | 1500 | 2
3 | 2015-03-01 12:03:30 | 1500 | 1
4 | 2015-03-01 12:04:00 | 1500 | 3
5 | 2015-03-01 15:01:00 | 1500 | 4
6 | 2015-03-01 15:01:15 | 1500 | 4
7 | 2015-03-01 17:01:15 | 1500 | 2
8 | 2015-03-01 18:01:30 | 1500 | 1
9 | 2015-03-01 19:02:00 | 1500 | 3
10 | 2015-03-01 20:01:00 | 1500 | 4
11 | 2015-03-01 21:01:00 | 1500 | 5
I would expect the following data to return:
ID | COUNT
1 | 2
4 | 2
You can use an analytic clause with a range window like this:
select *
from (select tranid
, patronid
, count(*) over(partition by patronid
order by trandate
range between 0 preceding
and 5/60/24 following) count
from tranledg
where trandate >= sysdate-1)
where count > 1
It will output all transactions that are followed with more ones for the same patronid in the range of 5 minutes along with the count of the transactions in the range (you did not specify what to do if there are more than one such a range or when the ranges are overlapping).
Output on the test data (without the condition for sysdate as it already passed):
TRANID PATRONID COUNT
------ -------- -----
1 1 2
5 4 2
I did it using Postgres online, Oracle version very similar, only be carefull with date operation.
SQL DEMO
You need a self join.
SELECT T1.patronid, count(*)
FROM Tranledg T1
JOIN Tranledg T2
ON T2."trandate" BETWEEN T1."trandate" + '-2 minute' AND T1."trandate" + '2 minute'
AND T1."patronid" = T2."patronid"
AND T1."tranid" <> T2."tranid"
GROUP BY T1.patronid;
OUTPUT
You need to fix the data, so 1 has two records.

Postgres: Adjust monthly calculations based on goals set

Below is my table:
practice_id | practice_name | practice_location | practice_monthly_revenue | practice_no_of_patients | date
-------------+-------------------+-------------------+--------------------------+-------------------------+---------------------
6 | Practice Clinic 1 | Location1 | 10000 | 8 | 2016-01-12 00:00:00
7 | Practice Clinic 1 | Location1 | 12000 | 10 | 2016-02-12 00:00:00
8 | Practice Clinic 1 | Location1 | 8000 | 4 | 2016-03-12 00:00:00
9 | Practice Clinic 1 | Location1 | 15000 | 10 | 2016-04-12 00:00:00
10 | Practice Clinic 1 | Location1 | 7000 | 3 | 2016-05-12 00:00:00
11 | Practice Clinic 2 | Location2 | 15000 | 12 | 2016-01-13 00:00:00
12 | Practice Clinic 2 | Location2 | 9000 | 8 | 2016-02-13 00:00:00
13 | Practice Clinic 2 | Location2 | 5000 | 2 | 2016-03-03 00:00:00
14 | Practice Clinic 2 | Location2 | 12000 | 9 | 2016-04-13 00:00:00
----------------------------------------------------------------------------------------------------------------------------------
I am firing below query to get monthly revenue vs monthly goal:-
select [date:month], SUM(practice_monthly_revenue) as Monthly_Revenue, 100000/12 as Goals
from practice_info
where practice_name IN ('Practice Clinic 1')
group by [date:month], practice_name
ORDER BY [date:month] ASC
Where "Monthly_Revenue" refers to exact revenue every month while Goal was the exact revenue expected to be generated.
Now I am having issue to write a sql query to adjust the goals next month if the goals aren't met.
E.g. if in March the revenue generated is below 8k which is the monthly goal then the remaining amount in goal should be adjusted in next months goal.
Will it be possible to achieve this with a sql query or I will have to write a sql procedure for it?
EDIT:- I forgot to add that the db belong to postgres.
Goals can be counted as
with recursive goals(mon, val, rev) as
(select min([pinf.date:month]) as mon /* Starting month */, 8000 as val /* Starting goal value */, pinf.practice_monthly_revenue as rev
from practice_info pinf
where pinf.practice_name IN ('Practice Clinic 1')
union all
select goals.mon + 1 as mon, 8000 + greatest(0, goals.val - goals.rev) as val, pinf.practice_monthly_revenue as rev
from practice_info pinf, goals
where goals.mon + 1 = [pinf.date:month]
and pinf.practice_name IN ('Practice Clinic 1')
)
select * from goals;
Just integrate it with your query to compare goals and revenues. It can be not exactly what you want, but I do believe you'll get the main point.