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)
)