Adding in max id for a SELECT query - mysql

I am trying to configure to only select the max id with a limit of one to my current query.
I have a link to a rextester below which shows the an actual example of what I am trying to figure out. Sorry for the error in it though...not sure what I did wrong when I created the example.
Anyways, you will see:
INSERT INTO `profile_img`
VALUES
(1,24,'beach'),
(2,55,'sandals'),
(3,56,'hotel'),
(4,55,'ocean'),
(5,55,'corona'),
(6,55,'tacos')
;
Which are some values in my profile_img table. Right now my query selects all of the profile images for the this ON p.user_id = f.friend_one. Resulting in duplicate output. It looks like this:
I just want it to output
55
56
and the appropiate max id img with it. Any one know how I can only SELECT the max id for for the img column of the profile_img table for the user_id that matches friend_one? ON p.user_id = f.friend_one.
However, sometimes the user does not have a profile_img. Previously I have used this in a query to get the default img for those that do not have one. (case when p.img <> '' then p.img else 'profile_images/default.jpg' end) as img
SELECT f.*
, p.*
FROM friends f
LEFT JOIN profile_img p
ON p.user_id = f.friend_one
WHERE f.friend_two = ?
AND f.status = ?
http://rextester.com/RLXS27142

If I understand correctly, you just want the profile image with the largest id:
SELECT f.*, p.*,
COALESCE(p.img, 'profile_images/default.jpg') as profile_img
FROM friends f LEFT JOIN
profile_img p
ON p.user_id = f.friend_one AND
p.id = (select max(p2.id) from profile_img p2 where p2.user_id = p.user_id)
WHERE f.friend_two = ? AND f.status = ?;

Related

Display a record although the condition return zero record

