Solved

SQL query that matches all values in subquery

Posted on 2008-10-03
8
195 Views
Last Modified: 2012-06-27
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

0
Comment
Question by:mikewiz
  • 4
  • 4
8 Comments
 
LVL 39

Accepted Solution

by:
BrandonGalderisi earned 250 total points
ID: 22639503
I think this covers all the scenarios....


I added a bunch of different contracts
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
 

print @i_constraintType
 

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 mrn=@AccountTypeNum

where rn=1

end

 
 
 

go

exec dbo.AccountTypeMatchingContracts 'a,b','all'

exec dbo.AccountTypeMatchingContracts 'a,b,c','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

0
 

Author Comment

by:mikewiz
ID: 22649688
Thanks for the response, checking out your solution now.  Will award points if it works...
0
 

Author Closing Comment

by:mikewiz
ID: 31502969
Thank you for the excellent code, very easy to read and was able to implement flawlessly
0
 

Author Comment

by:mikewiz
ID: 22652228
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'
0
What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

 
LVL 39

Expert Comment

by:BrandonGalderisi
ID: 22652616
ok... i'll hash that out
0
 
LVL 39

Expert Comment

by:BrandonGalderisi
ID: 22654235
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

0
 

Author Comment

by:mikewiz
ID: 22654531
Worked like a charm... Thanks again for all the help!
0
 
LVL 39

Expert Comment

by:BrandonGalderisi
ID: 22654634
:)
0

Featured Post

What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

Suggested Solutions

Ever needed a SQL 2008 Database replicated/mirrored/log shipped on another server but you can't take the downtime inflicted by initial snapshot or disconnect while T-logs are restored or mirror applied? You can use SQL Server Initialize from Backup…
Ever wondered why sometimes your SQL Server is slow or unresponsive with connections spiking up but by the time you go in, all is well? The following article will show you how to install and configure a SQL job that will send you email alerts includ…
Via a live example, show how to backup a database, simulate a failure backup the tail of the database transaction log and perform the restore.
Via a live example, show how to set up a backup for SQL Server using a Maintenance Plan and how to schedule the job into SQL Server Agent.

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now