Exclude records that meet certain criteria by row - sql

I'm in need of some brainstorming. I have built a query that shows me what I need. However the ask now is to use this list of records and exclude records based on a certain criteria.
This is my current output from the query built:
Patient | Action | Date
james | REG | 2019/01/01
James | CUR | 2019/01/15
Jacon | REG | 2019/01/12
Jacob | REG | 2019/01/13
Main | CUR | 2019/01/01
Main | REG | 2019/01/05
Lucy | REG | 2019/01/08
Lucy | CUR | 2019/01/09
Lucy | CUR | 2019/01/10
Based on the sample data from above I want to remove any patients where the first record is 'REG' and the following Action is 'CUR'. So in this example I only want to remove James.
Any Ideas on what I should do?
Thank you for your help!

Please group your data first by using dense_rank and row_number, then benefiting from temp tables, get the data you are looking for.
CREATE TABLE #temp (Patient VARCHAR(50), Action VARCHAR(3))
Insert INTO #temp VALUES
('james','REG'),
('james','CUR'),
('Jacob','REG'),
('Jacob','REG'),
('Main','CUR'),
('Main','REG'),
('Lucy','REG'),
('Lucy','CUR'),
('Lucy','CUR')
SELECT *, DENSE_RANK() OVER (ORDER BY Patient ASC) GroupNo,
ROW_NUMBER() OVER (partition BY Patient ORDER BY Patient ASC) GroupOrder
INTO #PatientsWithGroup
FROM #temp
SELECT MIN(c1.GroupNo) GroupNo
INTO #PatsToEliminate
FROM #PatientsWithGroup c1
INNER JOIN #PatientsWithGroup c2 ON c1.GroupNo=c2.GroupNo
WHERE (c1.GroupOrder=1 AND c1.Action='REG') AND (c2.GroupOrder = 2 AND c2.Action='CUR')
HAVING COUNT(c1.Patient)<3
SELECT *
FROM #PatientsWithGroup p
WHERE p.GroupNo NOT IN (SELECT GroupNo FROM #PatsToEliminate)

You can use the LEAD function to look ahead.
CREATE TABLE #Patients (
ID int IDENTITY(1,1),
Patient varchar(50),
[Action] varchar(50)
);
INSERT INTO #Patients (Patient, [Action])
VALUES
('james', 'REG'),
('James', 'CUR'),
('Jacon', 'REG'),
('Jacob', 'REG'),
('Main', 'CUR'),
('Main', 'REG'),
('Lucy', 'REG'),
('Lucy', 'CUR'),
('Lucy', 'CUR');
SELECT * FROM #Patients;
WITH
PatientWithNextAction AS (
SELECT
Patient,
[Action],
LEAD([Action]) OVER(PARTITION BY Patient ORDER BY ID) NextAction
FROM
#Patients
)
DELETE
FROM
#Patients
WHERE
Patient IN (
SELECT
Patient
FROM
PatientWithNextAction
WHERE
[Action] = 'REG'
AND NextAction = 'CUR'
);
SELECT * FROM #Patients;
DROP TABLE #Patients;

Try this:
select 1 as ordre, 'james' as Patient, 'REG' as Action into #tmp
union select 2,'James', 'CUR'
union select 3,'Jacon', 'REG'
union select 4,'Jacob', 'REG'
union select 5,'Main' , 'CUR'
union select 6,'Main' , 'REG'
union select 7,'Lucy' , 'REG'
union select 8,'Lucy' , 'CUR'
union select 9,'Lucy' , 'CUR'
;with cte as
(
select ordre, Patient, [Action], RANK () OVER (
PARTITION BY Patient
ORDER BY ordre
) Patient_order from #tmp a
)
select * from cte a where not exists(select 1 from cte b where a.Patient = b.Patient and b.Patient_order = 1 and Action = 'REG'
and exists(select 1 from cte c where c.Patient = b.Patient and c.Patient_order = 2 and Action = 'CUR')
)

Related

Get SQL Server result using subquery

