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
Related
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')
)
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);
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
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
I have a table A, with 4 columns:
first_name, invoice, value, date.
And a table B (first_name, max_invoice_name, max_invoice_value, last_date)
I want to create a procedure in order to move data from A, to B, but:
first_name should be one time in B,
max_invoice_name is the name of the max invoice value
max_invoice_value is the max value
last_date is the latest date from invoices from the same first_name.
For example:
TABLE A:
Smith | Invoice1 | 100 | 23.06.2016
John | Invoice13 | 23 | 18.07.2016
Smith | Invoice3 | 200 | 01.01.2015
Table B should be:
Smith |Invoice3 | 200 | 23.06.2016
John |Invoice13| 23 | 18.07.2016
Something like this should work:
select *, (select max(date) from #Table1 T1 where T1.first_name = X.first_name)
from (
select
*,
row_number() over (partition by first_name order by invoice_Value desc) as RN
from
#Table1
) X
where RN = 1
Row number takes care of selecting the row with biggest value, and the max get's the date. You'll need to list the columns in correct place instead of *
You will need to create 2 scalar functions getMaxNameForMaxValue AND getLastDateByFirstName to get the values you want.
INSERT INTO TableB (first_name, max_invoice_name, max_invoice_value, last_date) (SELECT DISTINCT first_name, getMaxNameForMaxValue(MAX(max_value)) AS 'max_invoice_name', MAX(max_invoice_value) AS 'max_invoice_value', getLastDateByFirstName(first_name) AS 'lastDate' FROM Table A)
You can use something like this:
--INSERT INTO TableB
SELECT first_name,
invoice_name,
invoice_value,
last_date
FROM (
SELECT a.first_name,
a.invoice_name,
a.invoice_value,
COALESCE(p.last_date,a.last_date) as last_date,
ROW_NUMBER() OVER (PARTITION BY a.first_name ORDER BY a.last_date) as rn
FROM TableA a
OUTER APPLY (SELECT TOP 1 * FROM TableA WHERE first_name = a.first_name and last_date > a.last_date) as p
) as res
WHERE rn = 1
As output:
first_name invoice_name invoice_value last_date
John Invoice13 23 2016-07-18
Smith Invoice3 200 2016-06-23
Try this
Insert into TableB(first_name, max_invoice_name, max_invoice_value, last_date)
select t1.first_name,t1.invoice,t1,value,t2.date from TableA as t1 inner join
(
select first_name, max(replace(invoice,'invoice','')) as invoice, max(date) as date
from TableA group by first_name
) as t2 on t1.first_name=t2.first_name and t1.invoice=t2.invoice