sql query is behaving strange - mysql

I have a query
select c.CommentId
,c.CommentText
, c.CommenterId
, c.CommentDate
, u.first_name
, u.last_name
, i.ImageName
, i.Format
from comment c
join users u
on c.CommenterId = u.user_id
join user_profile_image i
on u.user_id = i.UserId
where PostId = 76
order
by CommentDate desc
limit 10
This query returns empty results when i.ImageName field is empty in the table. I want to return the row if the ImageName field is emty. How should I do this?

JOIN defaults to INNER JOIN for MySQL - try changing
join user_profile_image i
to
LEFT join user_profile_image i
The accepted answer here has a good visual explanation: Difference in MySQL JOIN vs LEFT JOIN

To include the rows when the ImageName field is empty, use LEFT JOIN, like this:
SELECT c.CommentId,c.CommentText, c.CommenterId, c.CommentDate, u.first_name,
u.last_name,i.ImageName,i.Format
FROM comment c
INNER JOIN users u ON c.CommenterId=u.user_id
LEFT JOIN user_profile_image i ON u.user_id=i.UserId
WHERE PostId = 76
ORDER BY CommentDate DESC
LIMIT 10;

The issue isn't exactly that i.ImageName is empty. The issue is that there is no image associated with the user. The join doesn't find an image, and without a match, the user isn't returned.
The solution is to use left join. My inclination is to write the query entirely with left join:
select c.CommentId, c.CommentText, c.CommenterId, c.CommentDate,
u.first_name, u.last_name,
i.ImageName, i.Format
from comment c left join
users u
on c.CommenterId = u.user_id left join
user_profile_image i
on u.user_id = i.UserId
where PostId = 76
order by c.CommentDate desc
limit 10;
Note: This assumes that PostId is in the comment table, which seems reasonable given the table names.

Related

mysql query takes to much time

I want to optimize this query becouse it takes to much time to return records
SELECT
u.*,
s.legal_name AS structure_name,
ui.id AS userinfo_id,
ui.structure_id AS structure_id,
ui.lrn_user,
ui.gender,
ui.fiscal_code,
ui.prov,
ui.phone,
ui.school_name,
ui.school_codice_meccanografico,
us.status, us.date AS status_date,
CONCAT(u.lastname,' ',u.firstname) AS fullname,
CONCAT(u.firstname,' ',u.lastname) AS display_name,
uu.username AS created_by_name,
g.group_names,
IF(u.website_id = 0,'Sito Web principale', w.name) AS website_name
FROM fcf_users AS u
LEFT JOIN (
SELECT
gu.user_id,
GROUP_CONCAT(gg.name SEPARATOR ', ') AS group_names
FROM fcf_user_user_groups gu
JOIN fcf_user_groups gg ON gg.id = gu.group_id
GROUP BY user_id
) g ON g.user_id = u.id
LEFT JOIN fcf_users_userinfo AS ui ON ui.user_id = u.id
LEFT JOIN fcf_users_user_statuses AS us ON us.user_id = u.id
LEFT JOIN fcf_structures_structures AS s ON s.id = ui.structure_id
LEFT JOIN fcf_users AS uu ON uu.id = u.created_by
LEFT JOIN fcf_websites AS w ON w.id = u.website_id
WHERE
u.id IN (SELECT user_id FROM fcf_user_user_groups WHERE group_id = '8')
AND u.id IN (SELECT user_id FROM fcf_user_user_groups WHERE group_id = '8')
AND ui.lrn_user = '0'
ORDER BY fullname ASC
LIMIT 0,25
If anyone can help, thanks
Turn it inside-out. That is, first use a 'derived' table to locate 25 users you want. Then gather the rest of the info.
What you have gathers all the info (including all the JOIN work) for all the users, then sorts and peels off 25.
It will be something like:
SELECT -- lots of stuff
FROM ( SELECT u.id,
CONCAT(u.lastname,' ',u.firstname) AS fullname
FROM fcf_users AS u
JOIN fcf_user_user_groups AS ug ON ...
JOIN fcf_users_userinfo AS ui ON ui.user_id = u.id
WHERE ug.group_id = '8'
AND ui.lrn_user = '0'
ORDER BY u.lastname, u.firstname -- now sargeable
LIMIT 25
) AS u25
JOIN .... -- whatever tables are needed to get the rest of the columns
ORDER BY u25.fullname -- yes, again, but now using the CONCAT
-- no limit here
Also:
u: INDEX(lastname, firstname, id)
user_user_group is a "many-t0=many mapping" table? If so, follow the indexing advice here: http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table
Ditto for any other many:many tables.
Note how I put into the derived table only the tables needed to achieve the LIMIT.

