Link to home
Start Free TrialLog in
Avatar of mikewiz
mikewiz

asked on

SQL query that matches all values in subquery

Greetings everyone,
A little background...  I have a Contract and Account Type table, with a cross-reference table in between.  A contract can have multiple account types and an account type can belong to multiple contracts.
I'm trying to write a search function with a constraint type of Exact Match, Any or All.
In the case of Exact Match constraint type, if 3 account types were selected, it would return only contracts that have the exact three account types, no more no less... Hence exact match.
In the case of Any, if 3 account types were selected, it would returns contracts that match any combination of the 3.  If the account types are A, B, C; contracts with just A, just B, just C, A&B, A&C, B&C or A&B&C would be return.
I'm having a difficult time writing the "All" piece, which by requirement definition is "return all contracts that contain all the selected account types and have additional account types"   The way that would work is, any contract that had, at minimum, account types A&B&C would be returned.  And any contracts with A&B&C & D would be returned as well.

The way I'm handling the Exact Match I think could be written better as well.  I tried using the ALL logical operator, but it did not return any values (SELECT * FROM AccountTypeInContract WHERE AccountTypeID = ALL(SELECT ParsedValue FROM #ParsedValuesTable) )  There should be at least one record returned in that example, but I get zero records returned.

My existing code is shown below, this works for the case of Exact or Any.  Any help or suggestions is appreciated for how to write something to handle the "All" constraint scenario.
CREATE PROCEDURE dbo.AccountTypeMatchingContracts
(@i_AccountTypes 	 varchar(200) --Comma seperated string of AccountTypes
,@i_ConstraintType	 varchar(10)  --Expected Values, Exact, Any, All
)
AS
 
--Populate a temp table with AccountTypes split out
EXEC ParseString @i_AccountTypes 
 