I have a Members table like this:
PersonID FirstName Address City date
---------------------------------------------------------
3 Rasanga Skagen 21 South 2019-01-05
and a Persons table:
PersonID FirstName Address City date
-------------------------------------------------------
3 Rasanga Skagen 21 South 2019-01-06
1 Tom B. Skagen 21 Colombo 2018-01-07
2 Tom B. Skagen 21 Colombo 2019-01-05
I want to get Persons that do not exists in Members table using the FirstName column. For that I'm using this query:
SELECT *
FROM Persons p
WHERE NOT EXISTS (SELECT * FROM Members m WHERE m.FirstName = p.FirstName)
When I execute above query I'm getting same FirstName and 2 records but my requirement is if there's 2 records for same name retrieve latest record using date column. Therefore above scenario it should be Tom B. with 2018-01-07 record. If both records have same date should retrieve 1 record from 2 records.
Can somebody explain how to do this?
You can use the left join and checking the Members.PersonId is null.
create table Members(PersonID int
, FirstName varchar(20)
, Address varchar(50)
, City varchar(50)
, Dtdate date)
insert into Members values
(3, 'Rasanga', 'Skagen 21', 'South', '2019-01-05')
Create table Persons(PersonID int
, FirstName varchar(20)
, Address varchar(50)
, City varchar(50)
, Dtdate date)
insert into Persons values
(3, 'Rasanga', 'Skagen 21', 'South', '2019-01-06'),
(1, 'Tom B.', 'Skagen 21', 'Colombo', '2018-01-07'),
(2, 'Tom B.', 'Skagen 21', 'Colombo', '2019-01-05')
Select Persons.* from Persons
left join Members on Persons.PersonID = Members.PersonID
where Members.PersonId is null
Demo
Using the not exists you can check as shown below.
SELECT Persons.*
FROM Persons
WHERE NOT EXISTS (SELECT 1
FROM Members
WHERE Persons.PersonID = Members.PersonID)
Using the in operator
SELECT * FROM Persons
WHERE PersonID NOT IN (
SELECT PersonID FROM Members
)
To get the unique records based on the first name and date you can use the following query using ROW_NUMBER() function.
;WITH cte
AS (
SELECT Persons.*
,ROW_NUMBER() OVER (
PARTITION BY Persons.FirstName ORDER BY Persons.Dtdate DESC
) AS RN
FROM Persons
LEFT JOIN Members ON Persons.PersonID = Members.PersonID
WHERE Members.PersonId IS NULL )
SELECT *
FROM CTE
WHERE RN = 1
Output
PersonID FirstName Address City Dtdate RN
----------------------------------------------------------
2 Tom B. Skagen 21 Colombo 2019-01-05 1
You could use a window function as
SELECT T.PersonID,
T.FirstName,
T.Address,
T.City,
T.[Date]
FROM
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY FirstName ORDER BY [Date] DESC) RN
FROM Persons
) T
WHERE NOT EXISTS
(
SELECT 1
FROM Members
WHERE FirstName = T.FirstName
) AND T.RN = 1;
Here is a db<>fiddle

Query to get the list of Addresses that have AddressType one else AddressType two?

