In Sql, group on Column , but not show in all rows - sql

I am working on a query in database. Say i have a patient which travelled to 3 hospitals. Now i want to add the cost of his journey but want to show it only at his first journey ,the total cost.
file right now is like
Patient Hospital1 cost
A 1 200
A 2 400
A 3 100
B 1 200
I want the output as
Patient Hosptial Cost
A 1 700
A 2
A 3
B 1 200
Thanks

You can use window functions:
select t.*,
(case when row_number() over (partition by patient order by hospital1) = 1
then sum(cost) over (partition by patient)
end) as total_cost
from t
order by patient, hospital1;

CREATE TABLE MyTable(Patient varchar(20),Hospital int, Cost int)
INSERT INTO MyTable(Patient,Hospital,Cost) VALUES ('A',1,200),('A',2,400),
('A',3,100),
('B',1,200)
WITH CTE AS (SELECT Patient,SUM(Cost) AS Cost FROM MyTable
GROUP BY Patient)
SELECT M.Patient,M.Hospital,
CASE WHEN ROW_NUMBER() OVER (PARTITION BY M.Patient ORDER BY M.Hospital)=1 THEN CAST(C.Cost AS VARCHAR(255))
ELSE '' END AS Cost FROM CTE AS C
INNER JOIN Mytable AS M ON C.Patient=M.Patient
Patient | Hospital | Cost
:------ | -------: | :---
A | 1 | 700
A | 2 |
A | 3 |
B | 1 | 200
db<>fiddle here

If you had the records in a table, called PatientVisits, with the following data:
You could use this query:
SELECT PV.Patient, PV.Hospital, PVGroup.TotalCost
FROM PatientVisits PV
LEFT JOIN (
SELECT Patient, MIN(Hospital) as FirstVisit, SUM(Cost) as TotalCost
FROM PatientVisits
GROUP BY Patient) PVGroup ON
PV.Patient = PVGroup.Patient AND
PV.Hospital = PVGroup.FirstVisit
ORDER BY PV.Patient, PV.Hospital
The results would be:

Related

Select first rows where condition [duplicate]

