473,322 Members | 1,714 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,322 software developers and data experts.

Help with SELECT Query

I'm not a SQL expert. I want to be able to write a stored procedure
that will return 'people who bought this product also bought this...'.

I have a user table that links to a transaction table that links to a
transaction items table that links to the products table:

(User Table)
UserID
Other user data

(Transaction Table)
TransactionID
UserID
Other transaction data such as the date and the transaction result

(TransactionItem Table)
TransactionItemID
TransactionID
ProductID

(Product Table)
ProductID
Other product data

If I try to nest a SELECT query to give me the list of UserIDs for all
users who purchased a given ProductID then SQL Server gets very upset
as Nested Querys are only supposed to return a single value.

So, how do I do this? Build the first list of UserIDs and then select
all other ProductIDs for the users in the list excluding the original
ProductID?

I'm certain this must be a fairly straight forward thing for a SQL
server guru so any help would be appreciated...

Thanks

Jim

Jul 23 '05 #1
4 2806
Hi

See how to help use by posting DDL
http://www.aspfaq.com/etiquett*e.asp?id=5006 and example data.

This will get them for products X abd Y if they bought them in the same
transaction and you want details from both products:

SELECT U.[UserID], U.[Other user data], T.[TransactionID],
I.[TransactionItemID],
P.[ProductID],P.[Other product data], J.[TransactionItemID],
Q.[ProductID],Q.[Other product data]
FROM [User Table] U
JOIN [Transaction Table] T ON U.[UserID] = T.[UserID]
JOIN [TransactionItem Table] I ON T.[TransactionID] = I.[TransactionID]
JOIN [Product Table] P ON I.[ProductID] = P.[ProductID] AND P.[Other product
data] = 'X'
JOIN [TransactionItem Table] J ON T.[TransactionID] = J.[TransactionID]
JOIN [Product Table] Q ON J.[ProductID] = Q.[ProductID] AND Q.[Other product
data] = 'Y'

Alternatively:

SELECT U.[UserID], U.[Other user data], T.[TransactionID],
I.[TransactionItemID],
P.[ProductID],P.[Other product data], J.[TransactionItemID],
Q.[ProductID],Q.[Other product data]
FROM [User Table] U
JOIN [Transaction Table] T ON U.[UserID] = T.[UserID]
JOIN [TransactionItem Table] I ON T.[TransactionID] = I.[TransactionID]
JOIN [Product Table] P ON I.[ProductID] = P.[ProductID] AND P.[Other product
data] = 'X'
WHERE EXISTS ( SELECT * FROM
JOIN [TransactionItem Table] J
JOIN [Product Table] Q ON J.[ProductID] = Q.[ProductID] AND Q.[Other product
data] = 'Y'
WHERE T.[TransactionID] = J.[TransactionID])

Or if you want it at user level and not necessarily in the same transaction:

SELECT U.[UserID], U.[Other user data], T.[TransactionID],
I.[TransactionItemID],
P.[ProductID],P.[Other product data], J.[TransactionItemID],
Q.[ProductID],Q.[Other product data]
FROM [User Table] U
JOIN [Transaction Table] T ON U.[UserID] = T.[UserID]
JOIN [TransactionItem Table] I ON T.[TransactionID] = I.[TransactionID]
JOIN [Product Table] P ON I.[ProductID] = P.[ProductID] AND P.[Other product
data] = 'X'
WHERE EXISTS ( SELECT * FROM
JOIN [Transaction Table] S
JOIN [TransactionItem Table] J ON S.[TransactionID] = J.[TransactionID]
JOIN [Product Table] Q ON J.[ProductID] = Q.[ProductID] AND Q.[Other product
data] = 'Y'
WHERE U.[UserID] = S.[UserID] )

John

<ji**@netwasp.com> wrote in message
news:11*********************@f14g2000cwb.googlegro ups.com...
I'm not a SQL expert. I want to be able to write a stored procedure
that will return 'people who bought this product also bought this...'.

I have a user table that links to a transaction table that links to a
transaction items table that links to the products table:

(User Table)
UserID
Other user data

(Transaction Table)
TransactionID
UserID
Other transaction data such as the date and the transaction result

(TransactionItem Table)
TransactionItemID
TransactionID
ProductID

(Product Table)
ProductID
Other product data

If I try to nest a SELECT query to give me the list of UserIDs for all
users who purchased a given ProductID then SQL Server gets very upset
as Nested Querys are only supposed to return a single value.

So, how do I do this? Build the first list of UserIDs and then select
all other ProductIDs for the users in the list excluding the original
ProductID?

I'm certain this must be a fairly straight forward thing for a SQL
server guru so any help would be appreciated...

Thanks

Jim

Jul 23 '05 #2
This is called a relational division. Here is my usual "cut & paste"
about it. You might want to get a copy of SQL FOR SMARTIES for your
desk:

Relational division is one of the eight basic operations in Codd's
relational algebra. The idea is that a divisor table is used to
partition a dividend table and produce a quotient or results table.
The quotient table is made up of those values of one column for which a
second column had all of the values in the divisor.