Consider the folowing table
Id PersonId Address AddressTypeId
--------------------------------------------------------------------
1 1 AI1P1T1 1
2 1 AI2P1T2 2
3 2 AI3P2T2 2
I want to write a query to print the list of Addresses of Persons who have AddressType =1 or AddressTypeId=2 and
When person has AddressType =1 then select it,
else select person with AddressType =2
Expected result:
Address
--------------
AI1P1T1
AI3P2T2
Good day,
Please check if this solve your needs:
/***************************** DDL+DML */
drop table if exists T;
create table T(Id int,PersonId int, [Address] nvarchar(10), AddressTypeId int)
INSERT T(Id,PersonId, [Address], AddressTypeId)
values
(1,1,'AI1P1T1',1),
(2,1,'AI2P1T2',2),
(3,2,'AI3P2T2',2)
GO
select * from T
GO
/***************************** Solution */
With MyCTE as (
select *, ROW_NUMBER() OVER (partition by PersonId order by AddressTypeId) as RN
from T
)
select [Address]
from MyCTE
where
AddressTypeId in (1,2) -- if there can be only positive numbers then you can use "< 3"
and RN = 1
GO
You can try this also using joins:
select t1.PersonId,t1.Address from #T t1
inner join (select personid,min(AddressTypeId)atype from #T
group by PersonId )x
on x.atype=t1.AddressTypeId and x.PersonId=t1.PersonId
I would write a subquery to make ROW_NUMBER by window function, then use MAX in the main query.
SELECT
PersonId, MAX(Address) Address
FROM
(SELECT
PersonId,
(CASE
WHEN ROW_NUMBER() OVER (PARTITION BY PersonId ORDER BY PersonId) = 1
THEN Address
END) Address
FROM
T
WHERE
AddressTypeId IN (1,2)
) t1
GROUP BY
PersonId
sqlfiddle
[Results]:
| PersonId | Address |
+----------+---------+
| 1 | AI1P1T1 |
| 2 | AI3P2T2 |
Here's the top 1 with ties trick:
select top 1 * with ties
from yourtable
order by row_number() over (partition by PersonId order by AddressTypeId)
This will also work for versions <2012, and can return every field
You could use an union between the result for the result for only 1, only 2 and 1 when 1 and 2
select Address
from my_table m
Inner join (
select PersonId , count(distinct distinct AddressTypeId)
from my_table
where AddressTypeId in (1, 2)
group by PersonId
having count(distinct AddressTypeId) = 2
) t on t.personId = m.personId andm.AddressTypeId = 1
UNION
select Address
from my_table m
Inner join (
select PersonId , count(distinct distinct AddressTypeId)
from my_table
where AddressTypeId in ( 2)
group by PersonId
having count(distinct AddressTypeId) = 1
) t on t.personId = m.personId andm.AddressTypeId = 2
UNION
select Address
from my_table m
Inner join (
select PersonId , count(distinct distinct AddressTypeId)
from my_table
where AddressTypeId in ( 1)
group by PersonId
having count(distinct AddressTypeId) = 1
) t on t.personId = m.personId andm.AddressTypeId = 1
Try this one
select personId, last_value(Address) over(partition by personId order by AddressTypeId) as Address
from table
--use the where statement optionally
--where AddressTypeId in (1,2);

SQL - How to remove repeating values

My requirement is to remove the repeating values.
id name surname value
1 Vinduja Vijayan 5
3 Vinduja Vijayan 6
4 Vinduja Vijayan 7
Required output:
id name surname value
1 Vinduja Vijayan 5
3 NuLL Null 6
4 NULL NULL 7
This transformation should usually be applied in the application layer. It is possible to do in SQL, but not recommended, by using row_number() and case:
select id,
(case when row_number() over (partition by name, surname order by id) = 1
then name
end) as name,
(case when row_number() over (partition by name, surname order by id) = 1
then surname
end) as surname
from t
order by id;
Note that the final order by is very, very important. SQL result sets (like tables) are unordered by default. Without an explicit order by, the results could be in any order, and that would mess up your interpretation of the results.
DECLARE #table TABLE (
Id INT
,Name VARCHAR(20)
,Surname VARCHAR(20)
,value INT
);
INSERT into #table(ID,Name,Surname,value)
Select 1,'Vinduja','Vijayan',5
Union
Select 3,'Vinduja','Vijayan',6
Union
Select 4,'Vinduja','Vijayan',7
Select S.Id ,T.Name,T.Surname,S.value from (
Select * ,ROW_NUMBER() Over(Partition by name Order by name) [Row]
From #table)S
Left join #table T On T.Id =S.Id and S.[Row]=1
select
id,
case when rnk=1 then name end as name,
case when rnk=1 then surname end as surname ,
value
from
(
select
id,name,surname,value,
row_number()over(partition by name,surname order by id) as rnk
from table_name)repeatname
I'm not sure I understand your requirements. If you just want to display the data as described, then this won't work. But if you're trying to change the data in your table, this will do that.
DECLARE #Dupes TABLE
(
id INT
,name VARCHAR(30)
,surname VARCHAR(30)
,value INT
);
INSERT #Dupes
(
id
,name
,surname
,value
)
VALUES
(1, 'Vinduja', 'Vijayan', 5),
(3, 'Vinduja', 'Vijayan', 6),
(4, 'Vinduja', 'Vijayan', 7);
WITH cte AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY [name], surname ORDER BY id) AS RowNum
,id
,name
,surname
,value
FROM #Dupes
)
UPDATE cte
SET cte.name = NULL
,cte.surname = NULL
WHERE
cte.RowNum > 1;
SELECT *
FROM #Dupes;
--Results
+----+---------+---------+-------+
| id | name | surname | value |
+----+---------+---------+-------+
| 1 | Vinduja | Vijayan | 5 |
| 3 | NULL | NULL | 6 |
| 4 | NULL | NULL | 7 |
+----+---------+---------+-------+
And just for interest, using the LAG function. I assumed SQL Server.
select id,
iif(name = previous_name, null, name) name,
iif(surname = previous_surname, null, surname) surname
from (
select name, surname, id,
lag(name, 1, null) over (order by name, surname, id) previous_name,
lag(surname, 1, null) over (order by name, surname, id) previous_surname
from table_name ) a
order by a.name, a.surname, a.id

