How to query for suggested books like amazon does? - mysql

I'm working on developing a web portal for a bookstore, and I want to have the ability to suggest books to a user.
I want something similar to amazon.com, when a user orders book A, the system should provide a list of other suggested books. Book B is suggested if there exists a user Bob that bought both A and B. Additionally, I want my system to return the suggested books sorted on decreasing sales count, and only count sales to users that have bought both books (like Bob).
Here are the important tables:
Book(ISBN, title, publicationYear, etc..)
Orders(orderID, loginName, date)
BooksOrdered(orderID, ISBN, count)
This query is more complex than anything I've previously tried.
Current thoughts:
First find all the users that have ordered the same book (ISBN)
Join all three tables on Book.ISBN = BooksOrdered.ISBN AND Orders.orderID = BooksOrdered.ISBN
WHERE Book.ISBN = bookInQuestionISBN
GROUP BY Orders.loginName
Project out loginName
So something like:
SELECT Orders.loginName as otherBuyerLoginName
FROM Book, Orders, BooksOrdered,
WHERE Book.ISBN = bookInQuestionISBN AND Orders.orderID = BooksOrdered.ISBN
GROUP BY Orders.loginName
Then I could grab all the books these loginNames have ordered, group them by loginName, sum count and ORDER BY DESC SUM(BooksOrdered.count).
However, I'm thinking that the first result will most likely be the book in question. I don't want to suggest the same book the user has just bought.
What do you suggest? Maybe I should start over from scratch?
EDIT:
Here is some data:
BooksOrdered contains:
orderID ISBN count
3 FakeISBN 3
7 FakeISBN 3
8 FakeISBN 100
11 FakeISBN2 40
7 FakeISBN2 4
10 FakeISBN2 20
10 FakeISBN3 34
11 TesterISBN 3
9 TesterISBN 1
Orders contains:
orderID loginName date
2 Tester 2012-03-15 19:43:27
3 Tester 2012-03-16 15:56:55
6 Tester2 2012-03-16 17:28:02
7 Tester 2012-03-16 17:31:21
8 ni3hao3 2012-03-16 23:18:15
9 ni3hao3 2012-03-17 13:12:38
10 ni3hao3 2012-03-17 13:13:55
11 Bobby 2012-03-17 13:28:14
Alright, now I want to know the top suggestions for the book with ISBN = "TesterISBN"
Two people have ordered "TesterISBN": ni3hao3 and Bobby
ni3hao3's total sales history:
1 copy of "TesterISBN"
100 copies of "FakeISBN"
20 copies of "FakeISBN2"
34 copies of "FakeISBN3"
Bobby's total sales history:
3 copies of "TesterISBN"
40 copies of "FakeISBN2"
So the totals of sales for purchasers of "TesterISBN" are as follows:
4 copies of "TesterISBN"
100 copies of "FakeISBN"
60 copies of "FakeISBN2"
34 copies of "FakeISBN3"
So I'd like the results to return:
FakeISBN
FakeISBN2
FakeISBN3
In that order.
EDIT:
I believe I've figured it out:
SELECT Bo.ISBN, B.title, SUM(Bo.count)
FROM BooksOrdered Bo, Orders O, Book B
WHERE Bo.orderID = O.orderID AND Bo.ISBN = B.ISBN
AND Bo.ISBN != 'TesterISBN'
AND O.loginName IN ( SELECT DISTINCT(Orders.loginName) as otherBuyerLoginName
FROM Orders, BooksOrdered
WHERE BooksOrdered.ISBN = 'TesterISBN'
AND Orders.orderID = BooksOrdered.orderID)
GROUP BY Bo.ISBN
ORDER BY SUM(Bo.count) DESC