I have MySql query like this:
SELECT name, id_number, hr.id, file, created_at, updated_at
From users u
LEFT JOIN homework_targets ht on u.class_id = ht.class_id
LEFT JOIN homework_replies hr on u.id = hr.user_id
WHERE u.role = 'student' AND hr.homework_id = 8
This query work just fine, but not like what I expected. I want to display all the student (users) record is displayed although they don't have a record on homework_replies that match the homework_targets is it possible? if it possible how can I accomplish it? thanks.
Move the hr.homework_id condition from WHERE to ON to get true LEFT JOIN result:
SELECT name, id_number, hr.id, file, created_at, updated_at
From users u
LEFT JOIN homework_targets ht on u.class_id = ht.class_id
LEFT JOIN homework_replies hr on u.id = hr.user_id AND hr.homework_id = 8
WHERE u.role = 'student'
(If you have it in the WHERE clause, you'll get regular INNER JOIN result.)

How to Make This SQL Query More Efficient?

I'm not sure how to make the following SQL query more efficient. Right now, the query is taking 8 - 12 seconds on a pretty fast server, but that's not close to fast enough for a Website when users are trying to load a page with this code on it. It's looking through tables with many rows, for instance the "Post" table has 717,873 rows. Basically, the query lists all Posts related to what the user is following (newest to oldest).
Is there a way to make it faster by only getting the last 20 results total based on PostTimeOrder?
Any help would be much appreciated or insight on anything that can be done to improve this situation. Thank you.
Here's the full SQL query (lots of nesting):
SELECT DISTINCT p.Id, UNIX_TIMESTAMP(p.PostCreationTime) AS PostCreationTime, p.Content AS Content, p.Bu AS Bu, p.Se AS Se, UNIX_TIMESTAMP(p.PostCreationTime) AS PostTimeOrder
FROM Post p
WHERE (p.Id IN (SELECT pc.PostId
FROM PostCreator pc
WHERE (pc.UserId IN (SELECT uf.FollowedId
FROM UserFollowing uf
WHERE uf.FollowingId = '100')
OR pc.UserId = '100')
))
OR (p.Id IN (SELECT pum.PostId
FROM PostUserMentions pum
WHERE (pum.UserId IN (SELECT uf.FollowedId
FROM UserFollowing uf
WHERE uf.FollowingId = '100')
OR pum.UserId = '100')
))
OR (p.Id IN (SELECT ssp.PostId
FROM SStreamPost ssp
WHERE (ssp.SStreamId IN (SELECT ssf.SStreamId
FROM SStreamFollowing ssf
WHERE ssf.UserId = '100'))
))
OR (p.Id IN (SELECT psm.PostId
FROM PostSMentions psm
WHERE (psm.StockId IN (SELECT sf.StockId
FROM StockFollowing sf
WHERE sf.UserId = '100' ))
))
UNION ALL
SELECT DISTINCT p.Id AS Id, UNIX_TIMESTAMP(p.PostCreationTime) AS PostCreationTime, p.Content AS Content, p.Bu AS Bu, p.Se AS Se, UNIX_TIMESTAMP(upe.PostEchoTime) AS PostTimeOrder
FROM Post p
INNER JOIN UserPostE upe
on p.Id = upe.PostId
INNER JOIN UserFollowing uf
on (upe.UserId = uf.FollowedId AND (uf.FollowingId = '100' OR upe.UserId = '100'))
ORDER BY PostTimeOrder DESC;
Changing your p.ID in (...) predicates to existence predicates with correlated subqueries may help. Also since both halves of your union all query are pulling from the Post table and possibly returning nearly identical records you might be able to combine the two into one query by left outer joining to UserPostE and adding upe.PostID is not null as an OR condition in the WHERE clause. UserFollowing will still inner join to UPE. If you want the same Post record twice once with upe.PostEchoTime and once with p.PostCreationTime as the PostTimeOrder you'll need keep the UNION ALL
SELECT
DISTINCT -- <<=- May not be needed
p.Id
, UNIX_TIMESTAMP(p.PostCreationTime) AS PostCreationTime
, p.Content AS Content
, p.Bu AS Bu
, p.Se AS Se
, UNIX_TIMESTAMP(coalesce( upe.PostEchoTime
, p.PostCreationTime)) AS PostTimeOrder
FROM Post p
LEFT JOIN UserPostE upe
INNER JOIN UserFollowing uf
on (upe.UserId = uf.FollowedId AND
(uf.FollowingId = '100' OR
upe.UserId = '100'))
on p.Id = upe.PostId
WHERE upe.PostID is not null
or exists (SELECT 1
FROM PostCreator pc
WHERE pc.PostId = p.ID
and pc.UserId = '100'
or exists (SELECT 1
FROM UserFollowing uf
WHERE uf.FollowedId = pc.UserID
and uf.FollowingId = '100')
)
OR exists (SELECT 1
FROM PostUserMentions pum
WHERE pum.PostId = p.ID
and pum.UserId = '100'
or exists (SELECT 1
FROM UserFollowing uf
WHERE uf.FollowedId = pum.UserId
and uf.FollowingId = '100')
)
OR exists (SELECT 1
FROM SStreamPost ssp
WHERE ssp.PostId = p.ID
and exists (SELECT 1
FROM SStreamFollowing ssf
WHERE ssf.SStreamId = ssp.SStreamId
and ssf.UserId = '100')
)
OR exists (SELECT 1
FROM PostSMentions psm
WHERE psm.PostId = p.ID
and exists (SELECT
FROM StockFollowing sf
WHERE sf.StockId = psm.StockId
and sf.UserId = '100' )
)
ORDER BY PostTimeOrder DESC
The from section could alternatively be rewritten to also use an existence clause with a correlated sub query:
FROM Post p
LEFT JOIN UserPostE upe
on p.Id = upe.PostId
and ( upe.UserId = '100'
or exists (select 1
from UserFollowing uf
where uf.FollwedID = upe.UserID
and uf.FollowingId = '100'))
Turn IN ( SELECT ... ) into a JOIN .. ON ... (see below)
Turn OR into UNION (see below)
Some the tables are many:many mappings? Such as SStreamFollowing? Follow the tips in http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table
Example of IN:
SELECT ssp.PostId
FROM SStreamPost ssp
WHERE (ssp.SStreamId IN (
SELECT ssf.SStreamId
FROM SStreamFollowing ssf
WHERE ssf.UserId = '100' ))
-->
SELECT ssp.PostId
FROM SStreamPost ssp
JOIN SStreamFollowing ssf ON ssp.SStreamId = ssf.SStreamId
WHERE ssf.UserId = '100'
The big WHERE with all the INs becomes something like
JOIN ( ( SELECT pc.PostId AS id ... )
UNION ( SELECT pum.PostId ... )
UNION ( SELECT ssp.PostId ... )
UNION ( SELECT psm.PostId ... ) )
Get what you can done of that those suggestions, then come back for more advice if you still need it. And bring SHOW CREATE TABLE with you.

SQL query returns column with data from another column

I have a table Notices connected to tables Likes and Comments. When I return the notices for a user I also create columns: number_of_likes, number_of_comments and liked_by_me. The query is working correctly when the user making the query hasn't liked a notice (liked_by_me = 0) . But if they have (liked_by_me = 1) the value I get for number_of_likes is wrong and is the same as number_of_comments.
Example:
1)
- liked by me = false
- likes = 1
- comments = 5
Returned values:
- liked_by_me = 0
- number_of_likes = 1
- number_of_comments = 5
2)
- liked by me = true
- likes = 2
- comments = 5
Returned values:
- liked_by_me = 1
- number_of_likes = 5
- number_of_comments = 5
Here is the query I am using:
SELECT notices.*
, count(comment.id) as number_of_comments
, count(like1.user_id) as number_of_likes
, like2.user_id IS NOT NULL AS liked_by_me
, boards.name as board_name
FROM notices
LEFT JOIN comments as comment
ON (comment.notice_id = notices.id)
LEFT JOIN likes as like1
ON (like1.notice_id = notices.id)
LEFT JOIN likes as like2
ON (like2.notice_id = notices.id
AND like2.user_id = $1)
LEFT JOIN boards
ON (boards.id = notices.board_id)
LEFT OUTER JOIN board_users
ON (board_users.board_id = notices.board_id)
WHERE board_users.user_id = $1
GROUP BY notices.id
, boards.name
, like2.user_id
, userId
Any help would be appreciated. I have been on this for hours and I don't think I will be able to find the problem.
Thanks!
Solution:
Here is the working query
SELECT notices.*,
(SELECT COUNT(user_id) from likes WHERE likes.notice_id = notices.id) AS number_of_likes,
(SELECT user_id IS NOT NULL from likes WHERE likes.notice_id = notices.id AND likes.user_id = $1) AS liked_by_me,
count(comments.id) as number_of_comments, boards.name as board_name
FROM notices LEFT JOIN comments ON (comments.notice_id = notices.id)
LEFT JOIN boards ON (boards.id = notices.board_id)
LEFT OUTER JOIN board_users ON (board_users.board_id = notices.board_id)
WHERE board_users.user_id = $1 GROUP BY notices.id, boards.name", user);
You will have to use subeselects.
Excellent article on this problem: The GROUPing pitfall
TL;DR: Basically, you have to realize, that all your comments and likes are being multiplicated by one another. Try to display the result of the query without the group clause to see, that duplicate likes/comments are being counted.
EDIT: I didn't test this, but it's how the query might look:
(that is if user can only like one notice once, otherwise you would have to group current user likes too)
SELECT
notices.*,
comments.number_of_comments,
likes.number_of_likes
current_user_likes.user_id IS NOT NULL AS liked_by_me
boards.name AS board_name
FROM notices
LEFT JOIN (
SELECT
COUNT(*) AS number_of_comments,
notice_id
FROM comments
GROUP BY notice_id
) AS comments ON comments.notice_id = notices.id
LEFT JOIN (
SELECT
COUNT(*) AS number_of_likes,
notice_id
FROM likes
GROUP BY notice_id
) AS likes ON likes.notice_id = notices.id
LEFT JOIN likes AS current_user_likes
ON current_user_likes.notice_id = notices.id
AND current_user_likes.user_id = $1
LEFT JOIN boards ON boards.id = notices.board_id
INNER JOIN board_users
ON board_users.board_id = notices.board_id
AND board_users.user_id = $1;

