Postgres sql 8.4 Use of Time Difference and Date Difference Separately - sql

I Have this question and i can not resolve it because i think thats is impossible in sql
I have this table
Shedule
id_emp Name `time initial` time end
1 juan` 09:00` 12:00
2 Francisco 10:00 11:30
3 Sebastian 11:00 15:00
6 Roberto 15:00 18:00
Suspension
id_emp suspension_initial suspension_end
1 2013-06-01 2013-06-01
2 2013-06-01 2013-06-03
3 2013-06-03 2013-06-04
6 2013-06-01 2013-06-01
2 2013-07-01 2013-07-01
3 2013-07-05 2013-07-05
1 2013-07-06 2013-07-06
I want to catch hours worked ((time_end- time_initial) - suspension) (if i have 1 day of suspension is one unit of hour example: Juan Worked 3 hours per day and he has 1 day of suspension on june and one day of suspension on july. So i assume in one month he works 3*20 (Hours*dayworked) - 3 hours june and 3 hours july
How can i get this result
id_emp name ` June-2013 July-2013
1 Juan 57 (hours Worked) 57 (hours Worked)
2 Francisco 24 (hours worked) 27 (hours worked)
3 Sebastián
6 Roberto

Here is the SQLFiddel Demo
Below is the query which you can try
select EmpHrs.ID_EMP,
EmpHrs.Name,
(
(EmpHrs.NOOFHRS*20)-
(EmpHrs.NOOFHRS*
JuneSuspension.MONTHSUSPENSION)
) as "June-2013",
(
(EmpHrs.NOOFHRS*20)-
(EmpHrs.NOOFHRS*
JulySuspension.MONTHSUSPENSION)
) as "July-2013"
from
(
select ID_EMP,NAME,
Extract(Hours from time_end-time_initial)+
Extract(Minutes from time_end-time_initial)/60 as NoOfHrs
from schedule
) EmpHrs
Left join
(select ID_EMP,to_char(to_timestamp (Extract(Month from suspension_initial)::text, 'MM'),'Mon') as MonthIni,(suspension_end::date - suspension_initial::date)+1 MonthSuspension
from suspension
where Extract(Month from suspension_initial) = 6) JuneSuspension
On JuneSuspension.ID_EMP = EmpHrs.ID_EMP
Left join
(select ID_EMP,to_char(to_timestamp (Extract(Month from suspension_initial)::text, 'MM'),'Mon') as MonthIni,(suspension_end::date - suspension_initial::date)+1 MonthSuspension
from suspension
where Extract(Month from suspension_initial) = 7) JulySuspension
On JulySuspension.ID_EMP = EmpHrs.ID_EMP

Related

count number of records by month over the last five years where record date > select month

I need to show the number of valid inspectors we have by month over the last five years. Inspectors are considered valid when the expiration date on their certification has not yet passed, recorded as the month end date. The below SQL code is text of the query to count valid inspectors for January 2017:
SELECT Count(*) AS RecordCount
FROM dbo_Insp_Type
WHERE (dbo_Insp_Type.CERT_EXP_DTE)>=#2/1/2017#);
Rather than designing 60 queries, one for each month, and compiling the results in a final table (or, err, query) are there other methods I can use that call for less manual input?
From this sample:
Id
CERT_EXP_DTE
1
2022-01-15
2
2022-01-23
3
2022-02-01
4
2022-02-03
5
2022-05-01
6
2022-06-06
7
2022-06-07
8
2022-07-21
9
2022-02-20
10
2021-11-05
11
2021-12-01
12
2021-12-24
this single query:
SELECT
Format([CERT_EXP_DTE],"yyyy/mm") AS YearMonth,
Count(*) AS AllInspectors,
Sum(Abs([CERT_EXP_DTE] >= DateSerial(Year([CERT_EXP_DTE]), Month([CERT_EXP_DTE]), 2))) AS ValidInspectors
FROM
dbo_Insp_Type
GROUP BY
Format([CERT_EXP_DTE],"yyyy/mm");
will return:
YearMonth
AllInspectors
ValidInspectors
2021-11
1
1
2021-12
2
1
2022-01
2
2
2022-02
3
2
2022-05
1
0
2022-06
2
2
2022-07
1
1
ID
Cert_Iss_Dte
Cert_Exp_Dte
1
1/15/2020
1/15/2022
2
1/23/2020
1/23/2022
3
2/1/2020
2/1/2022
4
2/3/2020
2/3/2022
5
5/1/2020
5/1/2022
6
6/6/2020
6/6/2022
7
6/7/2020
6/7/2022
8
7/21/2020
7/21/2022
9
2/20/2020
2/20/2022
10
11/5/2021
11/5/2023
11
12/1/2021
12/1/2023
12
12/24/2021
12/24/2023
A UNION query could calculate a record for each of 50 months but since you want 60, UNION is out.
Or a query with 60 calculated fields using IIf() and Count() referencing a textbox on form for start date:
SELECT Count(IIf(CERT_EXP_DTE>=Forms!formname!tbxDate,1,Null)) AS Dt1,
Count(IIf(CERT_EXP_DTE>=DateAdd("m",1,Forms!formname!tbxDate),1,Null) AS Dt2,
...
FROM dbo_Insp_Type
Using the above data, following is output for Feb and Mar 2022. I did a test with Cert_Iss_Dte included in criteria and it did not make a difference for this sample data.
Dt1
Dt2
10
8
Or a report with 60 textboxes and each calls a DCount() expression with criteria same as used in query.
Or a VBA procedure that writes data to a 'temp' table.

SQL last 6 months visits

Purpose of the report: Identify patients who did not have dental cleanings in the last 6 months
What would be the best approach to write a sql script?
Patients table
patient_id
patient_name
11
Jason Strong
22
Ryan Smith
33
Casey Hammer
Visits table
v_id
patient_id
reason_visit
date_of_visit
1
11
medical
01/01/2021
2
22
dental cleaning
11/10/2020
3
22
annual
01/01/2021
4
11
dental cleaning
5/10/2021
5
11
annual
5/1/2021
Expected
patient_id
patient_name
22
Ryan Smith
33
Casey Hammer
Casey is on the list because she is not in the visits table meaning she never received a cleaning from our office.
Ryan Smith is on the list because it is time for his cleaning.
I was also thinking what if the patient did not have an appointment in the last 6 months but had an future appointment for dental cleaning. I would want to exclude that.
in postgresql:
select * from Patients p
where not exists (
select 1 from Visits v
where v.patient_id = p.patient_id
and reason_visit = 'dental cleaning'
and date_of_visit < now() - interval '6 month'
)
in sql server replace now() - interval '6 month' with dateadd(month, -6,getdate())
in mysql date_add(now(), interval -6 month)

Query to find active days per year to find revenue per user per year

I have 2 dimension tables and 1 fact table as follows:
user_dim
user_id
user_name
user_joining_date
1
Steve
2013-01-04
2
Adam
2012-11-01
3
John
2013-05-05
4
Tony
2012-01-01
5
Dan
2010-01-01
6
Alex
2019-01-01
7
Kim
2019-01-01
bundle_dim
bundle_id
bundle_name
bundle_type
bundle_cost_per_day
101
movies and TV
prime
5.5
102
TV and sports
prime
6.5
103
Cooking
prime
7
104
Sports and news
prime
5
105
kids movie
extra
2
106
kids educative
extra
3.5
107
spanish news
extra
2.5
108
Spanish TV and sports
extra
3.5
109
Travel
extra
2
plans_fact
user_id
bundle_id
bundle_start_date
bundle_end_date
1
101
2019-10-10
2020-10-10
2
107
2020-01-15
(null)
2
106
2020-01-15
2020-12-31
2
101
2020-01-15
(null)
2
103
2020-01-15
2020-02-15
1
101
2020-10-11
(null)
1
107
2019-10-10
2020-10-10
1
105
2019-10-10
2020-10-10
4
101
2021-01-01
2021-02-01
3
104
2020-02-17
2020-03-17
2
108
2020-01-15
(null)
4
102
2021-01-01
(null)
4
103
2021-01-01
(null)
4
108
2021-01-01
(null)
5
103
2020-01-15
(null)
5
101
2020-01-15
2020-02-15
6
101
2021-01-01
2021-01-17
6
101
2021-01-20
(null)
6
108
2021-01-01
(null)
7
104
2020-02-17
(null)
7
103
2020-01-17
2020-01-18
1
102
2020-12-11
(null)
2
106
2021-01-01
(null)
7
107
2020-01-15
(null)
note: NULL bundle_end_date refers to active subscription.
user active days can be calculated as: bundle_end_date - bundle_start_date (for the given bundle)
total revenue per user could be calculated as : total no. of active days * bundle rate per day
I am looking to write a query to find revenue generated per user per year.
Here is what I have for the overall revenue per user:
select pf.user_id
, sum(datediff(day, pf.bundle_start_date, coalesce(pf.bundle_end_date, getdate())) * bd.price_per_day) total_cost_per_bundle
from plans_fact pf
inner join bundle_dim bd on bd.bundle_id = pf.bundle_id
group by pf.user_id
order by pf.user_id;
You need a 'year' table to help parse out each multi-year spanning row into it's seperate years. For each year, you need to also recalculate the start and end dates. That's what I do in the yearParsed cte in the code below. I hard code the years into the join statement that creates y. You probably will do it different but however you get those values will work.
After that, pretty much sum as you did before, just adding the year column to your grouping.
Aside from that, all I did was move the null coalesce logic to the cte to make the overall logic simpler.
with yearParsed as (
select pf.*,
y.year,
startDt = iif(pf.bundle_start_date > y.startDt, pf.bundle_start_date, y.startDt),
endDt = iif(ap.bundle_end_date < y.endDt, ap.bundle_end_date, y.endDt)
from plans_fact pf
cross apply (select bundle_end_date = isnull(pf.bundle_end_date, getdate())) ap
join (values
(2019, '2019-01-01', '2019-12-31'),
(2020, '2020-01-01', '2020-12-31'),
(2021, '2021-01-01', '2021-12-31')
) y (year, startDt, endDt)
on pf.bundle_start_date <= y.endDt
and ap.bundle_end_date >= y.startDt
)
select yp.user_id,
yp.year,
total_cost_per_bundle = sum(datediff(day, yp.startDt, yp.endDt) * bd.bundle_cost_per_day)
from yearParsed yp
join bundle_dim bd on bd.bundle_id = yp.bundle_id
group by yp.user_id,
yp.year
order by yp.user_id,
yp.year;
Now, if this is common, you should probably create a base-table for your 'year' table. But if it's not common, but for this report you don't want to have to keep coming back to hard-code the year information into the y table, you can do this:
declare #yearTable table (
year int,
startDt char(10),
endDt char(10)
);
with y as (
select year = year(min(pf.bundle_start_date))
from #plans_fact pf
union all
select year + 1
from y
where year < year(getdate())
)
insert #yearTable
select year,
startDt = convert(char(4),year) + '-01-01',
endDt = convert(char(4),year) + '-12-31'
from y;
and it will create the appropriate years for you. But you can see why creating a base table may be preferred if you have this or a similar need often.

SQL how to count but only count one instance if two columns match?

Wondering how to select from a table:
FIELDID personID purchaseID dateofPurchase
--------------------------------------------------
2 13 147 2014-03-21 00:00:00
3 15 165 2015-03-23 00:00:00
4 13 456 2018-03-24 00:00:00
5 1 133 2018-03-21 00:00:00
6 23 123 2013-03-22 00:00:00
7 25 456 2013-03-21 00:00:00
8 25 456 2013-03-23 00:00:00
9 22 456 2013-03-28 00:00:00
10 25 589 2013-03-21 00:00:00
11 82 147 1991-10-22 00:00:00
12 82 453 2003-03-22 00:00:00
I'd like to get a result table of two columns: weekday and the number of purchases of each weekday, but only count the distinct days of purchases if done by the same person on the same day - for example since personID 25 purchased two things on 2013-03-21, that should only count as one 'thursday' instead of 2.
Basically, if the personID and the dateofPurchase are the same for more than one row, only count it once is what I want.
Here is what I have currently: It does everything correctly except it will count the above scenario under the thursday twice, when I would only want to add one:
SELECT v.wkday as day, COUNT(*) as 'absences'
FROM dbo.AttendanceRecord pr CROSS APPLY
(VALUES (CASE WHEN DATEPART(WEEKDAY, date) IN (1, 7)
THEN 'Weekend'
ELSE DATENAME(WEEKDAY, date)
END)
) v(wkday)
GROUP BY v.wkday;
to clarify:
If an item is purchased for at least one puchaseID on a specific day they will be counted as purchased for that day, and do not need to be counted again for each new purchase ID on that day.
I think you want to count distinct persons, so that would be:
COUNT(DISTINCT personid) as absences
Note that single quotes are not appropriate around column aliases. If you need to escape them, use square braces.
EDIT:
If you want to count distinct person-days, then you can use:
COUNT(DISTINCT CONCAT(personid, ':', dateofpurchase) as absences

Logical error in selecting rows with correct output

I understand the basics but I am new to DBMSs and I'm learning in a course.
Here is the assignment question:
Write a query to display the number of sales that were made in the last 40 months with the below table:
SALEID SID SLDATE
1001 1 01-JAN-14
1002 5 02-JAN-14
1003 4 01-FEB-14
1004 1 01-MAR-14
1005 2 01-FEB-14
1006 1 01-JUN-15
My query is:
select count(sldate) as sale_count
from sale
where sldate >= add_months(sysdate, -40)
The output expected and that I get is:
SALE_COUNT
0
But I get an error message:
Error: Your query output matches expected result, but there are logical errors.
I'm not sure where I got the logic wrong.
The 'last 40 months' is ambiguous.
There are several interpretation of 'what does last n months from date x mean' and oracle''s add_months does not have monopoly on that (in fact most people would say it doesn't work as expected, just wait till the 30.06 and ask somebody 'what was the date a month ago' :) )
Imagine today is 20 of april.
Does 'last month' include 15, 20, 21, or 25 of march?
Does it include 02 of april?
That depends, someone could say that 'last month' is from 21 of march to 20 of april.
Someone could say, that 'last month' from 01.03 to today.
Someone could say, that 'last month' starts from 01.04.
Someone could say, that 'last month' means whole march, but not a single day of april.
It gets even trickier when 'today' is close to the end of the month, especially in march.
Don't be hard on yourself just because you couldn't read mind of someone who wrote the assignment ;)
I've wrote a query showing how different approches might yield different results.
CREATE OR REPLACE FUNCTION temp_can_subst_interval_months(p_date date, p_n_of_months number) RETURN NUMBER AS
V_date DATE;
BEGIN
V_Date := p_date - (NUMTOYMINTERVAL(p_n_of_months, 'month'));
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
with all_days as (
select to_date('2016-01-01', 'YYYY-MM-DD') + (level - 1) as d
from dual
connect by level < 1462
),
all_days_2 as (
select d date_of_query_being_run,
add_months(d, -40)as min_date_your_approach,
add_months(d, -40) + 1 as min_date_your_approach_2, -- same, but exclude the first day
trunc(add_months(d, -40), 'mm') as min_date_whole_month,
case when temp_can_subst_interval_months(d, 40) = 1 then
d - (interval '40' month)
else null
end as min_date_interval_approach
from all_days ad
order by ad.d
)
select ads.*
from all_days_2 ads
;
The most interesting results are when your approach differs from interval approach:
1 (sysdate) 2 (yours) 3 4 5 (interval)
31.01.2016 30.09.2012 01.10.2012 01.09.2012
29.02.2016 31.10.2012 01.11.2012 01.10.2012 29.10.2012
31.03.2016 30.11.2012 01.12.2012 01.11.2012
30.04.2016 31.12.2012 01.01.2013 01.12.2012 30.12.2012
29.06.2016 28.02.2013 01.03.2013 01.02.2013
30.06.2016 28.02.2013 01.03.2013 01.02.2013
31.08.2016 30.04.2013 01.05.2013 01.04.2013
30.09.2016 31.05.2013 01.06.2013 01.05.2013 30.05.2013
31.10.2016 30.06.2013 01.07.2013 01.06.2013
30.11.2016 31.07.2013 01.08.2013 01.07.2013 30.07.2013
31.01.2017 30.09.2013 01.10.2013 01.09.2013
28.02.2017 31.10.2013 01.11.2013 01.10.2013 28.10.2013
31.03.2017 30.11.2013 01.12.2013 01.11.2013
30.04.2017 31.12.2013 01.01.2014 01.12.2013 30.12.2013
29.06.2017 28.02.2014 01.03.2014 01.02.2014
30.06.2017 28.02.2014 01.03.2014 01.02.2014
31.08.2017 30.04.2014 01.05.2014 01.04.2014
30.09.2017 31.05.2014 01.06.2014 01.05.2014 30.05.2014
31.10.2017 30.06.2014 01.07.2014 01.06.2014
30.11.2017 31.07.2014 01.08.2014 01.07.2014 30.07.2014
31.01.2018 30.09.2014 01.10.2014 01.09.2014
28.02.2018 31.10.2014 01.11.2014 01.10.2014 28.10.2014
31.03.2018 30.11.2014 01.12.2014 01.11.2014
30.04.2018 31.12.2014 01.01.2015 01.12.2014 30.12.2014
29.06.2018 28.02.2015 01.03.2015 01.02.2015
30.06.2018 28.02.2015 01.03.2015 01.02.2015
31.08.2018 30.04.2015 01.05.2015 01.04.2015
30.09.2018 31.05.2015 01.06.2015 01.05.2015 30.05.2015
31.10.2018 30.06.2015 01.07.2015 01.06.2015
30.11.2018 31.07.2015 01.08.2015 01.07.2015 30.07.2015
31.01.2019 30.09.2015 01.10.2015 01.09.2015
28.02.2019 31.10.2015 01.11.2015 01.10.2015 28.10.2015
31.03.2019 30.11.2015 01.12.2015 01.11.2015
30.04.2019 31.12.2015 01.01.2016 01.12.2015 30.12.2015
30.06.2019 29.02.2016 01.03.2016 01.02.2016
31.08.2019 30.04.2016 01.05.2016 01.04.2016
30.09.2019 31.05.2016 01.06.2016 01.05.2016 30.05.2016
31.10.2019 30.06.2016 01.07.2016 01.06.2016
30.11.2019 31.07.2016 01.08.2016 01.07.2016 30.07.2016
Side note:
Maybe the table contains something like 'future expected sales' and they want you to filter out dates later than sysdate ;)?