How do I get the value associated with a MIN or MAX - sql

I'm in the middle of creating a query and have it where I need the other values, however I am pulling a MIN and MAX date for individual patient_id. I'm wondering how I would go about how I would pull a value associated with that MIN or MAX date as well? I'm looking for a value the column provider_id which will show which doctor they saw on that MIN or MAX date. Here is what I have so far:
WITH test AS (
SELECT patient_id,
clinic,
SUM(amount) AS production,
MIN(tran_date) AS first_visit,
MAX(tran_date) AS last_visit
FROM transactions
WHERE impacts='P'
GROUP BY patient_id, clinic)
SELECT w.patient_id,
w.clinic,
p.city,
p.state,
p.zipcode,
p.sex,
w.production,
w.first_visit,
w.last_visit
FROM test w
LEFT JOIN patient p
ON (w.patient_id=p.patient_id AND w.clinic=p.clinic)

I believe that this will get what you're looking for:
;WITH CTE_Transactions AS (
SELECT DISTINCT
patient_id,
clinic,
SUM(amount) OVER (PARTITION BY patient_id, clinic) AS production,
FIRST_VALUE(tran_date) OVER (PARTITION BY patient_id, clinic ORDER BY tran_date) AS first_visit,
FIRST_VALUE(provider_id) OVER (PARTITION BY patient_id, clinic ORDER BY tran_date) AS first_provider_id,
LAST_VALUE(tran_date) OVER (PARTITION BY patient_id, clinic ORDER BY tran_date) AS last_visit,
LAST_VALUE(provider_id) OVER (PARTITION BY patient_id, clinic ORDER BY tran_date) AS last_provider_id,
ROW_NUMBER() OVER (PARTITION BY patient_id, clinic ORDER BY tran_date) AS row_num
FROM Transactions
WHERE impacts='P'
)
SELECT
w.patient_id,
w.clinic,
p.city,
p.state,
p.zipcode,
p.sex,
w.production,
w.first_visit,
w.last_visit
FROM
CTE_Transactions W
LEFT JOIN Patient P ON
W.patient_id = P.patient_id AND
W.clinic = P.clinic
INNER JOIN Provider FIRST_PROV ON
FIRST_PROV.provider_id = W.first_provider_id
INNER JOIN Provider LAST_PROV ON
LAST_PROV.provider_id = W.last_provider_id
WHERE
W.row_num = 1

I assume you are referring to the CTE. You can use conditional aggregation along with window functions. For instance, to get the amount for the first visit:
WITH test AS (
SELECT patient_id, clinic,
SUM(amount) AS production,
MIN(tran_date) AS first_visit,
MAX(tran_date) AS last_visit,
SUM(CASE WHEN tran_date = min_tran_date THEN amount END) as first_amount
FROM (SELECT t.*,
MIN(trans_date) OVER (PARTITION BY patient_id, clinic) as min_tran_date
FROM transactions
WHERE impacts = 'P'
) t
GROUP BY patient_id, clinic
)

Related

SQL - get start & end balance for each member each year

so I'd like to effectively get for each year the starting and end balance for each member for every year there is a record. for example the below would give me the latest balance for each member each year based on the date column
SELECT
T.MemberID,
T.DateCol,
T.Amount
FROM
(SELECT T.MemberID,
T.DateCol,
Amount,
ROW_NUMBER() OVER (PARTITION BY MemberID,
YEAR(DateCol)
ORDER BY
DateCol desc) AS seqnum
FROM
Tablet T
GROUP BY DateCol, MemberID, Amount
) T
WHERE
seqnum = 1 AND
MemberID = '1000009'
and the below would give me the earliest balance for each year
SELECT
T.MemberID,
T.DateCol,
T.Amount
FROM
(SELECT T.MemberID,
T.DateCol,
Amount,
ROW_NUMBER() OVER (PARTITION BY MemberID,
YEAR(DateCol)
ORDER BY
DateCol) AS seqnum
FROM
Tablet T
GROUP BY DateCol, MemberID, Amount
) T
WHERE
seqnum = 1 AND
MemberID = '1000009'
This would give me a result set like the below, column titles (MemberID, Date, Amount)
What I'm looking for is one query which is done by YEAR, MEMBERID, STARTBALANCE, ENDBALANCE as the columns. And would look like the below
What would be the best way to go about this?
commented above

Psql : Get Min, Max and Count records for each partner' invoice, and last payment