Get data even if one row is NULL

I have this query to get users from the users table and also get the latest time (a timestamp) from the logs table where the entry is "login_ok". This is intended to show a list of users and the last time them logged in.
SELECT u.`id`, u.`email`, u.`firstname`, u.`lastname`, u.`type`, u.`creation_date`, MAX(l.`time`) as `last_login`
FROM `users` AS u
JOIN `logs` AS l ON u.id = l.user_id
WHERE l.`action` = 'login_ok'
AND `visible` = 1
GROUP BY u.`id`
ORDER BY u.`id` ASC
My issue here is: if the user has never logged in, the "login_ok" entry doesn't exists for that user, so the query cannot get that user data.
Is there any way to get all user data even if the l.time on logs doesn't exist? I tried with JOINname_admin_users_logAS l ON (l.timeIS NOT NULL AND u.id = l.user_id) but still not showing that new user without login log.
Use a LEFT JOIN instead of a regular JOIN (which actually means INNER JOIN), and move the filter on action = 'login_ok' to that LEFT JOIN clause.
NB : from your query we cannot tell from which table the visible column comes from, so I assumed it is related to users...
SELECT
u.id,
u.email,
u.firstname,
u.lastname,
u.type,
u.creation_date,
MAX(l.time) as last_login
FROM users AS u
LEFT JOIN logs AS l
ON u.id = l.user_id and l.action = 'login_ok'
WHERE u.visible = 1
GROUP BY u.id
ORDER BY u.id ASC
Use a left join:
SELECT u.`id`, u.`email`, u.`firstname`, u.`lastname`, u.`type`, u.`creation_date`, MAX(l.`time`) as `last_login`
FROM `users` u LEFT JOIN
`logs` l
ON u.id = l.user_id AND l.`action` = 'login_ok'
WHERE u.`visible` = 1
GROUP BY u.`id`
ORDER BY u.`id` ASC;
This assumes that visible is in users. If it is in logs, then that condition should also be in the ON clause.
You can simply ignore the login_ok and search for all values of l.action.
But if you want only values with login_ok and null then you can do:
WHERE l.`action` = 'login_ok' OR l.`action` is null
You should use LEFT/RIGHT joins instead of JOIN(inner)
For example:
SELECT u.id, u.name, l.time
FROM users u LEFT JOIN logs l
ON u.id=l.user_id
In this case you'll get ALL the records from 'users'(the table on the left in the select: users - left, logs - right) and all records from the right('logs') table for which condition u.id=l.user_id is true.
Finally you'll get something like this:
u.id u.name l.time
1 John 10.00am
2 Mary 2.15pm
3 Mark null

Join two different tables ordered by common value (hotness)

