I have a table of people (CarOwners
) and the types of cars they own
+-------+-------+
| Name | Model |
+-------+-------+
| Bob | Camry |
| Bo
Try it's
if object_id('tempdb.dbo.#temp') is not null
drop table #temp
create table #temp (name varchar(100),model varchar(100))
insert into #temp values('Bob','Camry')
insert into #temp values('Bob','Civic')
insert into #temp values('Bob','Prius')
insert into #temp values('Kevin','Focus')
insert into #temp values('Kevin','Civic')
insert into #temp values('Mark','Civic')
insert into #temp values('Lisa','Focus')
insert into #temp values('Lisa','Civic')
select * from (
select row_number() over(partition by name order by (select null)) as n,
row_number() over(partition by model order by (select null)) as m,*
from #temp) as a
where n = m
order by name
One way to do this is comparing the ordered concatenated model value for each name.
with cte as (
select name,model,
STUFF((
SELECT ',' + t2.model
FROM t t2
WHERE t1.name=t2.name
ORDER BY model
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 1, '') concat_value
from t t1
)
select distinct x2.name,x2.model
from cte x1
join cte x2 on x1.concat_value=x2.concat_value and x1.name<>x2.name
where x1.name='Kevin'
If your version of SQL Server supports STRING_AGG, the query can be simplified as
with cte as (
select name,model,
STRING_AGG(model,',') WITHIN GROUP(ORDER BY model) as concat_value
from t t1
)
select distinct x2.name,x2.model
from cte x1
join cte x2 on x1.concat_value=x2.concat_value and x1.name<>x2.name
where x1.name='Kevin'
This counts the number of rows for each name
using a common table expression(cte) with count()
over().
Then the matches
cte uses a self-join where the names do not match, the models match, the count of models for each name match, and one of those names is 'Lisa'
. The having
clause ensures that count of matched rows (count(*)
) matches the number of models that name
has.
matches
itself would only return the name
of each person, so we join back to the source table t
to get the full list of models for each match.
;with cte as (
select *
, cnt = count(*) over (partition by name)
from t
)
, matches as (
select x2.name
from cte as x
inner join cte as x2
on x.name <> x2.name
and x.model = x2.model
and x.cnt = x2.cnt
and x.name = 'Lisa'
group by x2.name, x.cnt
having count(*) = x.cnt
)
select t.*
from t
inner join matches m
on t.name = m.name
rextester demo: http://rextester.com/SUKP78304
returns:
+-------+-------+
| name | model |
+-------+-------+
| Kevin | Civic |
| Kevin | Focus |
+-------+-------+
We could also write it without the ctes, but it makes it a little harder to follow:
select t.*
from t
inner join (
select x2.Name
from (
select *, cnt = count(*) over (partition by name)
from t
where name='Lisa'
) as x
inner join (
select *, cnt = count(*) over (partition by name)
from t
) as x2
on x.name <> x2.name
and x.model = x2.model
and x.cnt = x2.cnt
group by x2.name, x.cnt
having count(*) = x.cnt
) as m
on t.name = m.name
Since you want your match to be exact, we should add the number of cars each person owns as an additional field. Assuming your table name is '#owners' The following query
select *
, (select COUNT(*)
from #owners o2
where o2.name = o1.name) as num
from #owners o1
gives us the table
+-------+-------+-----+
| Name | Model | num |
+-------+-------+-----+
| Bob | Camry | 3 |
| Bob | Civic | 3 |
| Bob | Prius | 3 |
| Kevin | Civic | 2 |
| Kevin | Focus | 2 |
| Mark | Civic | 1 |
| Lisa | Focus | 2 |
| Lisa | Civic | 2 |
+-------+-------+-----+
Then we want to join this table to itself matching model and count. We use a CTE so that it reads better. The following query
; with
OwnedCount as (
select *
, (select COUNT(*)
from #owners o2
where o2.name = o1.name) as num
from #owners o1
)
select *
from OwnedCount o1
inner join OwnedCount o2
on o1.model = o2.model
and o1.num = o2.num
gives us this table
+-------+-------+-----+-------+-------+-----+
| Name | Model | num | Name | Model | num |
+-------+-------+-----+-------+-------+-----+
| Bob | Camry | 3 | Bob | Camry | 3 |
| Bob | Civic | 3 | Bob | Civic | 3 |
| Bob | Prius | 3 | Bob | Prius | 3 |
| Kevin | Civic | 2 | Kevin | Civic | 2 |
| Kevin | Civic | 2 | Lisa | Civic | 2 |
| Kevin | Focus | 2 | Kevin | Focus | 2 |
| Kevin | Focus | 2 | Lisa | Focus | 2 |
| Mark | Civic | 1 | Mark | Civic | 1 |
| Lisa | Civic | 2 | Kevin | Civic | 2 |
| Lisa | Civic | 2 | Lisa | Civic | 2 |
| Lisa | Focus | 2 | Kevin | Focus | 2 |
| Lisa | Focus | 2 | Lisa | Focus | 2 |
+-------+-------+-----+-------+-------+-----+
Lastly, you filter the results by the name you want
declare @given_name varchar(32) = 'Lisa'
; with
OwnedCount as (
select *
, (select COUNT(*)
from #owners o2
where o2.name = o1.name) as num
from #owners o1
)
select o2.name, o2.model
from OwnedCount o1
inner join OwnedCount o2
on o1.model = o2.model
and o1.num = o2.num
where o1.name = @given_name
and o2.name <> @given_name
try this,i think it is much easier and short code with just one partition function.
declare @t table(Name varchar(50),Model varchar(50))
insert into @t values
('Bob','Camry')
,('Bob','Civic')
,('Bob','Prius')
,('Kevin','Civic')
,('Kevin','Focus')
,('Mark','Civic')
,('Lisa','Focus')
,('Lisa','Civic')
declare @input varchar(50)='Lisa'
;with
CTE1 AS
(
select name,model,ROW_NUMBER()over( order by name) rn
from @t
where name=@input
)
,cte2 as
(
select t.name,t.Model
,ROW_NUMBER()over(partition by t.name order by t.name) rn3
from @t t
inner JOIN
cte1 c on t.Model=c.model
where t.Name !=@input
)
select * from cte2 c
where exists(select rn3 from cte2 c1
where c1.name=c.name and c1.rn3=(select max(rn) from cte1)
)