I have a table invoice like this :
id, partner_id, number, invoice_date
And a Payment Table like this:
id, payment_date, partner_id
I want to get min and max for both number & invoice_date, and count invoices, and last payment for each partner, something like this :
partner_id, min number, min date, max number, max date, count, last_pay
1, INV-2017-003, 02-01-2017, INV-2020-010, 01-01-2020, 142, 02-12-2019
5, INV-2019-124, 05-03-2019, INV-2020-005, 01-01-2020, 150, 01-01-2020
....
You can join those three tables including partners and grouping by parners' id column along with related aggregations :
select pr.id, min(invoice_date), max(invoice_date), count(*), max(payment_date) as last_pay
from partners pr
left join invoices i on i.partner_id = pr.id
left join payments p on p.partner_id = pr.id
group by pr.id
Update : You can use min() over (), max() over () and row_number() analytic functions to get the desired code depending on max and min dates :
select *
from
(
select pr.id,
min(invoice_date) over (partition by pr.id order by invoice_date) as min_invoice_date,
max(invoice_date) over (partition by pr.id order by invoice_date desc) as max_invoice_date,
max(code) over (partition by pr.id order by invoice_date desc) as max_code,
min(code) over (partition by pr.id order by invoice_date) as min_code,
count(*) over (partition by pr.id) as cnt,
max(payment_date) over (partition by pr.id) as last_pay,
row_number() over (partition by pr.id order by invoice_date desc) as rn
from partners pr
left join invoices i on i.partner_id = pr.id
left join payments p on p.partner_id = pr.id
) q
where rn = 1
Why isn't this simple aggregation?
select i.partner_id,
min(i.number) as min_number,
min(i.invoice_date) as min_invoice_date,
max(i.number) as min_number,
max(i.invoice_date) as min_invoice_date,
count(distinct i.invoice_id) as num_invoices,
max(p.payment_date) as max_payment_date
from invoices i left join
payments p
on p.invoice_id = i.invoice_id
group by i.partner_id;
If you want the number on the earliest invoice (and min() doesn't work), then you can do this with a "first" aggregation function. Unfortunately, Postgres doesn't directly support one. But it does through array functions:
select i.partner_id,
(array_agg(i.number order by i.invoice_date asc))[1] as min_number,
min(i.invoice_date) as min_invoice_date,
(array_agg(i.number order by i.invoice_date desc))[1] as min_number,
max(i.invoice_date) as min_invoice_date,
count(distinct i.invoice_id) as num_invoices,
max(p.payment_date) as max_payment_date
from invoices i left join
payments p
on p.partner_id = i.partner_id
group by i.partner_id;
This is similar to #BarbarosĂ–zhan, but calculates the min/max before the join (if you got multiple rows per partner for both invoices and payments the COUNT will be wrong otherwise). Additionally there's only a single PARTTION/ORDER which should result in a more efficient plan.
SELECT i.*, p.last_pay
FROM
( -- 1st row has all the min values = filtered using row_number
SELECT
partner_id
,number AS min_code
,invoice_date AS min_invoice_date
-- value from row with max date
,Last_Value(number)
Over (PARTITION BY partner_id
ORDER BY invoice_date
ROWS BETWEEN Unbounded Preceding AND Unbounded Following) AS max_code
,Last_Value(invoice_date)
Over (PARTITION BY partner_id
ORDER BY invoice_date
ROWS BETWEEN Unbounded Preceding AND Unbounded Following) AS max_invoice_date
,Count(*)
Over (PARTITION BY partner_id) AS Cnt
,Row_Number()
Over (PARTITION BY partner_id ORDER BY invoice_date) AS rn
FROM invoices
) AS i
LEFT JOIN
( -- max payment date per partner
SELECT partner_id, Max(payment_date) AS last_pay
FROM payments
GROUP BY partner_id
) AS p
ON p.partner_id = i.partner_id
WHERE i.rn = 1

Incremental count of duplicates

The following query displays duplicates in a table with the qty alias showing the total count, eg if there are five duplicates then all five will have the same qty = 5.
select s.*, t.*
from [Migrate].[dbo].[Table1] s
join (
select [date] as d1, [product] as h1, count(*) as qty
from [Migrate].[dbo].[Table1]
group by [date], [product]
having count(*) > 1
) t on s.[date] = t.[d1] and s.[product] = t.[h1]
ORDER BY s.[product], s.[date], s.[id]
Is it possible to amend the count(*) as qty to show an incremental count so that five duplicates would display 1,2,3,4,5?
The answer to your question is row_number(). How you use it is rather unclear, because you provide no guidance, such as sample data or desired results. Hence this answer is rather general:
select s.*, t.*,
row_number() over (partition by s.product order by s.date) as seqnum
from [Migrate].[dbo].[Table1] s join
(select [date] as d1, [product] as h1, count(*) as qty
from [Migrate].[dbo].[Table1]
group by [date], [product]
having count(*) > 1
) t
on s.[date] = t.[d1] and s.[product] = t.[h1]
order by s.[product], s.[date], s.[id];
The speculation is that the duplicates are by product. This enumerates them by date. Some combination of the partition by and group by is almost certainly what you need.

Query returns incorrect result

MY input table is patient_ID, and Appt_Resource_ID (Doctor) ( the second table is just getting the patient name )
Patient_ID Appt_Resource_ID
88299 47
88299 1
88299 40
88299 40
88299 40
88299 40
I am running an sql that is meant to write an output row for the patient_id and the Doctor_ID that occurs the most, in this case sb 40. But it's outputting Doctor_ID 1. Other cases I checked are doing correctly.
This is the query:
select distinct A.Patient_id, P.Patient_name, b.Appt_resource_id
from [PM].[vwGenPatApptInfo] A
inner join
(
select top 100 percent patient_id, Appt_resource_id, count(Appt_resource_id) as DR_count,
row_number() over (partition by patient_id order by count(*) desc) as seqnum
from [PM].[vwGenPatApptInfo]
where Patient_ID is NOT NULL
group by patient_id,Appt_resource_id
order by patient_id, seqnum
) B on B.Patient_ID = A.Patient_ID
and B.seqnum = 1
inner join [PM].[vwGenPatInfo] P on A.Patient_id = P.Patient_id
where A.Appt_DateTime >= DATEADD(yyyy, -2, GETDATE()) and A.Appt_Cancelled_Date is NULL
But the results are this:
Patient_ID Appt_Resource_ID
88299 1
Try this.
SELECT TOP 1 *
FROM (
SELECT Patient_ID, Appt_Resource_ID, COUNT(*) AS theCount
FROM vwGenPatApptInfo
GROUP BY Patient_ID, Appt_Resource_ID
WHERE ApptDateTime >= DATEADD(yyyy, -2, GETDATE()) and Appt_Cancelled_Date is NULL
) b
JOIN vwGenPatInfo p ON p.Patient_ID = b.Patient_ID
ORDER BY theCount DESC
This is not really an answer, sorry.
Your subquery finds the doctor that occurs most often for a patient. (If you want the most often occurring patient/doctor pair instead, then remove the partition clause from ROW_NUMBER.)
But then you join vwGenPatApptInfo again (as A). Why? And your join is even incomplete, because you only join by Patient_ID and not by Appt_resource_id, too. You are thus combining every most occurring doctor with other doctor records for the patient in a certain date range. These you dismiss again with DISTINCT.
I don't see however how your query gets the wrong doctor. If it had been A.Appt_resource_id you selected, that would have been a reason, but b.Appt_resource_id should be the most often occurring doctor.
I hope my comments above help you, though. Maybe you can simplify your query somehow and maybe get thus even rid of the error.
Good luck!
It might be not the most ideal solution, but try this:
;WITH CTE1 as (
SELECT Patient_ID, Appt_Resource_ID
, COUNT(*) OVER (PARTITION BY Patient_ID, Appt_Resource_ID) as CNT
FROM [PM].[vwGenPatApptInfo]
), CTE2 as (
SELECT Patient_ID, Appt_Resource_ID, CNT, RANK() OVER
(PARTITION BY Patient_ID ORDER BY CNT DESC) AS Rank
FROM CTE1
), CTE3 as (
SELECT Patient_ID, Appt_Resource_ID, CNT, Rank
FROM CTE2
WHERE Rank = 1
GROUP BY Patient_ID, Appt_Resource_ID, CNT, Rank
)
select distinct A.Patient_id, P.Patient_name, b.Appt_resource_id, B.CNT
from [PM].[vwGenPatApptInfo] A
INNER JOIN CTE3 as B on B.Patient_ID = A.Patient_ID
inner join [PM].[vwGenPatInfo] P on A.Patient_id = P.Patient_id
where A.Appt_DateTime >= DATEADD(yyyy, -2, GETDATE()) and A.Appt_Cancelled_Date is NULL

Getting the value of a previous record using ROW_NUMBER() in SQL Server

Hopefully this is easy enough for those more experienced in SQL Server.
I have a table to customer loan activity data which is updated whenever an action happens on their account. For example if their limit is increased, a new record will be created with their new limit. I want to be able to create a listing of their activity where the activity amount is their new limit subtracting whatever their previous limit was.
At the moment I have the following but I'm struggling to work out how to access that previous record.
SELECT
CUSTOMER
,LEDGER
,ACCOUNT
,H.AMOUNT - COALESCE(X.AMOUNT, 0)
FROM
dbo.ACTIVITY H WITH (NOLOCK)
LEFT OUTER JOIN
(SELECT
CUSTOMER
,LEDGER
,ACCOUNT
,ACTIVITY_DATE
,AMOUNT
,ROW_NUMBER() OVER (PARTITION BY CUSTOMER, LEDGER, ACCOUNT ORDER BY ACTIVITY_DATE ASC) AS ROW_NUMBER
FROM
dbo.ACTIVITY WITH (NOLOCK)) X ON H.CUSTOMER = X.CUSTOMER
AND H.LEDGER = X.LEDGER
AND H.ACCOUNT = X.ACCOUNT
So basically I only want to subtract x.amount if it's the previous record but I'm not sure how to do this when I don't know what day it happened.
I thought Row_Number() would help me but I'm still a bit stumped.
Hope you hear from you all soon :)
Cheers
Here's a query that will only pass through dbo.Activity ONCE
SELECT H.CUSTOMER
,H.LEDGER
,H.ACCOUNT
,MAX(H.ACTIVITY_DATE) ACTIVITY_DATE
,SUM(CASE X.I WHEN 1 THEN AMOUNT ELSE -AMOUNT END) AMOUNT
FROM (SELECT CUSTOMER
,LEDGER
,ACCOUNT
,ACTIVITY_DATE
,AMOUNT
,ROW_NUMBER() OVER (PARTITION BY CUSTOMER, LEDGER, ACCOUNT ORDER BY ACTIVITY_DATE DESC) AS ROW_NUMBER
FROM dbo.ACTIVITY WITH (NOLOCK)
) H
CROSS JOIN (select 1 union all select 2) X(I)
WHERE ROW_NUMBER - X.I >= 0
GROUP BY H.CUSTOMER
,H.LEDGER
,H.ACCOUNT
,ROW_NUMBER - X.I;
And here's the DDL/DML for some data I used to test
CREATE TABLE dbo.ACTIVITY(CUSTOMER int, LEDGER int, ACCOUNT int, ACTIVITY_DATE datetime, AMOUNT int)
INSERT dbo.ACTIVITY select
1,2,3,GETDATE(),123 union all select
1,2,3,GETDATE()-1,16 union all select
1,2,3,GETDATE()-2,12 union all select
1,2,3,GETDATE()-3,1 union all select
4,5,6,GETDATE(),1000 union all select
4,5,6,GETDATE()-6,123 union all select
7,7,7,GETDATE(),99;
Alternatives
A more traditional approach using a subquery to get the previous row:
SELECT CUSTOMER, LEDGER, ACCOUNT, ACTIVITY_DATE,
AMOUNT - ISNULL((SELECT TOP(1) I.AMOUNT
FROM dbo.ACTIVITY I
WHERE I.CUSTOMER = O.CUSTOMER
AND I.LEDGER = O.LEDGER
AND I.ACCOUNT = O.ACCOUNT
AND I.ACTIVITY_DATE < O.ACTIVITY_DATE
ORDER BY I.ACTIVITY_DATE DESC), 0) AMOUNT
FROM dbo.ACTIVITY O
ORDER BY CUSTOMER, LEDGER, ACCOUNT, ACTIVITY_DATE;
Or ROW_NUMBER() the data twice and join between them
SELECT A.CUSTOMER, A.LEDGER, A.ACCOUNT, A.ACTIVITY_DATE,
A.AMOUNT - ISNULL(B.AMOUNT,0) AMOUNT
FROM (SELECT *, RN=ROW_NUMBER() OVER (partition by CUSTOMER, LEDGER, ACCOUNT
order by ACTIVITY_DATE ASC)
FROM dbo.ACTIVITY) A
LEFT JOIN (SELECT *, RN=ROW_NUMBER() OVER (partition by CUSTOMER, LEDGER, ACCOUNT
order by ACTIVITY_DATE ASC)
FROM dbo.ACTIVITY) B ON A.CUSTOMER = B.CUSTOMER
AND A.LEDGER = B.LEDGER
AND A.ACCOUNT = B.ACCOUNT
AND B.RN = A.RN-1 -- prior record
ORDER BY A.CUSTOMER, A.LEDGER, A.ACCOUNT, A.ACTIVITY_DATE;