This is easier to explain with an example. We have a table of pilots
and the planes they can fly (dividend); we have a table of planes in
the hangar (divisor); we want the names of the pilots who can fly every
plane (quotient) in the hangar. To get this result, we divide the
PilotSkills table by the planes in the hangar.

CREATE TABLE PilotSkills
(pilot CHAR(15) NOT NULL,
plane CHAR(15) NOT NULL,
PRIMARY KEY (pilot, plane));

PilotSkills
pilot plane
=========================
'Celko' 'Piper Cub'
'Higgins' 'B-52 Bomber'
'Higgins' 'F-14 Fighter'
'Higgins' 'Piper Cub'
'Jones' 'B-52 Bomber'
'Jones' 'F-14 Fighter'
'Smith' 'B-1 Bomber'
'Smith' 'B-52 Bomber'
'Smith' 'F-14 Fighter'
'Wilson' 'B-1 Bomber'
'Wilson' 'B-52 Bomber'
'Wilson' 'F-14 Fighter'
'Wilson' 'F-17 Fighter'

CREATE TABLE Hangar
(plane CHAR(15) NOT NULL PRIMARY KEY);

Hangar
plane
=============
'B-1 Bomber'
'B-52 Bomber'
'F-14 Fighter'

PilotSkills DIVIDED BY Hangar
pilot
=============================
'Smith'
'Wilson'

In this example, Smith and Wilson are the two pilots who can fly
everything in the hangar. Notice that Higgins and Celko know how to
fly a Piper Cub, but we don't have one right now. In Codd's original
definition of relational division, having more rows than are called for
is not a problem.

The important characteristic of a relational division is that the CROSS
JOIN (Cartesian product) of the divisor and the quotient produces a
valid subset of rows from the dividend. This is where the name comes
from, since the CROSS JOIN acts like a multiplication operator.

Division with a Remainder

There are two kinds of relational division. Division with a remainder
allows the dividend table to have more values than the divisor, which
was Codd's original definition. For example, if a pilot can fly more
planes than just those we have in the hangar, this is fine with us.
The query can be written in SQL-89 as

SELECT DISTINCT pilot
FROM PilotSkills AS PS1
WHERE NOT EXISTS
(SELECT *
FROM Hangar
WHERE NOT EXISTS
(SELECT *
FROM PilotSkills AS PS2
WHERE (PS1.pilot = PS2.pilot)
AND (PS2.plane = Hangar.plane)));

The quickest way to explain what is happening in this query is to
imagine an old World War II movie where a cocky pilot has just walked
into the hangar, looked over the fleet, and announced, "There ain't no
plane in this hangar that I can't fly!" We are finding the pilots for
whom there does not exist a plane in the hangar for which they have no
skills. The use of the NOT EXISTS() predicates is for speed. Most SQL
systems will look up a value in an index rather than scan the whole
table. The SELECT * clause lets the query optimizer choose the column
to use when looking for the index.

This query for relational division was made popular by Chris Date in
his textbooks, but it is not the only method nor always the fastest.
Another version of the division can be written so as to avoid three
levels of nesting. While it is not original with me, I have made it
popular in my books.

SELECT PS1.pilot
FROM PilotSkills AS PS1, Hangar AS H1
WHERE PS1.plane = H1.plane
GROUP BY PS1.pilot
HAVING COUNT(PS1.plane) = (SELECT COUNT(plane) FROM Hangar);

There is a serious difference in the two methods. Burn down the
hangar, so that the divisor is empty. Because of the NOT EXISTS()
predicates in Date's query, all pilots are returned from a division by
an empty set. Because of the COUNT() functions in my query, no pilots
are returned from a division by an empty set.

In the sixth edition of his book, INTRODUCTION TO DATABASE SYSTEMS
(Addison-Wesley; 1995 ;ISBN 0-201-82458-2), Chris Date defined another
operator (DIVIDEBY ... PER) which produces the same results as my
query, but with more complexity.

Exact Division

The second kind of relational division is exact relational division.
The dividend table must match exactly to the values of the divisor
without any extra values.

SELECT PS1.pilot
FROM PilotSkills AS PS1
LEFT OUTER JOIN
Hangar AS H1
ON PS1.plane = H1.plane
GROUP BY PS1.pilot
HAVING COUNT(PS1.plane) = (SELECT COUNT(plane) FROM Hangar)
AND COUNT(H1.plane) = (SELECT COUNT(plane) FROM Hangar);

This says that a pilot must have the same number of certificates as
there planes in the hangar and these certificates all match to a plane
in the hangar, not something else. The "something else" is shown by a
created NULL from the LEFT OUTER JOIN.

Please do not make the mistake of trying to reduce the HAVING clause
with a little algebra to:

HAVING COUNT(PS1.plane) = COUNT(H1.plane)

because it does not work; it will tell you that the hangar has (n)
planes in it and the pilot is certified for (n) planes, but not that
those two sets of planes are equal to each other.