DECLARE @l_NumberAccountTypes   int
SELECT @l_NumberAccountTypes = COUNT(*) FROM #ParsedValuesTable
 
 
--Get all contracts that have the selected account types
SELECT ContractID, AccountTypeID
INTO #temp_ContractsWithMatchingAccountTypes
FROM AccountTypeInContract
WHERE AccountTypeID IN (SELECT ParsedValue FROM #ParsedValuesTable)
 
--Get all the contracts with one of the selected account types, that 
--have additional contract types
SELECT ContractID, AccountTypeID
INTO #temp_ContractsWithAdditionalAccountTypes
FROM AccountTypeInContract
WHERE ContractID IN (SELECT ContractID FROM #temp_ContractsWithMatchingAccountTypes)
  AND AccountTypeID NOT IN (SELECT ParsedValue FROM #ParsedValuesTable)
 
--Remove contracts that have more account types than the selected ones
DELETE #temp_ContractsWithMatchingAccountTypes
WHERE ContractID IN (SELECT ContractID FROM #temp_ContractsWithAdditionalAccountTypes)
 
IF @i_ConstraintType = 'Exact'
BEGIN
   --Since only contracts that contain the selected account types 
   --remain, we can remove any that do not have the same count
	DELETE #temp_ContractsWithMatchingAccountTypes
	WHERE ContractID IN 
		(SELECT ContractID 
		 FROM #temp_ContractsWithMatchingAccountTypes
		 GROUP BY ContractID
		 HAVING COUNT(*) <> @l_NumberAccountTypes )
END
 
--Return Contracts to calling proc
SELECT DISTINCT ContractID 
FROM #temp_ContractsWithMatchingAccountTypes
 
DROP TABLE #ParsedValuesTable
DROP TABLE #temp_ContractsWithAdditionalAccountTypes
DROP TABLE #temp_ContractsWithMatchingAccountTypes
GO

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of BrandonGalderisi
BrandonGalderisi
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of mikewiz
mikewiz

ASKER

Thanks for the response, checking out your solution now.  Will award points if it works...
Avatar of mikewiz

ASKER

Thank you for the excellent code, very easy to read and was able to implement flawlessly
Avatar of mikewiz

ASKER

Found an error in the code, trying to solve now...

In the case where the constraint type is exact and string is a,b  it returns the contract with ABC.  It should only return contract 11

exec dbo.AccountTypeMatchingContracts 'a,b,'exact'
ok... i'll hash that out
I think I got it...

I had to add another inner join to the exact section.
set nocount on
create table #contracts
 (contract_id       int
 ,contract_name     varchar(32)
 )
 
create table #accountType
 (AccountType       varchar(10)
 )
 
create table #ContractAccountTypes
 (Contract_Id        int
 ,AccountType       varchar(10)
 )
 
insert into #contracts values (1, 'HasA')
insert into #contracts values (2, 'HasB')
insert into #contracts values (3, 'HasC')
 
insert into #contracts values (11, 'HasAB')
insert into #contracts values (12, 'HasBC')
insert into #contracts values (13, 'HasAC')
 
 
insert into #contracts values (111, 'HasABC')
insert into #contracts values (112, 'HasBCD')
insert into #contracts values (113, 'HasACD')
 
insert into #AccountType values('A')
insert into #AccountType values('B')
insert into #AccountType values('C')
insert into #AccountType values('D')
 
insert into #ContractAccountTypes values(1,'A')
insert into #ContractAccountTypes values(2,'B')
insert into #ContractAccountTypes values(3,'C')
 
insert into #ContractAccountTypes values(11,'A')
insert into #ContractAccountTypes values(11,'B')
 
insert into #ContractAccountTypes values(12,'B')
insert into #ContractAccountTypes values(12,'C')
 
insert into #ContractAccountTypes values(13,'A')
insert into #ContractAccountTypes values(13,'C')
 
insert into #ContractAccountTypes values(111,'A')
insert into #ContractAccountTypes values(111,'B')
insert into #ContractAccountTypes values(111,'C')
 
insert into #ContractAccountTypes values(112,'B')
insert into #ContractAccountTypes values(112,'C')
insert into #ContractAccountTypes values(112,'D')
 
insert into #ContractAccountTypes values(113,'A')
insert into #ContractAccountTypes values(113,'C')
insert into #ContractAccountTypes values(113,'D')
 
go
if object_id('[dbo].[fn_DelimitedToTable]') is not null
     drop function [dbo].[fn_DelimitedToTable]
go
create function [dbo].[fn_DelimitedToTable](@DelimitedString nvarchar(max), @Delimiter nvarchar(32))
returns @Values TABLE
     (ident         int not null identity primary key clustered
     ,thePosition   int not null
     ,theValue      nvarchar(max)
     )
as
begin
 
insert into @Values (thePosition,theValue)
                select n, substring(@delimiter + @DelimitedString + @delimiter, n + (datalength(@delimiter)/2), charindex(@delimiter, @delimiter + @DelimitedString + @delimiter, n + len(@delimiter)) - n - len(@delimiter)) as string_value
                from    dbo.vw_Nums
                where
                        n <= (datalength(@delimiter + @DelimitedString + @delimiter)/2) - (datalength(@delimiter)/2)
                        and substring(@delimiter + @DelimitedString + @delimiter, n, (datalength(@delimiter)/2)) = @delimiter
 
 
 
return
end
/*
 
Requires:
create view vw_Nums
as
with
       cte0 as (select 1 as c union all select 1), -- 2
       cte1 as (select 1 as c from cte0 a, cte0 b), -- 4
       cte2 as (select 1 as c from cte1 a, cte1 b), -- 16
       cte3 as (select 1 as c from cte2 a, cte2 b), -- 256
       cte4 as (select 1 as c from cte3 a, cte3 b), -- 65,536
       cte5 as (select 1 as c from cte4 a, cte4 b), -- 4,294,967,296 --four BILLION, not million
       nums as (select row_number() over (order by c) as n from cte5)
       select n from nums 
 
 
select * from [dbo].[fn_DelimitedToTable]('a|%25basdf|%25c|%25d','|%25')
 
select theValue from [dbo].[fn_DelimitedToTable]('a','|')
*/
GO
 
 
drop PROCEDURE dbo.AccountTypeMatchingContracts
go
CREATE PROCEDURE dbo.AccountTypeMatchingContracts
(@i_AccountTypes         varchar(200) --Comma seperated string of AccountTypes
,@i_ConstraintType       varchar(10)  --Expected Values, Exact, Any, All
)
AS
declare @AccountTypeNum int
declare @AccountTypes table
(AccountType  varchar(10)
)
insert into @AccountTypes
select theValue from [dbo].[fn_DelimitedToTable](@i_AccountTypes,',')
select @AccountTypeNum = @@rowcount
 
 
if @i_constraintType = 'ALL'
begin
;with matches as (
select c.*
,row_number() over (partition by c.contract_id order by at.accounttype)rn from #Contracts c
  inner join #contractAccountTypes cat
    on c.contract_id = cat.contract_id
  inner join @AccountTypes AT
    on  cat.accounttype = at.accounttype
)
select * from matches
where rn=@AccountTypeNum
end
 
if @i_constraintType = 'any'
begin
;with matches as (
select distinct c.*
from #Contracts c
inner join (select distinct cat.contract_id from #contractAccountTypes cat
              inner join @AccountTypes AT 
                on cat.accounttype = at.accounttype) m
  on c.contract_id = m.contract_id
)
select * from matches
 
end
 
if @i_constrainttype = 'exact'
begin
;with matches as (
select c.*
,row_number() over (partition by c.contract_id order by at.accounttype)rn from #Contracts c
  inner join #contractAccountTypes cat
    on c.contract_id = cat.contract_id
  inner join @AccountTypes AT
    on  cat.accounttype = at.accounttype
)
select m.* from matches m
inner join (select contract_id, max(rn) mrn from matches group by contract_id) mm
on m.contract_id = mm.contract_id
 and mm.mrn=@AccountTypeNum
inner join (select contract_id, count(*) mrn from #ContractAccountTypes group by contract_id) mm2
on m.contract_id = mm2.contract_id
where m.rn=1
and mm2.mrn=@AccountTypeNum
 
end
 
 
 
go
--exec dbo.AccountTypeMatchingContracts 'a,b','all'
--exec dbo.AccountTypeMatchingContracts 'a,b,c','exact'
exec dbo.AccountTypeMatchingContracts 'a,b','exact'
exec dbo.AccountTypeMatchingContracts 'a','exact'
--exec dbo.AccountTypeMatchingContracts 'a,b,c','any'
--exec dbo.AccountTypeMatchingContracts 'a,d,c','exact'
go
drop table #contracts
drop table #accounttype
drop table #contractaccounttypes

Open in new window

Avatar of mikewiz

ASKER

Worked like a charm... Thanks again for all the help!