Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2000 Forums
 Transact-SQL (2000)
 find compound keys not used?

Author  Topic 

CorpDirect
Yak Posting Veteran

92 Posts

Posted - 2006-10-11 : 16:22:13
Hello all,

I'm looking for a way to identify contacts who have not placed orders. Some sample data is provided below, much simplified, but here's how the application manages companies (customers) and contacts:
  • each company has a unique id

  • each contact is identified by company and a sequential number

  • the contact sequence starts from 1 for each company's contacts

Contacts are identified by company_id, contact_no; this is simple enough to use when looking for information about contacts who HAVE placed orders... but how do I find out who HAS NOT placed orders?

Sample environment and data (generates order header records for random contacts, returns order header information):
-- create temp tables for testing
BEGIN
CREATE TABLE #cust_company (
company_id INT IDENTITY NOT NULL
, company_name NVARCHAR(50) NOT NULL
)
END

BEGIN
CREATE TABLE #cust_contact (
company_id INT NOT NULL
, contact_no INT NOT NULL
, contact_name NVARCHAR(50) NOT NULL
)
END

BEGIN
CREATE TABLE #order_header (
order_id INT IDENTITY NOT NULL
, company_id INT NOT NULL
, contact_no INT NOT NULL
)
END

-- insert sample data
-- sample companies
BEGIN
INSERT INTO #cust_company (company_name)
SELECT company_name = 'ABC Company'
UNION ALL
SELECT company_name = 'DEF Company'
UNION ALL
SELECT company_name = 'GHI Company'
END

-- sample contacts
BEGIN
INSERT INTO #cust_contact (company_id, contact_no, contact_name)
SELECT company_id = 1, contact_no = 1, contact_name = 'Joe Smith'
UNION ALL
SELECT company_id = 1, contact_no = 2, contact_name = 'Jane Grey'
UNION ALL
SELECT company_id = 2, contact_no = 1, contact_name = 'Tom Jones'
UNION ALL
SELECT company_id = 3, contact_no = 1, contact_name = 'Mary Thompson'
UNION ALL
SELECT company_id = 3, contact_no = 2, contact_name = 'Larry Watts'
END

-- sample orders for random contacts
BEGIN
DECLARE @Count INT
SET @Count = 1
WHILE @Count <= 4
BEGIN
INSERT INTO #order_header (company_id, contact_no)
SELECT TOP 1 company_id = c.company_id, contact_no = c.contact_no
FROM #cust_contact c
ORDER BY NewID()
SET @Count = @Count + 1
END
END

-- return contacts who have placed orders
BEGIN
SELECT
cu.company_name
, co.contact_name
, orders = count(*)
FROM
#order_header o
, #cust_company cu
, #cust_contact co
WHERE
o.company_id = cu.company_id
AND
o.company_id = co.company_id
AND
o.contact_no = co.contact_no
GROUP BY
cu.company_name
, co.contact_name
ORDER BY
count(*) DESC
END

-- clean up
BEGIN
DROP TABLE #cust_company
END

BEGIN
DROP TABLE #cust_contact
END

BEGIN
DROP TABLE #order_header
END

Thanks for any suggestions!

Regards,

Daniel

snSQL
Master Smack Fu Yak Hacker

1837 Posts

Posted - 2006-10-12 : 00:21:47
Try these queries to find those with and without orders
	SELECT DISTINCT
cu.company_name
, co.contact_name
FROM
#order_header o
, #cust_company cu
, #cust_contact co
WHERE
o.company_id = cu.company_id
AND
o.company_id = co.company_id
AND
o.contact_no = co.contact_no