Find values from row where some column meets an aggregate condition?

SQL SERVER 2008 R2. Two tables having a 1-many relationship, and want some fields from both, but on the many side, only values from a certain row. That row is specified by some condition, in my case it should the row having the smallest ClaimId for that PaymentId.
IF OBJECT_ID('tempdb..#Payments') IS NOT NULL DROP TABLE #Payments
CREATE TABLE #Payments ( PaymentId int UNIQUE NOT NULL )
INSERT INTO #Payments ( PaymentId )
VALUES (1), (2), (3)
IF OBJECT_ID('tempdb..#Claims') IS NOT NULL DROP TABLE #Claims
CREATE TABLE #Claims ( PaymentId int NOT NULL, ClaimId int NOT NULL, FirstName varchar(50) NOT NULL )
INSERT INTO #Claims ( PaymentId, ClaimId, FirstName )
VALUES (1, 51,'Joe'), (1, 57,'Jane'), (2, 62,'Spot'), (2, 63,'Rover'), (3, 88,'Sue'), (3, 89,'Sally')
SELECT * FROM #Payments p
JOIN #Claims c ON p.PaymentId=c.PaymentId
WHERE c.ClaimId=MIN(ClaimId)
GROUP BY p.PaymentId
HAVING MIN(ClaimId)
The desired result is as follows, where the claim is from the row having the minimum claimId for a given paymentId. ClaimId may not be ordered, so seeking minimum, not first.
PaymentId ClaimId FirstName
1 51 Joe
2 62 Spot
3 88 Sue
I'd be happy to refer to an existing question/answer, but don't know how to word this that I find something similar. Perhaps Get field value from a record that causes an aggregate condition to be true but I didn't understand it.
You can use the ROW_NUMBER window function like this:
SELECT PaymentId, ClaimId, FirstName
FROM
(
SELECT
p.PaymentId,
c.ClaimId,
c.FirstName,
ROW_NUMBER() OVER (PARTITION BY p.PaymentId ORDER BY c.ClaimId) as RN
FROM #Payments p
JOIN #Claims c ON p.PaymentId=c.PaymentId
) as T
WHERE RN = 1
In SQL Server, I like to use outer apply for this:
select p.*, c.*
from #Payments p outer apply
(select top 1 c.*
from #Claims c
where c.paymentid = p.paymentid
order by c.claimid
) c;
solution using RANK()
;WITH A
AS (SELECT p.PaymentId
, c.ClaimId
, c.FirstName
, rn = RANK() OVER(PARTITION BY P.PaymentId ORDER BY ClaimId ASC)
FROM #Payments p
JOIN #Claims c ON p.PaymentId = c.PaymentId)
SELECT PaymentId
, ClaimId
, FirstName
FROM A
WHERE rn = 1;
use windows function row_number to provide an order
;with cte as
(
select PaymentID, ClaimID, row_number() over (partition by PaymentID order by ClaimID ) as rn
from #Claims
)
select p.*,cte.ClaimID
from #Payments p
join cte on cte.paymentID = p.paymentID
where rn=1 --limits to earliest claimid
Both sentences return same result, first one select MIN(ClaimId) for every PaymentId:
SELECT P.*, C.*
FROM #Payments P
INNER JOIN #Claims C ON C.PaymentId = P.PaymentId
WHERE C.ClaimID IN (SELECT MIN(ClaimID) OVER (PARTITION BY PaymentId) FROM #Claims);
Second uses a CTE to find MIN(ClaimId) before to join to Payments table:cÂș
WITH PY AS
(
SELECT PaymentId, MIN(ClaimId) as ClaimID
FROM #Claims
GROUP BY PaymentId
)
SELECT PY.PayMentId, PY.ClaimId, C.FirstName, P.*
FROM PY
LEFT JOIN #Claims c ON p.ClaimId = c.ClaimId;
LEFT JOIN #Payments P ON P.PaymentId = PY.PaymentId
+-----------+---------+-----------+
| PayMentId | ClaimId | FirstName |
+-----------+---------+-----------+
| 1 | 51 | Joe |
+-----------+---------+-----------+
| 2 | 62 | Spot |
+-----------+---------+-----------+
| 3 | 88 | Sue |
+-----------+---------+-----------+
Check it here: http://rextester.com/KRT45653

How to find max value from each group and display their information when using "group by"

For example, i create a table about people contribue to 2 campaigns
+-------------------------------------+
| ID Name Campaign Amount (USD) |
+-------------------------------------+
| 1 A 1 10 |
| 2 B 1 5 |
| 3 C 2 7 |
| 4 D 2 9 |
+-------------------------------------+
Task: For each campaign, find the person (Name, ID) who contribute the most to
Expected result is
+-----------------------------------------+
| Campaign Name ID |
+-----------------------------------------+
| 1 A 1 |
| 2 D 4 |
+-----------------------------------------+
I used "group by Campaign" but the result have 2 columns "Campagin" and "max value" when I need "Name" and "ID"
Thanks for your help.
Edited: I fix some values, really sorry
You can use analytic functions for this:
select name, id, amount
from (select t.*, max(amount) over (partition by campaign) as max_amount
from t
) t
where amount = max_amount;
You can also do it by giving a rank/row_number partiton by campaign and order by descending order of amount.
Query
;with cte as(
select [num] = dense_rank() over(
partition by [Campaign]
order by [Amount] desc
), *
from [your_table_name]
)
select [Campaign], [Name], [ID]
from cte
where [num] = 1;
Try the next query:-
SELECT Campaign , Name , ID
FROM (
SELECT Campaign , Name , ID , MAX (Amount)
FROM MyTable
GROUP BY Campaign , Name , ID
) temp;
Simply use Where Clause with the max of amount group by Campaign:-
As following generic code:-
select a, b , c
from tablename
where d in
(
select max(d)
from tablename
group by a
)
Demo:-
Create table #MyTable (ID int , Name char(1), Campaign int , Amount int)
go
insert into #MyTable values (1,'A',1,10)
insert into #MyTable values (2,'B',1,5)
insert into #MyTable values (3,'C',2,7)
insert into #MyTable values (4,'D',2,9)
go
select Campaign, Name , ID
from #MyTable
where Amount in
(
select max(Amount)
from #MyTable
group by Campaign
)
drop table #MyTable
Result:-
Please find the below code for the same
SELECT *
FROM #MyTable T
OUTER APPLY (
SELECT COUNT(1) record
FROM #MyTable T1
where t.Campaign = t1.Campaign
and t.amount < t1.amount
)E
where E.record = 0