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 testingBEGINCREATE TABLE #cust_company ( company_id INT IDENTITY NOT NULL , company_name NVARCHAR(50) NOT NULL ) ENDBEGINCREATE TABLE #cust_contact ( company_id INT NOT NULL , contact_no INT NOT NULL , contact_name NVARCHAR(50) NOT NULL )ENDBEGINCREATE TABLE #order_header ( order_id INT IDENTITY NOT NULL , company_id INT NOT NULL , contact_no INT NOT NULL )END-- insert sample data-- sample companiesBEGIN 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 contactsBEGIN 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 contactsBEGIN 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 ENDEND-- return contacts who have placed ordersBEGIN 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(*) DESCEND-- clean upBEGIN DROP TABLE #cust_companyENDBEGIN DROP TABLE #cust_contactENDBEGIN DROP TABLE #order_headerEND 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) |
 |
|
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 AthalyeIndia."Nothing is Impossible" |
 |
|
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 |
 |
|
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 ordersBEGIN 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) DESCEND Also if anyone knows an alternate way to do this, please share -- options are good!Thank you,Daniel*Edit - code cleanup |
 |
|
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. |
 |
|
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 ordersBEGIN 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) DESCEND 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) DESCEND Harsh AthalyeIndia."Nothing is Impossible" |
 |
|
CorpDirect
Yak Posting Veteran
92 Posts |
Posted - 2006-10-12 : 11:41:06
|
quote: Originally posted by snSQLBased 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... |
 |
|
CorpDirect
Yak Posting Veteran
92 Posts |
Posted - 2006-10-12 : 12:23:58
|
quote: Originally posted by harsh_athalyeLooks 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 joinFROM table1 t1 , table2 t2WHERE t1.colA = t2.colA-- explicit joinFROM 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 |
 |
|
snSQL
Master Smack Fu Yak Hacker
1837 Posts |
Posted - 2006-10-12 : 12:44:38
|
quote: Originally posted by CorpDirect
quote: Originally posted by snSQLBased 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 contactSELECT 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. |
 |
|
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_athalyeLooks 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 joinFROM table1 t1 , table2 t2WHERE t1.colA = t2.colA-- explicit joinFROM 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 AthalyeIndia."Nothing is Impossible" |
 |
|
CorpDirect
Yak Posting Veteran
92 Posts |
Posted - 2006-10-12 : 13:45:57
|
quote: Originally posted by snSQLAh, OK then just adjust the WHERE clause to include the contactWHERE 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". |
 |
|
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. |
 |
|
CorpDirect
Yak Posting Veteran
92 Posts |
Posted - 2006-10-12 : 15:47:15
|
Thank you both for your help with this!Regards,Daniel |
 |
|
|