I'm trying to select results from two different unrelated tables, showcase and questions to appear in a feed. They should be ordered by the common column hotness which is a float value.
SELECT s.id,s.date,s.title,s.views,s.image,s.hidpi,s.width,s.description,u.display_name,u.avatar
FROM showcase AS s
INNER JOIN users AS u ON s.user_id = u.id
UNION
SELECT q.id,q.date,q.title,q.views,q.text,u.display_name,u.avatar,0,0,0
FROM questions AS q
INNER JOIN users AS u ON q.user_id = u.id
ORDER BY hotness DESC
LIMIT 10
I've tried UNION, but I have no idea how I should be using it here and get this error unknown column hotness
You need to select the value in order for the ORDER BY to recognize it:
SELECT s.id,s.date,s.title,s.views,s.image,s.hidpi,s.width,s.description,u.display_name,u.avatar, s.hotness
FROM showcase AS s
INNER JOIN users AS u ON s.user_id = u.id
UNION ALL
SELECT q.id,q.date,q.title,q.views,q.text,u.display_name,u.avatar,0,0,0, q.hotness
FROM questions AS q
INNER JOIN users AS u ON q.user_id = u.id
ORDER BY hotness DESC;
Note that I also changed the UNION to UNION ALL. Unless you intend to remove duplicates, there is no reason to incur the extra processing for doing that.
You can try this query:
SELECT r.* FROM (
SELECT s.id,s.date,s.title,s.views,s.image,s.hidpi,s.width,s.description,u.display_name,u.avatar, s.hotness
FROM showcase AS s
INNER JOIN users AS u ON s.user_id = u.id
UNION
SELECT q.id,q.date,q.title,q.views,q.text,u.display_name,u.avatar,0,0,0, q.hotness
FROM questions AS q
INNER JOIN users AS u ON q.user_id = u.id
) as r
ORDER BY r.hotness DESC
LIMIT 10
You need to merge Union result in subquery to apply Order by on the result. I also added hotness in select clause, please check I take field from good table.

MySQL check if value is in result set before summing

I have a table of ratings for comments, when I fetch comments, I also fetch the ratings and I also want to be able to display which comments the logged user has already voted on. This is what I am doing now
SELECT
c.id,
c.text,
c.datetime,
c.author,
u.email AS author_name,
SUM(cr.vote) AS rating,
cr2.vote AS voted
FROM comments c
LEFT JOIN users u ON u.id = c.author
LEFT JOIN comments_ratings cr ON c.id = cr.comment
LEFT JOIN comments_ratings cr2 ON c.id = cr2.comment AND cr2.user = :logged_user_id
GROUP BY c.id ORDER BY c.id DESC
But I don't like how I'm performing a second join on the same table. I know it is perfectly valid but if I could get the information I want from the first join, which is there anyway, why perform a second one?
Is it possible to figure out if a row with column user equal to :logged_user_id exists on table comments_ratings cr before executing the aggregate function(s)?
P.S.: If someone could come up with a better title, people can find in future, I'd also appreciate that.
You can do what you want with conditional aggregation:
SELECT c.id, c.text, c.datetime, c.author, u.email AS author_name,
SUM(cr.vote) AS rating,
MAX(cr.user = :logged_user_id) as voted
FROM comments c LEFT JOIN
users u
ON u.id = c.author LEFT JOIN
comments_ratings cr
ON c.id = cr.comment
GROUP BY c.id
ORDER BY c.id DESC;

MySQL query problem

How can I add the following code example 1 to example 2 without messing up my query.
Example 1
INNER JOIN users ON users_articles.user_id = users.user_id
Example 2.
SELECT users.*
FROM users_articles
INNER JOIN articles_comments ON users_articles.id = articles_comments.article_id
INNER JOIN users ON articles_comments.user_id = users.user_id
WHERE users.active IS NULL
AND users.deletion = 0
ORDER BY articles_comments.date_created DESC
LIMIT 50
If I understand you correctly, you want to join table users twice, once for comments, and once for articles? In that case, you need to alias the table. I usually use single- or two-letter aliases for brevity even when I do not double tables, but it is not important.
SELECT ...
FROM users_articles UA
INNER JOIN articles_comments AC ON UA.id = AC.article_id
INNER JOIN users UC ON AC.user_id = UC.user_id
AND UC.active IS NULL
AND UC.deletion = 0
INNER JOIN users UA ON UA.user_id = users.user_id
AND UA.active IS NULL
AND UA.deletion = 0
ORDER BY AC.date_created DESC
LIMIT 50
BTW, Don't use SELECT *, it is almost always better to list specifically what you want.
Disclaimer: I might have misunderstood what you are trying to do; posting a bit of context to your code is usually a good idea. In this case, the table names threw me a bit (if it's what I think it is, I'd've just gone with users, articles and comments).