Related
For tables patient and labh
patient
id lastname
19 patientone
20 patienttwo
patientid lastname loinc datetime numerical
19 patientone 4548-4 2014-05-15 00:00:00 6.5
19 patientone 4548-4 2015-05-15 00:00:00 7.5
19 patientone 4548-4 2016-05-15 00:00:00 3.5
19 patientone 4548-4 2017-05-15 00:00:00 5.5
19 patientone 5000-3 2018-05-15 00:00:00 123
20 patienttwo 4548-4 2013-05-15 00:00:00 2.5
20 patienttwo 4548-4 2012-05-15 00:00:00 1.5
20 patienttwo 4548-4 2011-05-15 00:00:00 9.5
20 patienttwo 4548-4 2010-05-15 00:00:00 3.5
Desired output:
patientid lastname datetime numerical
19 patientone 2017-05-15 00:00:00 5.5
20 patienttwo 2013-05-15 00:00:00 2.5
The labh table hold lab values(numerical), the type of lab (loinc) and when they were done (datetime). I'd like to query for the most recent value of loinc=4548-4 , and i'd like the output to show both the date and the value.
i've tried this below and it shows the most recent dates, but I can't see the values (numerical) at the same time. when I add the numerical column, the it shows all the values, not just the most recent.
Select Distinct patient.id, patient.lastname, Max(Date_Trunc('day', labh.datetime)) As "Date" From patient Inner Join labh On patient.id = labh.patientid Where labh.loinc = '4548-4' Group By patient.id, patient.lastname, patient.firstname Order By patient.id
you haven't selected the numerical column in your query. You can use CTE to store the data temporarily through ranking on pratition over patient id and ordering each partition on the basis of date.
So, according to this, you can try:
WITH summary AS (
SELECT p.id as "Patient ID",
p.lastname as "Patient Name",
l.datetime As "Date",
l.numerical as "Numerical",
ROW_NUMBER() OVER (PARTITION BY p.id
ORDER BY l.datetime DESC) AS rank
FROM patient p
Inner Join labh l
On p.id = l.patientid)
SELECT "Patient ID",
"Patient Name",
"Date",
"Numerical"
FROM summary
WHERE rank = 1;
And this will give you:
Patient ID
Patient Name
Date
Numerical
19
patientone
2017-05-15T00:00:00.000Z
5.5
20
patienttwo
2013-05-15T00:00:00.000Z
2.5
UPDATE
As you've updated the question and changed the expectation, the modified query will be nothing but adding a where condition inside cte construction:
WITH summary AS (
SELECT p.id as "Patient ID",
p.lastname as "Patient Name",
l.datetime As "Date",
l.numerical as "Numerical",
ROW_NUMBER() OVER (PARTITION BY p.id
ORDER BY l.datetime DESC) AS rank
FROM patient p
Inner Join labh l
On p.id = l.patientid
where l.loinc = '4548-4') -- Added this line
SELECT "Patient ID",
"Patient Name",
"Date",
"Numerical"
FROM summary
WHERE rank = 1;
This will give you the same result:
Patient ID
Patient Name
Date
Numerical
19
patientone
2017-05-15T00:00:00.000Z
5.5
20
patienttwo
2013-05-15T00:00:00.000Z
2.5
In order to achieve what you're looking for in Postgres (and other SQL RDBMSes), you need to essentially identify the max value and its corresponding primary key, then join it with the rest of the data set you are looking to retrieve:
SELECT patient.*, labh.*
FROM patient
JOIN labh
ON patient.id = labh.patientid
JOIN (SELECT patientid, max(datetime)
FROM labh
GROUP BY patientid) maxvals
ON maxvals.patientid = labh.patientid AND
maxvals.datetime = labh.datetime
I have a query which brings result of the every month. From this query I need to fetch sum of reading in a month and first and last reading of that month. I was able to do SUM using CASE however I'm unable to fetch the first and last reading, the condition to fetch this is that TYPE='YYY'
SELECT g.id, g.DATE_MONTH,
(CASE
WHEN g.TYPE ='XXX'
THEN (g.reading)
ELSE NULL
END ) AS fsum
FROM
(select to_char(DATE, 'Mon YYYY', 'en_US') DATE_MONTH ,
year(DATE) DATE_Y ,
month(DATE) DATE_M ,
min(DATE) as DATE_MIN ,
max(DATE) as DATE_MAX, id,sum(reading) AS reading, TYPE
from CXDATA
group by to_char(DATE, 'Mon YYYY', 'en_US'),
year(DATE), month(DATE),id, TYPE ) g
Data should be like below
ID------+READING+-------STARTDATE+-----+--TYPE
1010 250 05-Jan-2020 XXX
1010 500 12-Jan-2020 XXX
1010 680 20-Jan-2020 XXX
1011 100 08-Feb-2020 YYY
1011 340 11-Feb-2020 YYY
1011 180 12-Feb-2020 YYY
OUTPUT
-----------------------------------------
ID------+DATE_MONTH+----+FSUM+-----FIRSTREADING+-----LASTREADING+---TYPE
1010 JAN 2020 1430 NULL NULL XXX
1011 FEB 2020 NULL 100 180 YYY
Try this:
WITH
TAB (ID, READING, STARTDATE, TYPE) AS
(
VALUES
(1010, 250, DATE('2020-01-05'), 'XXX')
, (1010, 500, DATE('2020-01-12'), 'XXX')
, (1010, 680, DATE('2020-01-20'), 'XXX')
, (1011, 100, DATE('2020-02-08'), 'YYY')
, (1011, 340, DATE('2020-02-11'), 'YYY')
, (1011, 180, DATE('2020-02-12'), 'YYY')
)
SELECT
ID, DATE_MONTH, SUM(CASE TYPE WHEN 'XXX' THEN READING END) FSUM, FIRSTREADING, LASTREADING, TYPE
FROM
(
SELECT
ID
, to_char(STARTDATE, 'Mon YYYY', 'en_US') DATE_MONTH
, STARTDATE, READING, TYPE
, FIRST_VALUE (CASE TYPE WHEN 'YYY' THEN READING END, 'IGNORE NULLS') OVER (PARTITION BY ID, to_char(STARTDATE, 'Mon YYYY', 'en_US') ORDER BY STARTDATE) FIRSTREADING
, FIRST_VALUE (CASE TYPE WHEN 'YYY' THEN READING END, 'IGNORE NULLS') OVER (PARTITION BY ID, to_char(STARTDATE, 'Mon YYYY', 'en_US') ORDER BY STARTDATE DESC) LASTREADING
FROM TAB
)
GROUP BY ID, DATE_MONTH, FIRSTREADING, LASTREADING, TYPE;
Wondering how I can pass the RECEIVEDDATETIME to the statement below. What am I missing in my SQL statement?
I added an inner join to my CALENDAR table and TESTDATA table to pass the T.RECEIVEDDATETIME as the date from the original example thanks to Mark Barinstein.
This statement gets the C.WORKDATE from my tblCalendar, but I need to have the T.RECEIVEDDATETIME to pass to get my desired "DUEDATE".
I created a "tblCalendar" because I read that it was easier to reference a Calendar for true workdays...to exclude weekends and holidays and to account for leap years. Unsure if this is the best practice, but seemed straight forward to not code for exceptions. So I created the tblCalendar that includes ALL DATEs from 2017 until 2050 and holidays. The data below only represents partially January 2019 as I haven't found a way to attach a table here:
tblCalendar (partial)
DATE NUMDAYOFWK DAYOFWK HOLIDAY
01/01/2019 3 Tuesday YES
01/02/2019 4 Wednesday
01/03/2019 5 Thursday
01/04/2019 6 Friday
01/05/2019 7 Saturday
01/06/2019 1 Sunday
01/07/2019 2 Monday
01/08/2019 3 Tuesday
01/09/2019 4 Wednesday
01/10/2019 5 Thursday
01/11/2019 6 Friday
01/12/2019 7 Saturday
01/13/2019 1 Sunday
01/14/2019 2 Monday
01/15/2019 3 Tuesday
01/16/2019 4 Wednesday
01/17/2019 5 Thursday
01/18/2019 6 Friday
01/19/2019 7 Saturday
01/20/2019 1 Sunday
01/21/2019 2 Monday YES
The tblTestData table holds the core data where I reference all fields needed for my reports.
tblTestData Columns (partial) - DeliveryDays would reference the 2nd parameter BusDayAdd that was noted in the previous SQL.
ID RECEIVEDDATE DeliveryDays Address
T-20190116-255 01/16/2019 2 1234 Address
T-20190117-255 01/17/2019 2 3657 Address
T-20190118-222 01/18/2019 2 9999 Address
T-20190119-255 01/19/2019 2
T-20190120-255 01/20/2019
T-20190121-255 01/21/2019
T-20190303-1 03/03/2019
The Desired end results would look like the following taking into account the RECEIVEDDATETIME in my tblTestData and reference the tblCalendar table to exclude weekends and holidays to give the correct due date.
ID RECEIVEDDATE DeliveryDays DueDate Address
T-20190116-255 1/16/2019 2 1/18/2019 1234 Address
T-20190117-255 1/17/2019 2 1/22/2019 3657 Address
T-20190118-222 1/18/2019 2 1/23/2019 9999 Address
T-20190119-255 1/19/2019 2 1/23/2019 10000 Address
T-20190120-255 1/20/2019 2 1/23/2019 10001 Address
T-20190121-255 1/21/2019 2 1/23/2019 10002 Address
T-20190121-256 1/22/2019 2 1/24/2019 10003 Address
T-20190303-1 3/3/2019 3 3/6/2019 10004 Address
T-20190121-257 3/15/2019 7 3/26/2019 10005 Address
I have tried various statements by rewriting the code to wrap the SQL string to pass the table "RECEIVEDDATETIME", but each time the "DUEDATE" comes back {NULL}.
SELECT T.ID, VARCHAR_format(T.RECEIVEDDATETIME, 'MM/DD/YYYY') RECDATE,
(select VARCHAR_FORMAT(WORKDATE,'MM/DD/YYYY') DUEDATE
from
(Select
WORKDATE, T.RECEIVEDDATETIME,
sum(case when C.HOLIDAY='YES' or C.NUMDAYOFWK in (7,1) then 0 else 1 end) over (order by C.WORKDATE) BUSDAYADD
from tblCALENDAR C
--ADDED INNER JOIN TO GET T.RECEIVEDDATETIME TO FEED AUTOMATICALLY FROM TESTDATA TABLE
INNER JOIN TESTDATA T
ON
VARCHAR_FORMAT(C.WORKDATE, 'MM/DD/YYYY') = VARCHAR_FORMAT(T.RECEIVEDDATETIME,'MM/DD/YYYY')
where C.WORKDATE > VARCHAR_FORMAT(T.RECEIVEDDATETIME,'MM/DD/YYYY')) -- 1-st PARAMETER TO CAPTURE RECEIVEDDATETIME
WHERE BUSDAYADD = ? -- 2-nd parameter to add the number of days needed to be added to RECEIVEDDATETIME
order by WORKDATE --3rd Parameter
fetch first 1 row only)
FROM TESTDATA T
WHERE ID = 'T-20190303-1'
When I run the SQL, I get {NULL} for my results for DUEDATE:
ID RECDATE DUEDATE
T-20190303-1 03/03/2019 {NULL}
The results should be:
ID RECDATE DUEDATE
T-20190303-1 03/03/2019 03/05/2019
Any help is appreciated.
You provided inconsistent data in both tables: your calendar ends too early for all records in the tblTestData table except the 1-st one.
Let me provide absolutely the same query, which I already sent earlier to your another question.
with tblCalendar (DATE, HOLIDAY) as (values
(date(to_date('01/16/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/17/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/18/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/19/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/20/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/21/2019', 'MM/DD/YYYY')), 'YES')
, (date(to_date('01/22/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/23/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/24/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/25/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/26/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/27/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/28/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/29/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/30/2019', 'MM/DD/YYYY')), '')
, (date(to_date('01/31/2019', 'MM/DD/YYYY')), '')
, (date(to_date('02/01/2019', 'MM/DD/YYYY')), '')
, (date(to_date('02/02/2019', 'MM/DD/YYYY')), '')
, (date(to_date('02/03/2019', 'MM/DD/YYYY')), '')
, (date(to_date('02/04/2019', 'MM/DD/YYYY')), '')
, (date(to_date('02/05/2019', 'MM/DD/YYYY')), '')
)
, tblTestData (ID, RECEIVEDDATE, DeliveryDays) as (values
('T-20190116-255', date(to_date('01/16/2019', 'MM/DD/YYYY')), 2)
, ('T-20190117-255', date(to_date('01/17/2019', 'MM/DD/YYYY')), 2)
, ('T-20190118-222', date(to_date('01/18/2019', 'MM/DD/YYYY')), 2)
, ('T-20190119-255', date(to_date('01/19/2019', 'MM/DD/YYYY')), 2)
, ('T-20190120-255', date(to_date('01/20/2019', 'MM/DD/YYYY')), 2)
, ('T-20190121-255', date(to_date('01/21/2019', 'MM/DD/YYYY')), 2)
, ('T-20190121-256', date(to_date('01/22/2019', 'MM/DD/YYYY')), 2)
, ('T-20190303-1' , date(to_date('01/23/2019', 'MM/DD/YYYY')), 3)
, ('T-20190121-257', date(to_date('01/24/2019', 'MM/DD/YYYY')), 7)
)
select m.*, t.date as DUEDATE
--, dayofweek(date) as DAYOFWK, dayname(date) as DAY
from tblTestData m
, table
(
select date
from table
(
select
date
, sum(case when HOLIDAY='YES' or dayofweek(date) in (7,1) then 0 else 1 end) over (order by date) as dn_
from tblCalendar t
where t.date > m.RECEIVEDDATE
)
where dn_ = m.DeliveryDays
fetch first 1 row only
) t;
The result is:
ID RECEIVEDDATE DAYS DUEDATE
-------------- ------------ ---- ----------
T-20190116-255 2019-01-16 2 2019-01-18
T-20190117-255 2019-01-17 2 2019-01-22
T-20190118-222 2019-01-18 2 2019-01-23
T-20190119-255 2019-01-19 2 2019-01-23
T-20190120-255 2019-01-20 2 2019-01-23
T-20190121-255 2019-01-21 2 2019-01-23
T-20190121-256 2019-01-22 2 2019-01-24
T-20190303-1 2019-01-23 3 2019-01-28
T-20190121-257 2019-01-24 7 2019-02-04
The SELECT of the DUEDATE is
INNER JOIN TESTDATA T
ON VARCHAR_FORMAT(C.WORKDATE, 'MM/DD/YYYY') = VARCHAR_FORMAT(T.RECEIVEDDATETIME,'MM/DD/YYYY')
where C.WORKDATE > VARCHAR_FORMAT(T.RECEIVEDDATETIME,'MM/DD/YYYY'))
This means you join on the Equality and then restrict to Workdate > Receiveddatetime
that might be the problem...
I have a Table with columns ProductId, DateofPurchase, Quantity.
I want a report in which week it belongs to.
Suppose if I give March Month I can get the quantity for the march month.
But I want as below if I give date as parameter.
Here Quantity available for March month on 23/03/2018 is 100
Material Code Week1 Week2 Week3 Week4
12475 - - - 100
The logic is 1-7 first week, 8-15 second week, 16-23 third week, 24-30 fourth week
#Sasi, this can get you started. YOu will need to use CTE to build a template table that describes what happens yearly. Then using your table with inner join you can link it up and do a pivot to group the weeks.
Let me know if you need any tweaking.
DECLARE #StartDate DATE='20180101'
DECLARE #EndDate DATE='20180901'
DECLARE #Dates TABLE(
Workdate DATE Primary Key
)
DECLARE #tbl TABLE(ProductId INT, DateofPurchase DATE, Quantity INT);
INSERT INTO #tbl
SELECT 12475, '20180623', 100
;WITH Dates AS(
SELECT Workdate=#StartDate,WorkMonth=DATENAME(MONTH,#StartDate),WorkYear=YEAR(#StartDate), WorkWeek=datename(wk, #StartDate )
UNION ALL
SELECT CurrDate=DateAdd(WEEK,1,Workdate),WorkMonth=DATENAME(MONTH,DateAdd(WEEK,1,Workdate)),YEAR(DateAdd(WEEK,1,Workdate)),datename(wk, DateAdd(WEEK,1,Workdate)) FROM Dates D WHERE Workdate<#EndDate ---AND (DATENAME(MONTH,D.Workdate))=(DATENAME(MONTH,D.Workdate))
)
SELECT *
FROM
(
SELECT
sal.ProductId,
GroupWeek='Week'+
CASE
WHEN WorkWeek BETWEEN 1 AND 7 THEN '1'
WHEN WorkWeek BETWEEN 8 AND 15 THEN '2'
WHEN WorkWeek BETWEEN 16 AND 23 THEN '3'
WHEN WorkWeek BETWEEN 24 AND 30 THEN '4'
WHEN WorkWeek BETWEEN 31 AND 37 THEN '5'
WHEN WorkWeek BETWEEN 38 AND 42 THEN '6'
END,
Quantity
FROM
Dates D
JOIN #tbl sal on
sal.DateofPurchase between D.Workdate and DateAdd(DAY,6,Workdate)
)T
PIVOT
(
SUM(Quantity) FOR GroupWeek IN (Week1, Week2, Week3, Week4, Week5, Week6, Week7, Week8, Week9, Week10, Week11, Week12, Week13, Week14, Week15, Week16, Week17, Week18, Week19, Week20, Week21, Week22, Week23, Week24, Week25, Week26, Week27, Week28, Week29, Week30, Week31, Week32, Week33, Week34, Week35, Week36, Week37, Week38, Week39, Week40, Week41, Week42, Week43, Week44, Week45, Week46, Week47, Week48, Week49, Week50, Week51, Week52
/*add as many as you need*/)
)p
--ORDER BY
--1
option (maxrecursion 0)
Sample Data :
DECLARE #Products TABLE(Id INT PRIMARY KEY,
ProductName NVARCHAR(50))
DECLARE #Orders TABLE(ProductId INT,
DateofPurchase DATETIME,
Quantity BIGINT)
INSERT INTO #Products(Id,ProductName)
VALUES(1,N'Product1'),
(2,N'Product2')
INSERT INTO #Orders( ProductId ,DateofPurchase ,Quantity)
VALUES (1,'2018-01-01',130),
(1,'2018-01-09',140),
(1,'2018-01-16',150),
(1,'2018-01-24',160),
(2,'2018-01-01',30),
(2,'2018-01-09',40),
(2,'2018-01-16',50),
(2,'2018-01-24',60)
Query :
SELECT P.Id,
P.ProductName,
Orders.MonthName,
Orders.Week1,
Orders.Week2,
Orders.Week3,
Orders.Week4
FROM #Products AS P
INNER JOIN (SELECT O.ProductId,
SUM((CASE WHEN DATEPART(DAY,O.DateofPurchase) BETWEEN 1 AND 7 THEN O.Quantity ELSE 0 END)) AS Week1,
SUM((CASE WHEN DATEPART(DAY,O.DateofPurchase) BETWEEN 8 AND 15 THEN O.Quantity ELSE 0 END)) AS Week2,
SUM((CASE WHEN DATEPART(DAY,O.DateofPurchase) BETWEEN 16 AND 23 THEN O.Quantity ELSE 0 END)) AS Week3,
SUM((CASE WHEN DATEPART(DAY,O.DateofPurchase) >= 24 THEN O.Quantity ELSE 0 END)) AS Week4,
DATENAME(MONTH,O.DateofPurchase) AS MonthName
FROM #Orders AS O
GROUP BY O.ProductId,DATENAME(MONTH,O.DateofPurchase)) AS Orders ON P.Id = Orders.ProductId
Result :
-----------------------------------------------------------------------
| Id | ProductName | MonthNumber | Week1 | Week2 | Week3 | Week4 |
-----------------------------------------------------------------------
| 1 | Product1 | January | 130 | 140 | 150 | 160 |
| 2 | Product2 | January | 30 | 40 | 50 | 60 |
-----------------------------------------------------------------------
I have a table with the date columns (start_date, end_date) and I want to calculate the difference between these dates and grouped by the month.
I am able to get the datediff in days, but I do not know how to group this in month, any suggestions?
Table:
id Start_date End_date days
1234 2014-06-03 2014-07-05 32
12345 2014-02-02 2014-05-10 97
Expected results:
month diff_days
2 26
3 30
4 31
5 10
6 27
7 5
I think your expected output numbers are off a little. You might want to double-check.
I use a calendar table myself, but this query uses a CTE and date arithmetic. Avoiding the hard-coded date '2014-01-01' and the interval for 365 days is straightforward, but it makes the query harder to read, so I just used those values directly.
with your_data as (
select date '2014-06-03' as start_date, date '2014-07-05' as end_date union all
select '2014-02-02', '2014-05-10'
), calendar as (
select date '2014-01-01' + (n || ' days')::interval calendar_date
from generate_series(0, 365) n
)
select extract (month from calendar_date) calendar_month, count(*) from calendar
inner join your_data on calendar.calendar_date between start_date and end_date
group by calendar_month
order by calendar_month;
calendar_month count
--
2 27
3 31
4 30
5 10
6 28
7 5
As a rule of thumb, you should never group by the month alone--doing that risks grouping data from different years. This is a safer version that includes the year, and which also restricts output to a single calendar year.
with your_data as (
select date '2014-06-03' as start_date, date '2014-07-05' as end_date union all
select '2014-02-02', '2014-05-10'
), calendar as (
select date '2014-01-01' + (n || ' days')::interval calendar_date
from generate_series(0, 700) n
)
select extract (year from calendar_date) calendar_year, extract (month from calendar_date) calendar_month, count(*) from calendar
inner join your_data on calendar.calendar_date between start_date and end_date
where calendar_date between '2014-01-01' and '2014-12-31'
group by calendar_year, calendar_month
order by calendar_year, calendar_month;
SQL Fiddle
with min_max as (
select min(start_date) as start_date, max(end_date) as end_date
from t
), g as (
select daterange(d::date, (d + interval '1 month')::date, '[)') as r
from generate_series(
(select date_trunc('month', start_date) from min_max),
(select end_date from min_max),
'1 month'
) g(d)
)
select *
from (
select
to_char(lower(r), 'YYYY Mon') as "Month",
sum(upper(r) - lower(r)) as days
from (
select t.r * g.r as r
from
(
select daterange(start_date, end_date, '[]') as r
from t
) t
inner join
g on t.r && g.r
) s
group by 1
) s
order by to_timestamp("Month", 'YYYY Mon')
;
Month | days
----------+------
2014 Feb | 27
2014 Mar | 31
2014 Apr | 30
2014 May | 10
2014 Jun | 28
2014 Jul | 5
Range data types
Range functions and operators