Note on Performance

The nested EXISTS() predicates version of relational division was made
popular by Chris Date's textbooks, while the author is associated with
popularizing the COUNT(*) version of relational division. The Winter
1996 edition of DB2 ON-LINE MAGAZINE
(http://www.db2mag.com/96011ar:htm) had an article entitled "Powerful
SQL:Beyond the Basics" by Sheryl Larsen which gave the results of
testing both methods. Her conclusion for DB2 was that the nested
EXISTS() version is better when the quotient has less than 25% of the
dividend table's rows and the COUNT(*) version is better when the
quotient is more than 25% of the dividend table.

Jul 23 '05 #3
John

Thanks very much for your response. I think I may not have been quite
clear enough in my question. What I want to query the database for is:
'for every person who bought product 'X' give me a list of all the
other products they have bought?' This is very similar to the Amazon
site when you are about to purchase a book - underneath it says "people
who bought this book also bought these ones...."

Jim

Jul 23 '05 #4
>> 'for every person who bought product 'X' give me a list of all the
other products they have bought? <<

SELECT DISTINCT T1.user_id, T1.product_id
FROM Transactions AS T1
WHERE EXISTS
(SELECT *
FROM Transactions AS T2
WHERE T1.user_id = T2.user_id
AND product_id = 'X');

But I would do it this way to get more meaningful data in the
aggregate:

SELECT T1.product_id, COUNT(*) AS pairing_tally, COUNT(DISTINCT
user_id) AS buyer_tally
FROM Transactions AS T1
WHERE EXISTS
(SELECT *
FROM Transactions AS T2
WHERE T1.user_id = T2.user_id
AND product_id = 'X')
AND T1.product_id <> 'X'
GROUP BY T1.product_id
HAVING COUNT(*) > @my_threshold;

Jul 23 '05 #5

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

4
by: Surendra | last post by:
I have this query that I need to use in an Update statement to populate a field in the table by the value of Sq ---------------------------------------------------------------------------- Inline...
9
by: netpurpose | last post by:
I need to extract data from this table to find the lowest prices of each product as of today. The product will be listed/grouped by the name only, discarding the product code - I use...
2
by: Amanda | last post by:
From a guy in Microsoft newsgroups: | In *comp.databases.ibm-db2* there are always IBM guys | from the Toronto labs on line.Post with the | -for the love of god please help- | line...
9
by: Dom Boyce | last post by:
Hi First up, I am using MS Access 2002. I have a database which records analyst rating changes for a list of companies on a daily basis. Unfortunately, the database has been set up (by my...
8
by: Andrew McNab | last post by:
Hi folks, I have a problem with an MS Access SQL query which is being used in an Access Report, and am wondering if anyone can help. Basically, my query (shown below) gets some records from a...
1
by: Melissa Kay Beeline | last post by:
OK, here's the sitch : we have an access control system at work that registers ever entry/exit of every employee. I recently made some queries in Access so the ppl in HR could make reports (who...
9
by: hope | last post by:
Hi Access 97 I'm lost on this code please can you help ================================= Below is some simple code that will concatenate a single field's value from multiple records into a...
4
by: Alan Lane | last post by:
Hello world: I'm including both code and examples of query output. I appologize if that makes this message longer than it should be. Anyway, I need to change the query below into a pivot table...
47
by: Jo | last post by:
Hi there, I'm Jo and it's the first time I've posted here. I'm in process of creating a database at work and have come a little unstuck.....I'm a bit of a novice and wondered if anyone could...
0
by: DolphinDB | last post by:
Tired of spending countless mintues downsampling your data? Look no further! In this article, you’ll learn how to efficiently downsample 6.48 billion high-frequency records to 61 million...
0
by: ryjfgjl | last post by:
ExcelToDatabase: batch import excel into database automatically...
1
isladogs
by: isladogs | last post by:
The next Access Europe meeting will be on Wednesday 6 Mar 2024 starting at 18:00 UK time (6PM UTC) and finishing at about 19:15 (7.15PM). In this month's session, we are pleased to welcome back...
0
by: Vimpel783 | last post by:
Hello! Guys, I found this code on the Internet, but I need to modify it a little. It works well, the problem is this: Data is sent from only one cell, in this case B5, but it is necessary that data...
0
by: ArrayDB | last post by:
The error message I've encountered is; ERROR:root:Error generating model response: exception: access violation writing 0x0000000000005140, which seems to be indicative of an access violation...
1
by: CloudSolutions | last post by:
Introduction: For many beginners and individual users, requiring a credit card and email registration may pose a barrier when starting to use cloud servers. However, some cloud server providers now...
1
by: Shællîpôpï 09 | last post by:
If u are using a keypad phone, how do u turn on JavaScript, to access features like WhatsApp, Facebook, Instagram....
0
by: Faith0G | last post by:
I am starting a new it consulting business and it's been a while since I setup a new website. Is wordpress still the best web based software for hosting a 5 page website? The webpages will be...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 3 Apr 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome former...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.