SELECT
cu.company_name
, co.contact_name
FROM
#cust_company cu
, #cust_contact co
WHERE
co.company_id = cu.company_id
AND
NOT EXISTS (SELECT * FROM #order_header o WHERE o.company_id = cu.company_id)
Go to Top of Page

harsh_athalye
Master Smack Fu Yak Hacker

5581 Posts

Posted - 2006-10-12 : 00:31:28
You can use Outer join like below:

SELECT
cu.company_name
, co.contact_name
, orders = count(o.order_id)
FROM
#order_header o
join #cust_company cu on o.company_id = cu.company_id
right join #cust_contact co on o.company_id = co.company_id
and o.contact_no = co.contact_no
WHERE
o.company_id is null and o.contact_no is null
GROUP BY
cu.company_name
, co.contact_name
ORDER BY
count(o.order_id) DESC


Harsh Athalye
India.
"Nothing is Impossible"
Go to Top of Page

CorpDirect
Yak Posting Veteran

92 Posts

Posted - 2006-10-12 : 09:45:15
snSQL,

Your query sometimes returns no results, so it cannot be correct -- there are less order_header records generated than contacts, so each time some must not be used.

Harsh Athalye,

Your query returns results each time, but company_name is always null. Is there something about the way tables are joined that causes this?

Too bad there's not something like this available:
WHERE co.company_id, co.contact_no NOT IN (SELECT company_id, contact_no FROM #order_header)

Regards,

Daniel
Go to Top of Page

CorpDirect
Yak Posting Veteran

92 Posts

Posted - 2006-10-12 : 09:51:58
Harsh Athalye,

Looks like you were on target with the outer join. I worked with your concept to come up with a query that seems to give company name and contact name for contacts who have not ordered. Please take a look and let me know what you think.

-- return contacts who have not placed orders
BEGIN
SELECT
cu.company_name
, co.contact_name
, orders = count(o.order_id)
FROM
#cust_company cu
, #cust_contact co
LEFT JOIN #order_header o
ON co.company_id = o.company_id
AND co.contact_no = o.contact_no
WHERE
cu.company_id = co.company_id
AND
o.company_id IS NULL
AND
o.contact_no IS NULL
GROUP BY
cu.company_name
, co.contact_name
ORDER BY
count(o.order_id) DESC
END


Also if anyone knows an alternate way to do this, please share -- options are good!

Thank you,

Daniel

*Edit - code cleanup
Go to Top of Page

snSQL
Master Smack Fu Yak Hacker

1837 Posts

Posted - 2006-10-12 : 10:12:16
quote:
snSQL,

Your query sometimes returns no results, so it cannot be correct

Based on your example I assumed that an order always had a customer and a contact, if that is not the case then you will need to use the outer join and your original example must also not always return results.
Go to Top of Page

harsh_athalye
Master Smack Fu Yak Hacker

5581 Posts

Posted - 2006-10-12 : 10:21:41
quote:
Originally posted by CorpDirect

Harsh Athalye,

Looks like you were on target with the outer join. I worked with your concept to come up with a query that seems to give company name and contact name for contacts who have not ordered. Please take a look and let me know what you think.

-- return contacts who have not placed orders
BEGIN
SELECT
cu.company_name
, co.contact_name
, orders = count(o.order_id)
FROM
#cust_company cu
, #cust_contact co
LEFT JOIN #order_header o
ON co.company_id = o.company_id
AND co.contact_no = o.contact_no
WHERE
cu.company_id = co.company_id
AND
o.company_id IS NULL
AND
o.contact_no IS NULL
GROUP BY
cu.company_name
, co.contact_name
ORDER BY
count(o.order_id) DESC
END


Also if anyone knows an alternate way to do this, please share -- options are good!

Thank you,

Daniel

*Edit - code cleanup



Looks fine to me!

One thing is you can use ANSI join to avoid the risk of inadvertently having cross join.

Like this:

BEGIN
SELECT
cu.company_name
, co.contact_name
, orders = count(o.order_id)
FROM
#cust_company cu
Join #cust_contact co
on cu.company_id = co.company_id
LEFT JOIN #order_header o
ON co.company_id = o.company_id
AND co.contact_no = o.contact_no
WHERE
o.company_id IS NULL
AND
o.contact_no IS NULL
GROUP BY
cu.company_name
, co.contact_name
ORDER BY
count(o.order_id) DESC
END



Harsh Athalye
India.
"Nothing is Impossible"
Go to Top of Page

CorpDirect
Yak Posting Veteran

92 Posts

Posted - 2006-10-12 : 11:41:06
quote:
Originally posted by snSQL
Based on your example I assumed that an order always had a customer and a contact, if that is not the case then you will need to use the outer join and your original example must also not always return results.


Every order DOES have a customer and contact. I'm looking for the set of contacts who have NOT ordered. Your query returned no records each time...
Go to Top of Page

CorpDirect
Yak Posting Veteran

92 Posts

Posted - 2006-10-12 : 12:23:58
quote:
Originally posted by harsh_athalye
Looks fine to me!

One thing is you can use ANSI join to avoid the risk of inadvertently having cross join.


Do you mean so that you don't forget to specify join criteria? Seems to me that these two are the same:

-- implicit join
FROM
table1 t1
, table2 t2
WHERE
t1.colA = t2.colA

-- explicit join
FROM
table1 t1
JOIN table2 t2
ON t1.colA = t2.colA


The difference is just one of semantics, right? No difference in their handling/processing by SQL Server?

So I assume you are suggesting using JOIN so that you will follow with ON and remember to define the relationship. Please correct me if I'm missing something!

Thanks,

Daniel
Go to Top of Page

snSQL
Master Smack Fu Yak Hacker

1837 Posts

Posted - 2006-10-12 : 12:44:38
quote:
Originally posted by CorpDirect

quote:
Originally posted by snSQL
Based on your example I assumed that an order always had a customer and a contact, if that is not the case then you will need to use the outer join and your original example must also not always return results.


Every order DOES have a customer and contact. I'm looking for the set of contacts who have NOT ordered. Your query returned no records each time...


Ah, OK then just adjust the WHERE clause to include the contact

SELECT
cu.company_name
, co.contact_name
FROM
#cust_company cu
, #cust_contact co
WHERE
co.company_id = cu.company_id
AND
NOT EXISTS (SELECT * FROM #order_header o WHERE o.company_id = cu.company_id AND o.contact_no = co.contact_no)

This is not necessarily better or worse than the join option, you'd need to test.
Go to Top of Page

harsh_athalye
Master Smack Fu Yak Hacker

5581 Posts

Posted - 2006-10-12 : 13:45:13
quote:
Originally posted by CorpDirect

quote:
Originally posted by harsh_athalye
Looks fine to me!

One thing is you can use ANSI join to avoid the risk of inadvertently having cross join.


Do you mean so that you don't forget to specify join criteria? Seems to me that these two are the same:

-- implicit join
FROM
table1 t1
, table2 t2
WHERE
t1.colA = t2.colA

-- explicit join
FROM
table1 t1
JOIN table2 t2
ON t1.colA = t2.colA


The difference is just one of semantics, right? No difference in their handling/processing by SQL Server?

So I assume you are suggesting using JOIN so that you will follow with ON and remember to define the relationship. Please correct me if I'm missing something!

Thanks,

Daniel



Yes...it's matter of coding style !

Both alternatives are performance-wise equivalent.

JOIN..ON syntax is ANSI-92 standard while adding condition in WHERE clause is not. It's better to be explicit about what you do...it will make your code more readable, too.

Harsh Athalye
India.
"Nothing is Impossible"
Go to Top of Page

CorpDirect
Yak Posting Veteran

92 Posts

Posted - 2006-10-12 : 13:45:57
quote:
Originally posted by snSQL
Ah, OK then just adjust the WHERE clause to include the contact

WHERE
NOT EXISTS (SELECT * FROM #order_header o WHERE o.company_id = cu.company_id AND o.contact_no = co.contact_no)

This is not necessarily better or worse than the join option, you'd need to test.



Now it works! I confess I don't really understand how EXISTS works in this context. It makes sense to me when checking for an object or a single value, but for set-theory operations* involving multiple comparisons, I would never think to use this.

Thanks for demonstrating the option!

Regards,

Daniel

*- The phrase "set-theory operations" is taken from the BOL article "Using EXISTS and NOT EXISTS to Find Intersection and Difference".
Go to Top of Page

snSQL
Master Smack Fu Yak Hacker

1837 Posts

Posted - 2006-10-12 : 14:03:46
To phrase your query in natrual language (which may make it easier to understand)

Return the company name and contact name for all customers and contacts where there is not at least one order header for that customer and contact.

Basically SQL Server looks at the query and says would it return at least one row - if so EXISTS is true, if not then NOT EXISTS is true.
Go to Top of Page

CorpDirect
Yak Posting Veteran

92 Posts

Posted - 2006-10-12 : 15:47:15
Thank you both for your help with this!

Regards,

Daniel
Go to Top of Page
   

- Advertisement -