I have a query that works very well. Let me start with it:
Edit: The SQL has been updated. I get 0 in every row.
SELECT i.item, i.user_id, u.username,
(COALESCE(r.ratetotal, 0)) AS total,
(COALESCE(c.commtotal, 0)) AS comments,
(COALESCE(r.rateav, '50%')) AS rate,
(COALESCE(x.wasRated, '0')) AS wasRated
FROM items AS i
LEFT JOIN master_cat AS c
ON (c.cat_id = i.cat_id)
LEFT JOIN users AS u
ON u.user_id = i.user_id
LEFT JOIN
(SELECT item_id,
COUNT(item_id) AS ratetotal,
AVG(rating) AS rateav
FROM ratings GROUP BY item_id) AS r
ON r.item_id = i.item_id
LEFT JOIN
(SELECT item_id,
COUNT(item_id) AS commtotal
FROM reviews GROUP BY item_id) AS c
ON c.item_id = i.item_id
LEFT JOIN
(SELECT xu.user_id, ra.item_id, '1' AS wasRated
FROM users AS xu
LEFT JOIN ratings AS ra
ON ra.user_id = xu.user_id
WHERE xu.user_id = '1') AS x
ON x.user_id = u.user_id
AND x.item_id = r.item_id
WHERE c.category = 'Movies'
ORDER by i.item ASC;
I need to add one more function to it, where you see AS x
Basically, there are three tables here that are important. items, reviews and ratings. In the top portion you see there are subqueries that are taking statistics such as averages and totals for each item.
I need a final query that is tied to user_id, item_id and rate_id (in ratings). In the end result, where it list each item and the stats with it, I want one more column, a simple true or false if logged in user has rated it. So I need something like this:
SELECT ???
FROM ratings AS r
WHERE r.user_id = '{$user_id}'
(user_id of logged in user is passed in from PHP.`)
How can I make a subquery that gives me that last bit of info, but puts it in each row of items in the parent query?
Add this to the parent query.
, coalesce(x.WasRated, 'false') as WasRated
Your x subquery is:
(select users.user_id
, ratings.item_id
, 'true' WasRated
from users join ratings on user.user_id = ratings.user_id
where users.user_id = the one for the logged in user
) x on x.user_id = users.user_id
and x.item_id = ratings.item_id
or something like it.
Related
I'm trying to get these values from two tables, joining on member id. In the table with the points values, there are running total rows for each sale by each member. I need the member's point total associated with their row in the members table, sorted by value descending. This is my current query, it returns unique values unless a member has two identical entries.
SELECT m.id
, m.fname
, m.lname
, p.points_total
FROM
( SELECT s.member_id
, MAX(s.points_total) points_total
FROM sale_sale s
GROUP
BY s.member_id
) p
JOIN sale_sale x
ON x.member_id = p.member_id
AND x.points_total = p.points_total
JOIN member_member m
ON m.id = p.member_id
WHERE x.site_id = 1
AND m.fname != "Sales"
ORDER
BY p.points_total DESC;
A simple JOIN and GROUP BY would likely do what you're asking for:
SELECT
m.id,
m.fname,
m.lname,
COALESCE(MAX(s.points_total), 0) AS points_total
FROM member_member AS m
LEFT JOIN sale_sale AS s
ON m.id = s.member_id
AND s.site_id = 1
WHERE m.fname != "Sales"
GROUP BY m.id
ORDER BY points_total DESC;
EDIT: Made it a LEFT JOIN with COALESCE(points_total, 0) to allow for members who have no sales totals to show in the results. If you don't want this, you could change LEFT JOIN to INNER JOIN and eliminate the COALESCE function.
I want to show maximum of guarantee which specific user has. For example user has bought 3 items which have 1,2,5 years guarantee. So I want to show 5 years guarantee and name of this product.
I did subquery in case few products have this same guarantee.
SELECT t.`id-user`, name, guarantee FROM transactions t
JOIN user u ON `t`.`id-user` = `u`.`id-user`
JOIN products p ON `p`.`id-product = `t`.`id-product`
WHERE guarantee = (SELECT MAX(p2.guarantee)
FROM products p2
WHERE `p2`.`id-product` = `p`.`id-product`)
This query shows all products and their guarantees.
I think the simplest method is the substring_index()/group_concat() method for getting values associated with a maximum/minimum:
SELECT t.iduser, u.name,
MAX(p.guarantee) as guarantee,
SUBSTRING_INDEX(GROUP_CONCAT(p.name ORDER BY p.guarantee DESC), ',', 1)
FROM transactions t JOIN
user u
ON t.iduser = u.iduser JOIN
products p
ON p.idproduct = t.idproduct
GROUP BY t.iduser, u.name;
You can use your method too, but the correlated subquery is tricky:
SELECT t.iduser, u.name, p.guarantee, p.name
FROM transactions t JOIN
user u
ON t.iduser = u.iduser JOIN
products p
ON p.idproduct = t.idproduct
WHERE p.guarantee = (SELECT MAX(p2.guarantee)
FROM transactions t2 JOIN
products p2
ON p2.idproduct = t2.idproduct
WHERE t2.iduser = u.iduser
);
I think it work.
select [User].Name as [UserName],
Product.MaxGuarantee,
Product.Name as Product_Name
from [Users] [User]
left join Transactions [Transaction]
on [Transaction].[User] = [User].ID
cross apply(
select max(guarantee) MaxGuarantee, Name
from Products
where ID = [Transaction].Product
) Product
where [User].ID = ''
I have three tables, libraryitems, copies and loans.
A libraryitem hasMany copies, and a copy hasMany loans.
I'm trying to get the latest loan entry for a copy only; The query below returns all loans for a given copy.
SELECT
libraryitems.title,
copies.id,
copies.qruuid,
loans.id AS loanid,
loans.status,
loans.byname,
loans.byemail,
loans.createdAt
FROM copies
INNER JOIN libraryitems ON copies.libraryitemid = libraryitems.id AND libraryitems.deletedAt IS NULL
LEFT OUTER JOIN loans ON copies.id = loans.copyid
WHERE copies.libraryitemid = 1
ORDER BY copies.id ASC, loans.createdAt DESC
I know there needs to be a sub select of some description in here, but struggling to get the correct syntax. How do I only return the latest, i.e MAX(loans.createdAt) row for each distinct copy? Just using group by copies.id returns the earliest, rather than latest entry.
Image example below:
in the subquery , getting maximum created time for a loan i.e. latest entry and joining back with loans to get other details.
SELECT
T.title,
T.id,
T.qruuid,
loans.id AS loanid,
loans.status,
loans.byname,
loans.byemail,
loans.createdAt
FROM
(
SELECT C.id, C.qruuid, L.title, MAX(LN.createdAt) as maxCreatedTime
FROM Copies C
INNER JOIN libraryitems L ON C.libraryitemid = L.id
AND L.deletedAt IS NULL
LEFT OUTER JOIN loans LN ON C.id = LN.copyid
GROUP BY C.id, C.qruuid, L.title) T
JOIN loans ON T.id = loans.copyid
AND T.maxCreatedTime = loans.createdAt
A self left join on loans table will give you latest loan of a copy, you may join the query to the other tables to fetch the desired output.
select * from loans A
left outer join loans B
on A.copyid = B.copyid and A.createdAt < B.createdAt
where B.createdAt is null;
This is your query with one simple modification -- table aliases to make it clearer.
SELECT li.title, c.id, c.qruuid,
l.id AS loanid, l.status, l.byname, l.byemail, l.createdAt
FROM copies c INNER JOIN
libraryitems li
ON c.libraryitemid = li.id AND
li.deletedAt IS NULL LEFT JOIN
loans l
ON c.id = l.copyid
WHERE c.libraryitemid = 1
ORDER BY c.id ASC, l.createdAt DESC ;
With this as a beginning let's think about what you need. You want the load with the latest createdAt date for each c.id. You can get this information with a subquery:
select l.copyid, max(createdAt)
from loans
group by l.copyId
Now, you just need to join this information back in:
SELECT li.title, c.id, c.qruuid,
l.id AS loanid, l.status, l.byname, l.byemail, l.createdAt
FROM copies c INNER JOIN
libraryitems li
ON c.libraryitemid = li.id AND
li.deletedAt IS NULL LEFT JOIN
loans l
ON c.id = l.copyid LEFT JOIN
(SELECT l.copyid, max(l.createdAt) as maxca
FROM loans
GROUP BY l.copyid
) lmax
ON l.copyId = lmax.copyId and l.createdAt = lmax.maxca
WHERE c.libraryitemid = 1
ORDER BY c.id ASC, l.createdAt DESC ;
This should give you the most recent record. And, the use of left join should keep all copies, even those that have never been leant.
I have this query (below) while it does work I am wondering if it is the best as it will be going against thousands of records. I will try to explain the best I can.
SELECT items.*,
p.file AS item_pic,
i_f.id AS favorite_id,
COALESCE(f.favorite_count, 0) AS favorite_count,
COALESCE(b.num_buys, 0) AS num_buys,
COALESCE(c.comment_count, 0) AS comment_count
FROM items i
INNER JOIN (SELECT file,
item_id
FROM item_pics
ORDER BY item_pics.id ASC) AS p
ON p.item_id = i.id
LEFT JOIN (SELECT COUNT(*) AS favorite_count,
item_id
FROM item_favorites
GROUP BY item_id) AS f
ON f.item_id = i.id
LEFT JOIN (SELECT COUNT(*) AS num_buys,
item_id
FROM purchases
GROUP BY item_id) AS b
ON b.item_id = i.id
LEFT JOIN (SELECT COUNT(*) AS comment_count,
item_id
FROM comments
GROUP BY item_id) AS c
ON c.item_id = i.id
LEFT JOIN item_favorites AS i_f
ON i.id = i_f.item_id
AND i_f.userid = '14'
GROUP BY i.id
LIMIT 0, 20
So we are selecting the items in the database. The first join is for a picture (Items have multiple pictures but I only want one).
The next join is for favorite count. Each time a user favorites something it adds it to the table favorites with some info, so I am just trying to get the total number of favorites for that item.
Next up is the number of purchases for this item. Pretty much the same as favorites.
After that it is for comments. Again this is just like the purchases and favorites count.
The last join is to see if the logged in user (id 14) has favorited this item if not I use COALESCE to return 0.
Like I said this all works correctly but it does take a few seconds to load on a table of about 6700 items and about 180K rows in the purchases table for only loading 20 at a time (I do a scrolling/load similar to Facebook/Twitter). Indexes have been properly setup on all tables. Once this is complete/correct I would like to know how to limit results for purchases in the last seven days and order by number of purchases (num_buys).
EDIT: Results from EXPLAIN
I suppose you want the first picture (lowest id), and pictures are required, where as everything else is optional.
I guess you're doing subqueries because you think joining on uncorrelated subqueries (hitting the joined tables just once) will be faster than correlated subqueries or a plain JOIN. However, you end up having to lookup the records twice, and the second lookup (for the actual join) doesn't get to use an index because derived (temporary tables) don't have indexes.
Try normal JOINs:
SELECT items.*,
p.file AS item_pic,
COALESCE(i_f.id, 0) AS favorite_id,
COUNT(f.item_id) AS favorite_count,
COUNT(b.item_id) AS num_buys,
COUNT(c.item_id) AS comment_count
FROM items i
STRAIGHT_JOIN item_pics p
ON p.item_id = i.id
LEFT JOIN item_pics p2
ON p2.item_id = i.id
AND p2.id < p1.id
LEFT JOIN item_favorites f
ON f.item_id = i.id
LEFT JOIN purchases b
ON b.item_id = i.id
LEFT JOIN comments c
ON c.item_id = i.id
LEFT JOIN item_favorites AS i_f
ON i_f.item_id = i.id
AND i_f.userid = '14'
WHERE p2.id IS NULL
GROUP BY i.id
LIMIT 20
The double join on pictures is an anti-join WHERE p2.id IS NULL, to retrieve the picture with the lowest id.
I have a fairly complicated SQL statement I am working on. Here is where I am at:
SELECT c.category
FROM master_cat as c
LEFT JOIN
(
SELECT cat_id, user_id COUNT(cat_id) favoriteCat
FROM ratings
GROUP BY user_id
) a ON a.cat_id= c.cat_id
LEFT JOIN users AS u
ON u.user_id AND a.user_id
WHERE u.username = '{$user}' LIMIT 1
This statement is incomplete. I am missing a middle table here. cat_id is not actually in ratings. But items_id is from a table called items and cat_id is also in that table as well.
So what I am trying to do is this:
SELECT rating FROM ??? GROUP BY cat_id where u.user=$user
The only thing I can think of doing maybe is another LEFT join with items inside favoriteCat but I am not sure if that is allowed.
I was overthinking this, here is my final solution:
SELECT c.category, count(r.rating) AS totalCount
FROM ratings as r
LEFT JOIN items AS i
ON i.items_id = r.item_id
LEFT JOIN users AS u
ON u.user_id = r.user_id
LEFT JOIN master_cat AS c
ON c.cat_id = i.cat_id
WHERE r.user_id = '{$user_id}'
GROUP BY c.category
ORDER BY totalCount DESC