Here's what I'm trying to do. Let's say I have this table t:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
2 | 18 | 2012-05-19 | y
3 | 18 | 2012-08-09 | z
4 | 19 | 2009-06-01 | a
5 | 19 | 2011-04-03 | b
6 | 19 | 2011-10-25 | c
7 | 19 | 2012-08-09 | d
For each id, I want to select the row containing the minimum record_date. So I'd get:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
4 | 19 | 2009-06-01 | a
The only solutions I've seen to this problem assume that all record_date entries are distinct, but that is not this case in my data. Using a subquery and an inner join with two conditions would give me duplicate rows for some ids, which I don't want:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
5 | 19 | 2011-04-03 | b
4 | 19 | 2009-06-01 | a
How about something like:
SELECT mt.*
FROM MyTable mt INNER JOIN
(
SELECT id, MIN(record_date) AS MinDate
FROM MyTable
GROUP BY id
) t ON mt.id = t.id AND mt.record_date = t.MinDate
This gets the minimum date per ID, and then gets the values based on those values. The only time you would have duplicates is if there are duplicate minimum record_dates for the same ID.
I could get to your expected result just by doing this in mysql:
SELECT id, min(record_date), other_cols
FROM mytable
GROUP BY id
Does this work for you?
To get the cheapest product in each category, you use the MIN() function in a correlated subquery as follows:
SELECT categoryid,
productid,
productName,
unitprice
FROM products a WHERE unitprice = (
SELECT MIN(unitprice)
FROM products b
WHERE b.categoryid = a.categoryid)
The outer query scans all rows in the products table and returns the products that have unit prices match with the lowest price in each category returned by the correlated subquery.
I would like to add to some of the other answers here, if you don't need the first item but say the second number for example you can use rownumber in a subquery and base your result set off of that.
SELECT * FROM
(
SELECT
ROW_NUM() OVER (PARTITION BY Id ORDER BY record_date, other_cols) as rownum,
*
FROM products P
) INNER
WHERE rownum = 2
This also allows you to order off multiple columns in the subquery which may help if two record_dates have identical values. You can also partition off of multiple columns if needed by delimiting them with a comma
This does it simply:
select t2.id,t2.record_date,t2.other_cols
from (select ROW_NUMBER() over(partition by id order by record_date)as rownum,id,record_date,other_cols from MyTable)t2
where t2.rownum = 1
If record_date has no duplicates within a group:
think of it as of filtering. Simpliy get (WHERE) one (MIN(record_date)) row from the current group:
SELECT * FROM t t1 WHERE record_date = (
select MIN(record_date)
from t t2 where t2.group_id = t1.group_id)
If there could be 2+ min record_date within a group:
filter out non-min rows (see above)
then (AND) pick only one from the 2+ min record_date rows, within the given group_id. E.g. pick the one with the min unique key:
AND key_id = (select MIN(key_id)
from t t3 where t3.record_date = t1.record_date
and t3.group_id = t1.group_id)
so
key_id | group_id | record_date | other_cols
1 | 18 | 2011-04-03 | x
4 | 19 | 2009-06-01 | a
8 | 19 | 2009-06-01 | e
will select key_ids: #1 and #4
SELECT p.* FROM tbl p
INNER JOIN(
SELECT t.id, MIN(record_date) AS MinDate
FROM tbl t
GROUP BY t.id
) t ON p.id = t.id AND p.record_date = t.MinDate
GROUP BY p.id
This code eliminates duplicate record_date in case there are same ids with same record_date.
If you want duplicates, remove the last line GROUP BY p.id.
This a old question, but this can useful for someone
In my case i can't using a sub query because i have a big query and i need using min() on my result, if i use sub query the db need reexecute my big query. i'm using Mysql
select t.*
from (select m.*, #g := 0
from MyTable m --here i have a big query
order by id, record_date) t
where (1 = case when #g = 0 or #g <> id then 1 else 0 end )
and (#g := id) IS NOT NULL
Basically I ordered the result and then put a variable in order to get only the first record in each group.
The below query takes the first date for each work order (in a table of showing all status changes):
SELECT
WORKORDERNUM,
MIN(DATE)
FROM
WORKORDERS
WHERE
DATE >= to_date('2015-01-01','YYYY-MM-DD')
GROUP BY
WORKORDERNUM
select
department,
min_salary,
(select s1.last_name from staff s1 where s1.salary=s3.min_salary ) lastname
from
(select department, min (salary) min_salary from staff s2 group by s2.department) s3

How to use select statement inside SUM function

I have a table which looks like this.
ID | Block | Flats | Ammount | Balance |
1 | 1 | GF-1 | 1000 | 500 |
2 | 1 | GF-2 | 1000 | 500 |
3 | 2 | GF-1 | 1000 | 500 |
4 | 2 | GF-2 | 1000 | 1000 |
5 | 2 | GF-2 | 1000 | 0 |
I want to execute sum query on this. I have tried
Select distinct A.Block,(Select Sum(Ammount) from t1 where block = A.block),(select Sum(Balance) from t1 where block = A.block) from t1 A
This query is working fine but its summing balance to 2500 but as ID 4 & 5 are of same Flat so I want it to sum latest of balance which should be 1500. I have tried to put a select statement inside sum function but that doesn't work . So how can I achieve this?
You can select the most recent id for each block/flats combo first (using row_number()) and then aggregate:
Select t1.Block, sum(amount)
from (select t1.*,
row_number() over (partition by block, flats order by id desc) as seqnum
from t1
) t1
where seqnum = 1
group by t1.Block;
You can use below query
Select A.Block, Sum(A.Ammount), Sum(A.Balance)
from t1 A group by A.Flats order by A.ID;
Order by clause is used to sort with respect to ID
I think you just want to SUM() group by block like
select Block, sum(amount) totalamount,
sum(case when balance <> 0 then balance end) totalbalance
from t1
group by Block;
You can use a SUM(DISTINCT ...)
select Block, sum(DISTINCT amount) totalamount,
sum(DISTINCT case when balance <> 0 then balance end) totalbalance
from t1
group by Block;

Select only 1 payment from a table with customers with multiple payments

I have a table called "payments" where I store all the payments of my costumers and I need to do a select to calculate the non-payment rate in a given month.
The costumers can have multiples payments in that month, but I should count him only once: 1 if any of the payments is done and 0 if any of the payment was made.
Example:
+----+------------+--------+
| ID | DATEDUE | AMOUNT |
+----+------------+--------+
| 1 | 2016-11-01 | 0 |
| 1 | 2016-11-15 | 20.00 |
| 2 | 2016-11-10 | 0 |
+----+------------+--------+
The result I expect is from the rate of november:
+----+------------+--------+
| ID | DATEDUE | AMOUNT |
+----+------------+--------+
| 1 | 2016-11-15 | 20.00 |
| 2 | 2016-11-10 | 0 |
+----+------------+--------+
So the rate will be 50%.
But if the select is:
SELECT * FROM payment WHERE DATEDUE BETWEEN '2016-11-01' AND '2016-11-30'
It will return me 3 rows and the rate will be 66%, witch is wrong. Ideas?
PS: This is a simpler example of the real table. The real query have a lot of columns, subselects, etc.
It sounds like you need to partition your results per customer.
SELECT TOP 1 WITH TIES
ID,
DATEDUE,
AMOUNT
ORDER BY ROW_NUMBER() OVER (PARTITION BY ID ORDER BY AMOUNT DESC)
WHERE DATEDUE BETWEEN '2016-11-01' AND '2016-11-30'
PS: The BETWEEN operator is frowned upon by some people. For clarity it might be better to avoid it:
What do BETWEEN and the devil have in common?
Try this
SELECT
id
, SUM(AMOUNT) AS AMOUNT
FROM
Payment
GROUP BY
id;
This might help if you want other columns.
WITH cte (
SELECT
id
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY AMOUNT DESC ) AS RowNum
-- other row
)
SELECT *
FROM
cte
WHERE
RowNum = 1;
To calculate the rate, you can use explicit division:
select 1 - count(distinct case when amount > 0 then id end) / count(*)
from payment
where . . .;
Or, in a way that is perhaps easier to follow:
select avg(flag * 1.0)
from (select id, (case when max(amount) > 0 then 0 else 1 end) as flag
from payment
where . . .
group by id
) i

Compare Multiple rows In SQL Server

I have a SQL Server database full of the following (fictional) data in the following structure:
ID | PatientID | Exam | (NON DB COLUMN FOR REFERENCE)
------------------------------------
1 | 12345 | CT | OK
2 | 11234 | CT | OK(Same PID but Different Exam)
3 | 11234 | MRI | OK(Same PID but Different Exam)
4 | 11123 | CT | BAD(Same PID, Same Exam)
5 | 11123 | CT | BAD(Same PID, Same Exam)
6 | 11112 | CT | BAD(Conflicts With ID 8)
7 | 11112 | MRI | OK(SAME PID but different Exam)
8 | 11112 | CT | BAD(Conflicts With ID 6)
9 | 11123 | CT | BAD(Same PID, Same Exam)
10 | 11123 | CT | BAD(Same PID, Same Exam)
I am trying to write a query with will go through an identify everything that isn't bad as per my example above.
Overall, a patient (identified by PatientId) can have many rows, but may not have 2 or more rows with the same exam!
I have attempted various modifications of exams I found on here but still with no luck.
Thanks.
You seem to want to identify duplicates, ranking them as good or bad. Here is a method using window functions:
select t.id, t.patientid, t.exam,
(case when cnt > 1 then 'BAD' else 'OK' end)
from (select t.*, count(*) over (partition by patientid, exam) as cnt
from table t
) t;
use Count() over() :
select *,case when COUNT(*) over(partition by PatientID, Exam) > 1 then 'bad' else 'ok'
from yourtable
You can also use:
;WITH CTE_Patients
(ID, PatientID, Exam, RowNumber)
AS
(
SELECT ID, PatientID, Exam
ROW_NUMBER() OVER (PARTITION BY PatientID, Exam ORDER BY ID)
FROM YourTableName
)
SELECT TableB.ID, TableB.PatientID, TableB.Exam, [DuplicateOf] = TableA.ID
FROM CTE_Patients TableB
INNER JOIN CTE_Patients TableA
ON TableB.PatientID = TableA.PatientID
AND TableB.Exam = TableA.Exam
WHERE TableB.RowNumber > 1 -- Duplicate rows
AND TableA.RowNumber = 1 -- Unique rows
I have a sample here: SQL Server – Identifying unique and duplicate rows in a table, you can identify unique rows as well as duplicate rows
If you don't want to use a CTE or Count Over, you can also group the Source table, and select from there...(but I'd be surprised if #Gordon was too far off the mark with the original answer :) )
SELECT a.PatientID, a.Exam, CASE WHEN a.cnt > 1 THEN 'BAD' ELSE 'OK' END
FROM ( SELECT PatientID
,Exam
,COUNT(*) AS cnt
FROM tableName
GROUP BY Exam
,PatientID
) a
Select those patients that never have 2 or more exams of same type.
select * from patients t1
where not exists (select 1 from patients t2
where t1.PatientID = t2.PatientID
group by exam
having count(*) > 1)
Or, if you want all rows, like in your example:
select ID,
PatientID,
Exam,
case when exists (select 1 from patients t2
where t1.PatientID = t2.PatientID
group by exam
having count(*) > 1) then 'BAD' else 'OK' end
from patients

Group by minimum value in one field while selecting distinct rows

Here's what I'm trying to do. Let's say I have this table t:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
2 | 18 | 2012-05-19 | y
3 | 18 | 2012-08-09 | z
4 | 19 | 2009-06-01 | a
5 | 19 | 2011-04-03 | b
6 | 19 | 2011-10-25 | c
7 | 19 | 2012-08-09 | d
For each id, I want to select the row containing the minimum record_date. So I'd get:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
4 | 19 | 2009-06-01 | a
The only solutions I've seen to this problem assume that all record_date entries are distinct, but that is not this case in my data. Using a subquery and an inner join with two conditions would give me duplicate rows for some ids, which I don't want:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
5 | 19 | 2011-04-03 | b
4 | 19 | 2009-06-01 | a
How about something like:
SELECT mt.*
FROM MyTable mt INNER JOIN
(
SELECT id, MIN(record_date) AS MinDate
FROM MyTable
GROUP BY id
) t ON mt.id = t.id AND mt.record_date = t.MinDate
This gets the minimum date per ID, and then gets the values based on those values. The only time you would have duplicates is if there are duplicate minimum record_dates for the same ID.
I could get to your expected result just by doing this in mysql:
SELECT id, min(record_date), other_cols
FROM mytable
GROUP BY id
Does this work for you?
To get the cheapest product in each category, you use the MIN() function in a correlated subquery as follows:
SELECT categoryid,
productid,
productName,
unitprice
FROM products a WHERE unitprice = (
SELECT MIN(unitprice)
FROM products b
WHERE b.categoryid = a.categoryid)
The outer query scans all rows in the products table and returns the products that have unit prices match with the lowest price in each category returned by the correlated subquery.
I would like to add to some of the other answers here, if you don't need the first item but say the second number for example you can use rownumber in a subquery and base your result set off of that.
SELECT * FROM
(
SELECT
ROW_NUM() OVER (PARTITION BY Id ORDER BY record_date, other_cols) as rownum,
*
FROM products P
) INNER
WHERE rownum = 2
This also allows you to order off multiple columns in the subquery which may help if two record_dates have identical values. You can also partition off of multiple columns if needed by delimiting them with a comma
This does it simply:
select t2.id,t2.record_date,t2.other_cols
from (select ROW_NUMBER() over(partition by id order by record_date)as rownum,id,record_date,other_cols from MyTable)t2
where t2.rownum = 1
If record_date has no duplicates within a group:
think of it as of filtering. Simpliy get (WHERE) one (MIN(record_date)) row from the current group:
SELECT * FROM t t1 WHERE record_date = (
select MIN(record_date)
from t t2 where t2.group_id = t1.group_id)
If there could be 2+ min record_date within a group:
filter out non-min rows (see above)
then (AND) pick only one from the 2+ min record_date rows, within the given group_id. E.g. pick the one with the min unique key:
AND key_id = (select MIN(key_id)
from t t3 where t3.record_date = t1.record_date
and t3.group_id = t1.group_id)
so
key_id | group_id | record_date | other_cols
1 | 18 | 2011-04-03 | x
4 | 19 | 2009-06-01 | a
8 | 19 | 2009-06-01 | e
will select key_ids: #1 and #4
SELECT p.* FROM tbl p
INNER JOIN(
SELECT t.id, MIN(record_date) AS MinDate
FROM tbl t
GROUP BY t.id
) t ON p.id = t.id AND p.record_date = t.MinDate
GROUP BY p.id
This code eliminates duplicate record_date in case there are same ids with same record_date.
If you want duplicates, remove the last line GROUP BY p.id.
This a old question, but this can useful for someone
In my case i can't using a sub query because i have a big query and i need using min() on my result, if i use sub query the db need reexecute my big query. i'm using Mysql
select t.*
from (select m.*, #g := 0
from MyTable m --here i have a big query
order by id, record_date) t
where (1 = case when #g = 0 or #g <> id then 1 else 0 end )
and (#g := id) IS NOT NULL
Basically I ordered the result and then put a variable in order to get only the first record in each group.
The below query takes the first date for each work order (in a table of showing all status changes):
SELECT
WORKORDERNUM,
MIN(DATE)
FROM
WORKORDERS
WHERE
DATE >= to_date('2015-01-01','YYYY-MM-DD')
GROUP BY
WORKORDERNUM
select
department,
min_salary,
(select s1.last_name from staff s1 where s1.salary=s3.min_salary ) lastname
from
(select department, min (salary) min_salary from staff s2 group by s2.department) s3