How to substract between same date/time DB column in Oracle - sql

Oracle SQL In the table there are status given to the each row(arrived, ExamCompleted, VitalsTaken, discharged...)where we track information about patients in the hospital. It is required to find the number of hours patient spent inside the hospital. How can I substract one column with itself where status are different.
patient_id | status | created_on
7654 | arrived | 2022-09-18 07:22:46
7654 | examCompleted | 2022-09-18 09:35:26
7654 | vitalsTaken | 2022-09-20 02:41:55
7654 | discharged | 2022-09-20 07:42:33
I need to substract arrived status date/time from discharged status date/time Table have many other columns but these are the ones I need to work with. If there are null recorded for a created_on column (for few rows) how can I ignore that particular row while showing data.
Desired result is something like
patient_id | timeSpent
7654 | 48:19:13
Thank you in advance

Do a self join on the database table. The total time would be the value of column CREATED_ON when the STATUS is discharged minus the value of CREATED_ON when the STATUS is arrived for a given PATIENT_ID. The below SQL gives the result as a number of hours.
select A.PATIENT_ID
,(B.CREATED_ON - A.CREATED_ON) * 24 as STAY
from PATIENTS A
join PATIENTS B
on A.PATIENT_ID = B.PATIENT_ID
where A.STATUS = 'arrived'
and B.STATUS = 'discharged'
Refer to this db<>fiddle
Also refer to: https://asktom.oracle.com/Misc/DateDiff.html

Correct?? That what you expected?
SELECT DISTINCT patient_id,
MAX(created_on) OVER(PARTITION BY patient_id) - MIN(created_on) OVER(PARTITION BY patient_id)
FROM PATIENTS

Related

How to write SQLite code to show a specified code along with its associated codes from group_concat function?