Why does a LEFT JOIN break my MySQL query?

SELECT value AS $point_fld, COUNT(value) as cnt
FROM ?_data d LEFT JOIN
?_user u
ON d.`table` = 'user' AND d.`id` = u.user_id
WHERE `key` = ? AND `value`<>'' AND `value`<>'Blank' AND
u.added BETWEEN ? AND ?
/* ----- I added this bit */
LEFT JOIN ?_invoice_payment p
ON d.'id' = p.user_id
SUM(p.amount) as moneysum
/* End ---- */
GROUP BY $point_fld
I am trying to edit this query to add in a sum value for payments based on which users are returned in the first part. The commented text is what I added. It is working just as intended but when I add in my part to get the values it breaks it.
Any suggestions would be amazing thanks. I'm pretty new to joins and stuff in SQL.
SELECT value AS $point_fld,
COUNT(value) as cnt,
SUM(p.amount) as moneysum
FROM ?_data d
INNER JOIN ?_user u ON d.`table` = 'user' AND d.`id` = u.user_id
LEFT JOIN ?_invoice_payment p ON d.'id' = p.user_id
WHERE `key` = ? AND `value`<>'' AND `value`<>'Blank' AND
u.added BETWEEN ? AND ?
GROUP BY value
OK this is what finally worked. Thanks for the help!
SELECT value AS $point_fld,
COUNT(DISTINCT u.user_id) as cnt,
SUM(p.amount) as moneysum
FROM ?_data d
LEFT JOIN ?_user u ON d.table = 'user' AND d.id = u.user_id
LEFT JOIN ?_invoice_payment p ON d.id = p.user_id
WHERE key = ? AND value<>'' AND value<>'Blank' AND
u.added BETWEEN ? AND ?
GROUP BY value