I've written this on SQL Server, but converting to MySQL syntax should be trivial.
This query returns recommendations based on books in the orderID in question (#currentOrderID):
select b.ISBN, b.title, sum(bo.count) as ranking
from Book b inner join
BooksOrdered bo on b.ISBN = bo.ISBN inner join
( select distinct orderID
from BooksOrdered bo
where bo.ISBN in ( select ISBN
from BooksOrdered bo1
where bo1.orderID = #currentOrderID )
) o on bo.orderID = o.orderID
where b.ISBN not in ( select ISBN
from BooksOrdered bo1
where bo1.orderID = #currentOrderID )
group by b.ISBN, b.title
order by sum(bo.count) desc
This one returns recommendations based on the entire order history of the login that made the current order:
select b.ISBN, b.title, sum(bo.count) as ranking
from Book b inner join
BooksOrdered bo on b.ISBN = bo.ISBN inner join
( select bo.orderID
from BooksOrdered bo
where bo.ISBN in ( select ISBN
from BooksOrdered bo1
where bo1.orderID = #currentOrderID )
) o on bo.orderID = o.orderID
where b.ISBN not in ( select ISBN
from Orders o inner join
BooksOrdered bo on o.orderID = bo.orderID
where o.loginName = ( select loginName
from Orders
where orderID = #currentOrderID ) )
group by b.ISBN, b.title
order by sum(bo.count) desc
Hope these give you what you're looking for!

It seems to me that you can approach this in two steps:
Find which users bought the book in question and get the ids of all their orders
Group all other books in those orders by ISBN and count number of occurrences (i.e. number of orders they have been bought together with the book in question)
It would translate to something like this:
SELECT bo.ISBN, COUNT(*) AS timesBoughtTogether
FROM (SELECT DISTINCT(o.orderID) FROM Orders o
LEFT JOIN BooksOrdered bo ON o.orderID = bo.orderID
WHERE bo.ISBN = 'ISBN to provide suggestions for') relevantOrders
LEFT JOIN BooksOrdered bo ON relevantOrders.orderID = bo.orderID
WHERE bo.ISBN != 'ISBN to provide suggestions for'
GROYP BY bo.ISBN
ORDER BY timesBoughtTogether DESC
I didn't actually run this, so I hope the syntax isn't off.

SELECT Bo.ISBN, B.title, SUM(Bo.count)
FROM BooksOrdered Bo, Orders O, Book B
WHERE Bo.orderID = O.orderID AND Bo.ISBN = B.ISBN
AND Bo.ISBN != 'TesterISBN'
AND O.loginName IN ( SELECT DISTINCT(Orders.loginName) as otherBuyerLoginName
FROM Orders, BooksOrdered
WHERE BooksOrdered.ISBN = 'TesterISBN'
AND Orders.orderID = BooksOrdered.orderID)
GROUP BY Bo.ISBN
ORDER BY SUM(Bo.count) DESC
This seems to do the trick

Related

Query that outputs the totals of two associated tables not returning totals correctly, why?

Hello I have the following db schema, 3 tables:
connected_accounts (fields: id, user_id, account_name)
contacts (fields: id, user_id, connected_account_id, type)
user_recommendations (fields: id, user_id, contact_id)
I'm trying to output a query that shows, per each connect_account record in the DB, totals of # of contacts and user_recommendations associated. Here is what I have so far:
SELECT
ca.id,
ca.user_id,
ca.account_name,
COUNT(c.id) AS Total
FROM connected_accounts ca
LEFT JOIN contacts c
ON ca.id = c.connected_account_id
AND c.contact_type = 'email'
WHERE ca.resource_type = 'calendar.readonly'
ORDER BY ca.id DESC
LIMIT 8
This is not working correctly... The desired output would look like this:
RESULTS
id | user_id | account_name | Total Contacts | Total User Recommendations
I haven't joined the UserRecommendations yet as my contacts join isn't working as desired.
What am I doing wrong in my query? thank you
UPDATED:
SELECT
ca.id,
ca.user_id,
ca.account_name,
COUNT(c.id) AS 'Total Contacts',
COUNT(pr.id) AS 'Total User Rec.'
FROM connected_accounts ca
LEFT JOIN contacts c
ON ca.id = c.connected_account_id
AND c.contact_type = 'email'
LEFT JOIN user_recommendations pr
ON ca.id = pr.connected_account_id
WHERE ca.resource_type = 'calendar.readonly'
GROUP BY ca.id, ca.user_id, ca.account_name
ORDER BY ca.id DESC
LIMIT 8
UPDATED ABOVE.. The above updated query is returning the same # for Total Contacts & Total User Rec -- the number, which is the same for both is the right number for total contacts.. The Total User Rec is not being returned... why?
You have missed the GROUP BY. Try this. Hope it helps.
Also, LEFT JOIN will have all the records from the connected_accounts table, is this what you want?
SELECT
ca.id,
ca.user_id,
ca.account_name,
COUNT(c.id) AS Total
FROM connected_accounts ca
LEFT JOIN contacts c
ON ca.id = c.connected_account_id
AND c.contact_type = 'email'
WHERE ca.resource_type = 'calendar.readonly'
GROUP BY ca.id, ca.user_id, ca.account_name
ORDER BY ca.id DESC
LIMIT 8

Count two different rows in mysql query

I have organizations. Each organization can have members and projects.
I want to get list of organizations with number of members and projects.
For example,
Organization | Members | Projects | Action
------------------------------------------
Org 1 | 5 | 6 | Delete - Edit
Org 2 | 2 | 9 | Delete - Edit
I am using this query,
SELECT COUNT(m.id) as members, COUNT(p.id) as projects,
o.status,o.organization_name,o.logo, o.id as id
from tbl_organizations o
LEFT JOIN tbl_organization_members m ON (o.id = m.organization_id)
LEFT JOIN tbl_projects p ON (o.id = p.organization_id)
WHERE o.status= 'active' AND o.created_by= 1
But the output of number of projects is equal to number of members.
How can I make the sample above using query?
Try this way:
SELECT o.id as id, o.organization_name, cnt_ as members, cnt_p as projects
from tbl_organizations o
LEFT JOIN (
SELECT organization_id, COUNT(id) cnt_m
FROM tbl_organization_members
GROUP BY organization_id
) m ON (o.id = m.organization_id)
LEFT JOIN (
SELECT organization_id, COUNT(id) cnt_p
FROM tbl_projects
GROUP BY organization_id
) p ON (o.id = p.organization_id)
WHERE o.status= 'active' AND o.created_by= 1
This way you JOIN to an already aggregated version of member/project tables, so as to get the count of members/projects per organization_id.
Group by the organisation columns and count distinct IDs
SELECT o.status,o.organization_name, o.logo, o.id as id,
COUNT(distinct m.id) as members, COUNT(distinct p.id) as projects,
from tbl_organizations o
LEFT JOIN tbl_organization_members m ON (o.id = m.organization_id)
LEFT JOIN tbl_projects p ON (o.id = p.organization_id)
WHERE o.status= 'active'
AND o.created_by= 1
GROUP BY o.status, o.organization_name, o.logo, o.id
You can co-related subquery:
SELECT
o.id as Organization,
(SELECT COUNT(*) FROM tbl_organization_members WHERE organization_id = o.id) as members,
(SELECT COUNT(*) FROM tbl_projects WHERE organization_id = o.id) as projects
FROM
tbl_organizations o
WHERE
o.status= 'active' AND o.created_by = 1

Write query without join and using subquery

I want to rewrite the join query with sub-query like IN(), ANY() operator.
Data set:
*categories*
categoryID name
5 ROD
7 CEMENT
*products*
productID categoryID name
7 5 BSRM 10mm
9 5 KSRM 5mm
10 5 Julius
11 7
12 5 BSRM 25mm
*sale_products*
saleID productID
118 9
119 9
120 9
121 9
122 12
123 12
124 12
This is my query to read saleID, product name and category name for which already have sold.
My query is:
SELECT sale_products.saleID, products.name, categories.name
FROM categories
INNER JOIN (products, sale_products)
ON categories.categoryID = products.categoryID
AND products.productID = sale_products.productID
Now I want to the result set without join and with the sub-query methodology.
I try in this way:
SELECT categories.name
FROM categories
WHERE categories.categoryID IN
(SELECT products.categoryID
FROM products
WHERE products.productID in
(SELECT sale_products.productID FROM sale_products))
this query only give me the category name but I need also saleID, product name.
Your original query looks queer. Why do you cross join products and sale_products?
It should better be:
select sp.saleid, p.name as product, c.name as category
from sale_products sp
join products p on p.productid = sp.productid
join categories c on c.categoryid = p.categoryid;
This is the straight-forward way to show the data. You can use subqueries instead, but I see no sense in it:
select
sp.saleid
(
select p.name
from products p
where p.productid = sp.productid
) as product,
(
select c.name
from categories c
where c.categoryid = p.categoryid
) as category
from sale_products sp;
Here is yet another query with subqueries. Again without any benefit over the simple query using direct joins on the tables.
select sp.saleid, p.name as product, c.name as category
from sale_products sp
join (select productid, name from products) p on p.productid = sp.productid
join (select categoryid, name from categories) c on c.categoryid = p.categoryid;

Intersection in mysql for multiple values

The intersect keyword is not available in mysql. I want to know how to implement the following in mysql db. My tables are:
customer(cid,city,name,state)
orders(cid,oid,date)
product(pid,price,productname)
lineitem(lid,pid,oid,totalquantity,totalprice)
I want the products bought by all the customers of a particular city 'X'. i.e. every customer in city 'x' should have bought the product. I managed to select the oid's and the pid's of customers living in that particular city. Now I should select the pid's which is present in all the oid's.
Example.
Oid Pid
2400 1
2400 2
2401 3
2401 1
2402 1
2403 1
2403 3
The answer from the above input should be 1 because it is present in all oid's. The query which I used to get the oid's and pid's:
select t.oid,l.pid
from lineitem l
join (select o.oid,c1.cid
from orders o
join (select c.cid
from customer c
where c.city='X') c1
where o.cid=c1.cid) t on l.oid=t.oid
Now I need to intersect all the oid's and get the result.The query should not be dependent on data.
Try:
select pid, count(*)
from (select t.oid, l.pid
from lineitem l
join (select o.oid, c1.cid
from orders o
join (select c.cid from customer c where c.city = 'X') c1
where o.cid = c1.cid) t
on l.oid = t.oid) x
group by pid
having count(*) = (select count(*)
from (select distinct oid
from lineitem l
join (select o.oid, c1.cid
from orders o
join (select c.cid
from customer c
where c.city = 'X') c1
where o.cid = c1.cid) t
on l.oid = t.oid) y) z
I think you can achieve what you want by using IN

MySQL query - Join Query

Not sure how to write this join for 2 tables. Here is some sample data to illustrate:
orders
-----------------------
| order_id | customer |
-----------------------
ABC12345 1
ABC12346 4
ABC12347 3
ABC12348 2
ABC12349 2
ABC12350 3
customers
-----------------------------------
| id | name | email |
-----------------------------------
1 James james#gmail.com
2 Alice alice#hotmail.com
3 Jimbo james#gmail.com
4 Jim james#gmail.com
5 Lucy lucy#yahoo.com
I have an order_id, which I already know. Let's use the first one in the table: ABC12345. As you can see, the customer ID is 1, so that order was placed by James. Now sometimes James has ordered again using different names but we know it's him because of his email address.
So how do I retrieve all of James' orders based on his email address of james#gmail.com, if I know one of his order numbers (ABC12345)?
Edit: Not sure I stressed this enough... James has ordered 3 times, using the same email address but names of James, Jim and Jimbo. I need all of his orders using james#gmail.com as the email address.
SELECT o2.order_id
FROM orders o1
INNER JOIN customers c1
ON o1.customer = c1.id -- get the customer for the first order
INNER JOIN customers c2
ON c1.email = c2.email -- find all customers with same email
INNER JOIN orders o2
ON c2.id = o2.customer -- find all orders for those customers
WHERE o1.order_id = 'ABC12345'
You can use this:
SELECT order_id
FROM orders
WHERE customer IN (
SELECT id
FROM customers
WHERE email = (SELECT c.email FROM customers c JOIN orders o ON c.id = o.customer WHERE o.order_id = 'ABC12345')
)
You need to first quantify the person of the order to the customer table... Then, get all customer IDs by that same email... THEN get the orders that qualify those customer IDs.
select o2.*
from orders o2
JOIN ( select c2.ID
from customers c2
join ( select c1.email
from orders o
join customers c1
on o.Customer = c1.ID
where
o.order_id = 'ABC12345' ) FoundTheEmail
on c2.email = FoundTheEmail.email
) as SameCustomerEMail
on o2.Customer = SameCustomerEMail.ID
SELECT order_id
FROM orders
WHERE customer IN (SELECT customer
FROM orders
WHERE order_id = 'ABC12345')
try this since james has only one order
SELECT orders.order_id, customers.name, customers.email
FROM orders
INNER JOIN customers ON customers.id = orders.customer
WHERE orders.orer_id = 'ABC12345'
The order ID is a UNIQUE number, then you will not have two orders with the same ID, why care about the customer name?
"Every order will have a Customer's ID."