November 22, 2005 at 8:35 am
I need a query for the following (simplified) situation:
There is a table with customers, a table with products, and a link table that links many customers to many products.
The query I need will find all customers that ordered product x AND product y AND product z (etc ...)
Can anyone help me the right SELECT ... for this problem
November 22, 2005 at 9:25 am
IF EXISTS (Select * from dbo.SysObjects where name = 'fnSplit_Set' and XType IN ('FN', 'IF', 'TF'))
DROP FUNCTION dbo.fnSplit_Set
GO
IF EXISTS (Select * from dbo.SysObjects where name = 'Numbers' and XType = 'U')
DROP TABLE dbo.Numbers
GO
CREATE TABLE dbo.Numbers (PkNumber int identity(1,1), dude bit null, CONSTRAINT Pk_Number PRIMARY KEY CLUSTERED (PkNumber))
GO
INSERT INTO dbo.Numbers (dude)
SELECT NULL FROM (SELECT TOP 100 NULL AS A FROM master.dbo.spt_values) dt100 cross join (SELECT TOP 80 null AS A FROM master.dbo.spt_values) dt80
GO
ALTER TABLE dbo.Numbers
DROP COLUMN dude
GO
--Select min(PkNumber) as MinA, max(PkNumber) as MaxA from dbo.Numbers
--1, 8000
GO
CREATE FUNCTION [dbo].[fnSplit_Set] (@IDS as varchar(8000), @vcDelimiter varchar(3))
RETURNS TABLE
WITH SCHEMABINDING
AS
Return
Select dtSplitted.EachID, dtSplitted.Rank from (
SELECT SUBSTRING(@vcDelimiter + @IDs + @vcDelimiter, N.PkNumber + len(@vcDelimiter),
CHARINDEX(@vcDelimiter, @vcDelimiter + @IDs + @vcDelimiter, N.PkNumber + len(@vcDelimiter)) - N.PkNumber - len(@vcDelimiter)) as EachID
, (LEN(SUBSTRING(@IDs, 1, N.PkNumber)) - LEN (REPLACE (SUBSTRING(@IDs, 1, N.PkNumber), ',', ''))) + 1 AS Rank
FROM dbo.Numbers N
WHERE SUBSTRING(@vcDelimiter + @IDs + @vcDelimiter, N.PkNumber, len(@vcDelimiter)) = @vcDelimiter
AND PkNumber 0
GO
DECLARE @Ids as varchar(8000)
SET @IDS = ''
Select @IDS = @IDS + CAST(id as varchar(10)) + ',' from (Select TOP 10 id from dbo.SysObjects order by NEWID()) dtIds
SET @IDS = left(@IDS, ABS(len(@IDS) - 1))
PRINT @IDS
Select O.id, dtIDS.EachId, O.name, dtIDS.Rank from dbo.SysObjects O inner join (Select CAST(EachID as INT) AS EachID, Rank from dbo.fnSplit_Set (@IDS, ',')) dtIDS on O.id = dtIDS.EachID order by O.Name
Select O.id, dtIDS.EachId, O.name, dtIDS.Rank from dbo.SysObjects O inner join (Select CAST(EachID as INT) AS EachID, Rank from dbo.fnSplit_Set (@IDS, ',')) dtIDS on O.id = dtIDS.EachID order by dtIDS.Rank
--Dont't ever forget to cast the eachid column to the same datatype as in the join to avoid table scans.
GO
IF NOT Object_id('SearchColumns') IS NULL
DROP PROCEDURE SearchColumns
GO
CREATE PROCEDURE dbo.SearchColumns @Items as varchar(8000), @ItemsCount as smallint --avoid calling split twice
AS
SET NOCOUNT ON
SELECT
O.id
, O.Name
FROMdbo.SysObjects O
WHERE Exists(
SELECT 1
FROMdbo.SysColumns C
WHERE C.id = O.id and C.Name in (Select EachID from dbo.fnSplit_Set(@Items, ','))
GROUP BY id having count(*) >= @ItemsCount
)
ORDER BYName
SET NOCOUNT OFF
GO
--find all items
Exec dbo.SearchColumns 'name,id', 2-- 4 row(s) affected
Exec dbo.SearchColumns 'name,id,indid', 3-- 1 row(s) affected
Exec dbo.SearchColumns 'id', 1-- 12 row(s) affected
--find at least 2
Exec dbo.SearchColumns 'name,id,indid', 2-- 5 row(s) affected (instead of 1)
--find at least 1
Exec dbo.SearchColumns 'name', 1-- 10 row(s) affected
Exec dbo.SearchColumns 'name,indid', 1-- 11 row(s) affected
GO
DROP PROCEDURE SearchColumns
--DROP FUNCTION fnSplit_Set
GO
November 22, 2005 at 10:05 am
Below is a fairly simple method. Note joining the table on itself (link table) can be cpu intensive.
SELECT distinct ct.customer_name
FROM (select customerid from linktable where productlid = a) a
JOIN (select customerid from linktable where productlid = b) b
on a.customerid = b.customerid
JOIN (select customerid from linktable where productlid = b) c
on b.customerid = c.customerid
JOIN customer_table ct ON ct.customerid = a.customerid
November 22, 2005 at 10:23 am
Yes but this doesn't allow for a dynamic number of parameters.
November 22, 2005 at 10:53 am
Sorry, neglected to catch that in the question.
Another option would be to create the sql statement in a cursor
and then exectue the statement. Storing the result in a tmp table.
cursor
...
WHILE @@FETCH_STATUS = 0
begin
strSQL = strSQL + 'JOIN (select customer from linktable where product = 'x') tableident#
ON tableident1.customerid = tableident#.customerid) '
fetch next into
end
insert into #tmpResult
EXEC sp_executeSQL @strSQL
November 22, 2005 at 12:28 pm
Test that on a 1000 rows table with 25 left joins... Now test mine after that...
November 22, 2005 at 1:08 pm
Very correct, the above derived tables on the original link_table performance would rapidly degrade.
The simplest approach
SELECT distinct customer
FROM customer join link-table
where link-table.product in (yada)
November 22, 2005 at 1:58 pm
Insert list of queried produxct to temp table #QProduct
SELECT CustomerId
FROM Customer C
Inner join CustomerProduct CP on CustomerId = CP.CustomerId
Inner join Product P on P.Productd = CP.ProductId
Inner join #QproductId QP on QP.ProductCode (??) = P.ProductCode
Group by CustomerId
HAVING count(QP.ProductCode) = (select count(ProductCode) from QProduct)
_____________
Code for TallyGenerator
November 22, 2005 at 3:22 pm
And if you read the poster correctly you notice that the many-to-many is already populated therefore simplifiying Sergiy's strategy to:
SELECT l.CustomerId
FROM LINKEDTABLE l join #QproductId QP on QP.Productid = l.ProductId
Group by l.CustomerId
HAVING count(l.Productid) = (select count(*) from #QProductid)
* Noel
Viewing 9 posts - 1 through 8 (of 8 total)
You must be logged in to reply to this topic. Login to reply