How to scan through multiple items in MySQL?

I want to select all elements from the element table, but with an extra column stating whether or not ANY of their comments have admin status. A comment has admin status if the user that posted the status' admin column is 1.
I don't know how I'd scan through each comment for every element being queried.
In another part of the program, there is a query to draw down all comments of a single issue, and I was able to reason how to determine admin status there, but I can't think of a way to do it in a query that pulls down more than one issue.
SELECT comments.id, comments.elementID, comments.googleID, comments.time, comments.body, users.name, users.admin
FROM comments
LEFT JOIN users ON comments.googleID = users.googleID
WHERE comments.elementID = ? AND comments.approved = 1
ORDER BY comments.time DESC
To know which elements have an comment from an admin user, you could simply do:
SELECT C.elementID, MAX(U.admin) AS admin
FROM comments C
LEFT JOIN users U ON C.googleId = U.googleID
WHERE C.approved = 1
GROUP BY C.elementID;
and then to get all elements with the additional admin column:
SELECT E.*, CASE WHEN A.admin = 1 THEN 1 ELSE 0 END AS admin
FROM elements E
LEFT OUTER JOIN
(SELECT C.elementID, MAX(U.admin) AS admin
FROM comments C
LEFT JOIN users U ON C.googleId = U.googleID
WHERE C.approved = 1
GROUP BY C.elementID) A
ON E.id = A.elementID
You can use a case expression with an exists predicate and a correlated subquery like so:
SELECT
e.*,
CASE WHEN EXISTS (
SELECT 1
FROM comments c
JOIN users u ON c.googleID = u.googleID
WHERE e.googleID = c.googleID AND u.admin = 1
) THEN 'Yes' ELSE 'No' END AS AdminComment
FROM elements e;
Or you could express it using joins:
SELECT
e.*,
CASE WHEN u.admin IS NOT NULL THEN 'Yes' ELSE 'No' END AS AdminComment
FROM elements e
LEFT JOIN comments c ON e.googleID = c.googleID
LEFT JOIN users u ON c.googleID = u.googleID AND u.admin = 1