I am trying to create a table where the code N09 is included, where a student was assigned a set of codes that contains N09, and "Status Complete" was yes. I wanted to use group_concat to see if each set contains N09. I saw a similar question to this but unfortunately, it did not satisfy my goal for Table 2 as it led to a problem. This problem I am experiencing is that it keeps showing 1 instead of 2, 3 for count. It also keeps showing N09, instead of N09 and its other codes from the set from the group_concat function. Is there a code to achieve my goal for Table 2 in SQLite? If my question is not clear, feel free to comment as I am new here.
Goal for Table 2:
Student ID
Status Complete
Status Date
Status Time
Code
Count
Group_Concat(Code)
1
yes
03/03/2021
00:00:00
N09
1
N09
2
yes
03/04/2021
10:03:10
N09
2
N09, M33
3
yes
03/04/2021
01:00:10
N09
3
N09, Y03, B55
Problem:
Student ID
Status Complete
Status Date
Status Time
Code
Count
Group_Concat(Code)
1
yes
03/03/2021
00:00:00
N09
1
N09
2
yes
03/04/2021
10:03:10
N09
1
N09
3
yes
03/04/2021
01:00:10
N09
1
N09
Sample Data:
Student ID
Status Complete
Status Date
Status Time
Code
1
yes
03/03/2021
00:00:00
N09
2
yes
03/04/2021
10:03:10
N09
2
yes
03/04/2021
10:03:10
M33
3
yes
03/04/2021
01:00:10
N09
3
yes
03/04/2021
01:00:10
Y03
3
yes
03/04/2021
01:00:10
B55
Code:
CREATE TABLE table2 AS
select Student_ID
,Status_Complete
,Status_Date
,Status_TIME
,Code
,count(Code) /*over (partition by Student_ID,Code)*/ as 'Count'
,GROUP_CONCAT(Code)
from table1
where Code in ('N09') AND Status_Complete = 'yes'
group by Student_ID, Status_Date, Status_TIME, 'Count'
HAVING 'Count'> 0
ORDER BY Student_ID;
You should group by Student_ID only since you want only 1 row for each student.
The columns Status_Date and Status_TIME of the results that you want seem to be the min values of each student (I assume that the dates have the proper format of YYYY-mm-dd which is the only valid date format for SQLite).
Also, the condition Code = 'N09' should be checked in the HAVING clause:
CREATE TABLE table2 AS
SELECT Student_ID, Status_Complete,
MIN(Status_Date) Status_Date,
TIME(MIN(Status_Date || ' ' || Status_TIME)) Status_TIME,
COUNT(*) count,
GROUP_CONCAT(Code) Codes
FROM table1
WHERE Status_Complete = 'yes'
GROUP BY Student_ID
HAVING SUM(Code = 'N09') > 0
ORDER BY Student_ID;
See the demo.
Never use single quotes for column names.
'Count' is a string literal when used in code. It never refers to a column alias.
the WHERE cluase you have excludes all columns that are not N09 and have the status completed, so switch zu a EXISTS clause
As Lennart points out, here the having is redundant, as all rows now will have at least the count of 1
CREATE TABLE table2 AS
select Student_ID
,Status_Complete
,Status_Date
,Status_TIME
,Code
,count(Code) /*over (partition by Student_ID,Code)*/ as 'Count'
,GROUP_CONCAT(Code)
from table1 t1
where EXISTS( (SELECT 1 FROM table1 WHERR Code in ('N09') AND Status_Complete = 'yes' AND Student_ID = t1.Student_ID)
group by Student_ID, Status_Date, Status_TIME
ORDER BY Student_ID;

PostgreSQL: How to join two tables using between date?

I really don't know how to ask this question of mine.
I'll illustrate it using two tables I needed to join.
TABLE_1
Name Date
John 01-01-2016
May 04-08-2015
Rose 10-25-2016
Mary 12-15-2015
Ruby 07-07-2017
TABLE_2
Signatory DateFrom DateTo
President 1 01-01-2015 12-31-2015
President 2 01-01-2016 12-31-2016
RESULT:
Name Date Signatory
John 01-01-2016 President 2
May 04-08-2015 President 1
Rose 10-25-2016 President 2
Mary 12-15-2015 President 1
Ruby 07-07-2017 NULL
All I need to check if the Date of Table_1 is within the DateFrom and DateTo of Table_2 to get the Signatory field.
How I can do that?
Thanks a lot! ^_^
Try this:
SELECT t1.*, t2.Signatory
FROM Table_1 AS t1
LEFT JOIN Table_2 AS t2
ON t1."Date" BETWEEN t2.DateFrom AND t2.DateTo
What you need is just a LEFT JOIN with BETWEEN in the ON clause in order to determine whether Date field of Table_1 falls within any [DateFrom, DateTo] interval of Table_2.
Demo here

Counting number of Days in the where clause

I have a SQL Server table that looks like this:
ID | Club Name | Booking Date | Submission Date
---+-------------+-------------------------+-------------------------
1 | Basketball | 2015-10-21 00:00:00.000 | 9/18/2015 3:23:42 PM
2 | Tennis | 2015-10-14 00:00:00.000 | 9/28/2015 1:50:25 PM
3 | Basketball | 2015-10-06 00:00:00.000 | 9/29/2015 11:08:20 AM
1 | Other | 2015-10-21 00:00:00.000 | 9/29/2015 11:08:39 AM
I want to know how many times each club did a submission less than 15 days from the booking date..
The solution I came up with was adding a new column and running a the datefiff function and storing the value in the new column.. Then just grouping by club name and adding a parameter for > 15 on the new column..
The question I have is: can this be done on the fly with out having to create the new column? how much would that affect performance if its done on the fly?
Yes, this can be done inline, in a query. In a database, you almost never want to store a calculated column, which is what that datediff column would be. Instead, you can do the math in the WHERE clause.
SELECT
*
FROM
myTable
WHERE
DATEDIFF(day, -15, BookingDate) >= SubmissionDate
I wrote that pretty quickly, so the date math might be going in the wrong direction (checking in the future instead of in the past) but playing with the above query should set you on the right path. Just keep in mind that, if this table gets very big, you're going to be doing a TON of DATEDIFFs and that can have a performance impact.
Something like this?
Declare #Table table (Id int,Club_Name varchar(50),Booking_Date datetime,Sumbission_Date datetime)
Insert #Table values
(1,'Basketball','2015-10-21 00:00:00.000','9/18/2015 3:23:42 PM'),
(2,'Tennis ','2015-10-14 00:00:00.000','9/28/2015 1:50:25 PM'),
(3,'Basketball','2015-10-06 00:00:00.000','9/29/2015 11:08:20 AM'),
(1,'Other ','2015-10-21 00:00:00.000','9/29/2015 11:08:39 AM')
Select Club_Name
,Submissions= count(*)
,Early = sum(case when datediff(DD,Sumbission_Date,Booking_Date)<15 then 1 else 0 end)
From #Table
Group By Club_Name
Returns
Club_Name Submissions Early
Basketball 2 1
Other 1 0
Tennis 1 0
Try this.
SELECT ID,
ClubName,
Sum(Value) As Ttle
FROM
(
SELECT ID,
ClubName,
COUNT(*) AS Value
FROM TableName
GROUP BY ID,
ClubName,
RecordDate
HAVING DATEDIFF(D, BookingDate, SubmissionDate) > 15
) Data
GROUP BY ID,
ClubName,
ORDER BY ttle DESC

MAX on group returns multiple values with same date but different times

I have followed many of the excellent pieces of advise on this site about selecting the MAX from a group of rows.
I have a history file and I only want the top date and comments for each project number. I am creating a derived table in a Boxi universe from this information. It all goes pretty well but if there are two entries for the same day but with different times they are both returned. This duplicates that entry on the subsequent report. Is there some way to make the MAX command go down to the time level of the date field?
Database is SQL Server 2005
-------------Sql used for derived table
Select
Projectno, Comment, CreatedOn
from
ReportHistory
Where
ReportHistory.ItemName=('ProjectCode1')
and
CreatedOn in(Select max(CreatedOn) FROM ReportHistory group by Projectno)
-------------------Example database
Projectno Comment Created on
1 Started 2013-01-04 11:04:00
2 Late 2013-01-06 11:22:00
3 Late 2013-01-07 11:06:00
1 On Time 2013-01-08 11:01:00 *these two both get selected*
1 Late 2013-01-08 12:05:00 *these two both get selected*
3 Back on schedule 2013-01-08 14:20:00
2 Still overdue 2013-01-09 09:01:00
MAX on a DATETIME data type do obviously take the time into account, that is not what's wrong with your query. The problem is that you are not ensuring that the max value for CreatedOn is for the correct ProjectNo. You could use analytical functions for this:
;WITH CTE AS
(
SELECT Projectno,
Comment,
CreatedOn,
ROW_NUMBER() OVER(PARTITION BY ProjectNo ORDER BY CreatedOn DESC) RN
FROM ReportHistory
WHERE ReportHistory.ItemName = 'ProjectCode1'
)
SELECT Projectno, Comment, CreatedOn
FROM CTE
WHERE RN = 1
Query if there are no same projectno with the same date:
SQLFIDDLEExample
SELECT h.Projectno,
h.Comment,
h.[Created on]
FROM ReportHistory h
WHERE h.[Created on] =(Select max(h2.[Created on])
FROM ReportHistory h2
WHERE h2.Projectno = h.Projectno )
ORDER BY h.Projectno
Result:
| PROJECTNO | COMMENT | CREATED ON |
-----------------------------------------------------------------
| 1 | Late | January, 08 2013 12:05:00+0000 |
| 2 | Still overdue | January, 09 2013 09:01:00+0000 |
| 3 | Back on schedule | January, 08 2013 14:20:00+0000 |
Query if there are same projectno with the same date:
SELECT h.Projectno,
MAX(h.Comment) AS Comment,
h.[Created on]
FROM ReportHistory h
WHERE h.[Created on] =(Select max(h2.[Created on])
FROM ReportHistory h2
WHERE h2.Projectno = h.Projectno )
GROUP BY h.Projectno,
h.[Created on]
ORDER BY h.Projectno
I think you receive copies when dates at different projects are identical.
For eg. add in your data (4, 'On Time', '2013-01-08 11:01:00')
Then result will be SQLFiddle
But you need this result SQLFiddle
SELECT *
FROM ReportHistory t
WHERE t.ItemName=('ProjectCode1')
AND EXISTS (
SELECT 1
FROM ReportHistory
WHERE projectNo = t.projectNo
GROUP BY projectNo
HAVING MAX(CreatedOn) = t.CreatedOn
)

How to create a pivot table by product by month in SQL

I have 3 tables:
users (id, account_balance)
grocery (user_id, date, amount_paid)
fishmarket (user_id, date, amount_paid)
Both fishmarket and grocery tables may have multiple occurrences for the same user_id with different dates and amounts paid or have nothing at all for any given user. I am trying to develop a pivot table of the following structure:
id | grocery_amount_paid_January | fishmarket_amount_paid_January
1 10 NULL
2 40 71
The only idea I can come with is to create multiple left joins, but this should be wrong since there will be 24 joins (per each month) for each product. Is there a better way?
I have provided a lot of answers on crosstab queries in PostgreSQL lately. Sometimes a "plain" query like the following does the job:
WITH x AS (SELECT '2012-01-01'::date AS _from
,'2012-12-01'::date As _to) -- provide date range once in CTE
SELECT u.id
,to_char(m.mon, 'MM.YYYY') AS month_year
,g.amount_paid AS grocery_amount_paid
,f.amount_paid AS fishmarket_amount_paid
FROM users u
CROSS JOIN (SELECT generate_series(_from, _to, '1 month') AS mon FROM x) m
LEFT JOIN (
SELECT user_id
,date_trunc('month', date) AS mon
,sum(amount_paid) AS amount_paid
FROM x, grocery -- CROSS JOIN with a single row
WHERE date >= _from
AND date < (_to + interval '1 month')
GROUP BY 1,2
) g ON g.user_id = u.id AND m.mon = g.mon
LEFT JOIN (
SELECT user_id
,date_trunc('month', date) AS mon
,sum(amount_paid) AS amount_paid
FROM x, fishmarket
WHERE date >= _from
AND date < (_to + interval '1 month')
GROUP BY 1,2
) f ON f.user_id = u.id AND m.mon = g.mon
ORDER BY u.id, m.mon;
produces this output:
id | month_year | grocery_amount_paid | fishmarket_amount_paid
---+------------+---------------------+------------------------
1 | 01.2012 | 10 | NULL
1 | 02.2012 | NULL | 65
1 | 03.2012 | 98 | 13
...
2 | 02.2012 | 40 | 71
2 | 02.2012 | NULL | NULL
Major points
The first CTE is for convenience only. So you have to type your date range once only. You can use any date range - as long as it's dates with the first of the month (rest of the month will be included!). You could add date_trunc() to it, but I guess you can keep the urge to use invalid dates in check.
First CROSS JOIN users to the result of generate_series() (m) which provides one row per month in your date range. You have learned in your last question how that results in multiple rows per user.
The two subqueries are identical twins. Use WHERE clauses that operate on the base column, so it can utilize an index - which you should have if your table runs over many years (no use for only one or two years, a sequential scan will be faster):
CREATE INDEX grocery_date ON grocery (date);
Then reduce all dates to the first of the month with date_trunc() and sum amount_paid per user_id and the resulting mon.
LEFT JOIN the result to the base table, again by user_id and the resulting mon. This way, rows are neither multiplied nor dropped. You get one row per user_id and month. Voilá.
BTW, I'd never use a column name id. Call it user_id in the table users as well.