Single SQL to retrieve different information from different tables - mysql

I have this query which retrives 10 ( $limited ) queries from MySQL ,
"SELECT content.loc,content.id,content.title,
voting_count.up,voting_count.down
FROM
content,voting_count
WHERE names.id = voting_count.unique_content_id
ORDER BY content.id DESC $limit"
This query did great for posts that were allready in database and had votes , however new posts won't show.
Vote row is "inserted" first time someone votes on post. I guess that the reason why they won't be listed as there is no unique_content_id to connect to.
If i change query into this :
"SELECT content.loc,content.id,content.title
FROM
content
ORDER BY content.id DESC $limit"
it works , but i can't access voting_count.up & voting_count.down rows.
How could i access both information in single query ? Is it doable ?

If some data might not exist in one of the tables, instead of using INNER JOIN you should use LEFT JOIN:
SELECT content.loc,content.id,content.title,
-- USE function COALSESCE will show 0 if there are no
-- related records in table voting_count
COALESCE(voting_count.up, 0) as votes_up,
COALSESCE(voting_count.down, 0) as voted_down
FROM content LEFT JOIN voting_count
ON content.id = voting_count.unique_content_id
ORDER BY content.id DESC

As someone else above mentioned, what is names.id? However, perhaps the following might be of use assuming the join should have been from content.id to voting_count.unique_content_id:
$sql="select
c.`loc`,c.`id`, c.`title`,
case
when v.`up` is null then
0
else
v.`up`
end as 'up',
case
when v.`down` is null then
0
else
v.`down`
end as 'down'
from `content` c
left outer join `voting_count` v on v.`unique_content_id`=c.`id`
order by c.`id` desc {$limit}";

Related

Order by inside the LEFT JOIN

I am trying to write a query. I got it work half way, but I am having problems with the LEFT JOIN.
I have three tables:
user
user_preferences
user_subscription_plan
User will always have one user_preference, but it can have many or no entries in the user_subscription_plan
If the user has no entry in the user_subscription_plan, or if he has only one then my sql works. If I have more then one, then I have issue. In the case of two entries, how can I make it to return the last one entered? I tried playing with ORDER statement, but it does not work as expected. Somehow I get empty rows.
Here is my query:
SELECT u.id AS GYM_USER_ID, subscription_plan.id AS subscriptionId, up.onboarding_completed AS CompletedOnboarding,
(CASE
WHEN ((up.onboarding_completed = 1)
AND (ISNULL(subscription_plan.id)))
THEN 'freemiun'
WHEN (ISNULL(up.onboarding_completed)
AND (ISNULL(subscription_plan.id)))
THEN 'not_paying'
END) AS subscription_status
FROM user AS u
INNER JOIN user_preferences up ON up.user_id = u.id
LEFT JOIN (
SELECT * FROM user_subscription_plan AS usp ORDER BY usp.id DESC LIMIT 1
) AS subscription_plan ON subscription_plan.user_id = u.id
GROUP BY u.id;
If I run it as it is, then subscription_plan.id AS subscriptionId is always empty.
If I remove the LIMIT clause, then its not empty, but I am still getting the first entry, which is wrong in my case
I have more CASE's to cover, but I can't process until I solve this problem.
Please try to use "max(usp.id)" that "group by subscription_plan.user_id" instead of limit 1.
If you limit 1 in the subquery, the subquery's result will always return only 1 record (if the table has data).
So the above query can be rewritten like this.
Sorry, I didn't test, because I don't have data, but please try, hope this can help.
SELECT
u.id AS GYM_USER_ID,
subscription_plan.id AS subscriptionId,
up.onboarding_completed AS CompletedOnboarding,
(CASE
WHEN
((up.onboarding_completed = 1)
AND (ISNULL(subscription_plan.id)))
THEN
'freemiun'
WHEN
(ISNULL(up.onboarding_completed)
AND (ISNULL(subscription_plan.id)))
THEN
'not_paying'
END) AS subscription_status
FROM
user AS u
INNER JOIN
user_preferences up ON up.user_id = u.id
LEFT JOIN
(SELECT
usp.user_id, MAX(usp.id)AS id
FROM
user_subscription_plan AS usp
GROUP BY usp.user_id) AS subscription_plan ON subscription_plan.user_id = u.id;

How to fix MySQL providing duplicates that do not exist?

I have been recently messing with MySQL as I'm using it in a current project, I have a few thousand records in a table but there's one which stands out to me, I have a SELECT statement which collects a bunch of column names and uses them for the final query to send.
However when I run the query, it gives me duplicates as seen here:
https://i.imgur.com/PImNBam.png
The strange thing is that the ID is set as the key, so there's no right for MySQL to produce duplicates, and even if I go into the table and check manually, no duplicates exist.
This query used to work without a hitch on this exact server, I tried to group the scores by id and by song_name (from the photo) but it has given no results, I tried to delete duplicates using:
DELETE t1
FROM scores t1
INNER JOIN scores t2
WHERE t1.score < t2.score
AND t1.beatmap_md5 = t2.beatmap_md5
AND t1.userid = t2.userid;
But that returned zero queries and didn't change anything at all.
SQL query that I use to gather the information:
SELECT scores.id,
beatmaps.song_name,
scores.beatmap_md5,
users.username,
scores.userid,
scores.time,
scores.score,
scores.pp,
scores.play_mode,
scores.mods
FROM scores
LEFT JOIN beatmaps ON beatmaps.beatmap_md5 = scores.beatmap_md5
LEFT JOIN users ON users.id = scores.userid
WHERE users.privileges & 1 > 0
I really expected no duplicates to show as none of those exist, I don't know if mysql is having some caching issue or if this could be something else.
For avoid duplicated rows you could use distinct
SELECT DISTINCT
scores.id
, beatmaps.song_name
, scores.beatmap_md5
, users.username
, scores.userid
, scores.time
, scores.score
, scores.pp
, scores.play_mode
, scores.mods
FROM scores
LEFT JOIN beatmaps ON beatmaps.beatmap_md5 = scores.beatmap_md5
LEFT JOIN users ON users.id = scores.userid
WHERE users.privileges & 1 > 0

mysql Multiple left joins using count

I have been researching this for hours and the best code that I have come up with is this from an example i found on overstack. I have been through several derivations but the following is the only query that returns the correct data, the problem is it takes over 139s (more than 2 minutes) to return only 30 rows of data. Im stuck. (life_p is a 'likes'
SELECT
logos.id,
logos.in_gallery,
logos.active,
logos.pubpriv,
logos.logo_name,
logos.logo_image,
coalesce(cc.Count, 0) as CommentCount,
coalesce(lc.Count, 0) as LikeCount
FROM logos
left outer join(
select comments.logo_id, count( * ) as Count from comments group by comments.logo_id
) cc on cc.logo_id = logos.id
left outer join(
select life_p.logo_id, count( * ) as Count from life_p group by life_p.logo_id
) lc on lc.logo_id = logos.id
WHERE logos.active = '1'
AND logos.pubpriv = '0'
GROUP BY logos.id
ORDER BY logos.in_gallery desc
LIMIT 0, 30
I'm not sure whats wrong. If i do them singularly meaningremove the coalece and one of the joins:
SELECT
logos.id,
logos.in_gallery,
logos.active,
logos.pubpriv,
logos.logo_name,
logos.logo_image,
count( * ) as lc
FROM logos
left join life_p on life_p.logo_id = logos.id
WHERE logos.active = '1'
AND logos.pubpriv = '0'
GROUP BY logos.id
ORDER BY logos.in_gallery desc
LIMIT 0, 30
that runs in less than half a sec ( 2-300 ms )....
Here is a link to the explain: https://logopond.com/img/explain.png
MySQL has a peculiar quirk that allows a group by clause that does not list all non-aggregating columns. This is NOT a good thing and you should always specify ALL non-aggregating columns in the group by clause.
Note, when counting over joined tables it is useful to know that the COUNT() function ignores NULLs, so for a LEFT JOIN where NULLs can occur don't use COUNT(*), instead use a column from within the joined table and only rows from that table will be counted. From these points I would suggest the following query structure.
SELECT
logos.id
, logos.in_gallery
, logos.active
, logos.pubpriv
, logos.logo_name
, logos.logo_image
, COALESCE(COUNT(cc.logo_id), 0) AS CommentCount
, COALESCE(COUNT(lc.logo_id), 0) AS LikeCount
FROM logos
LEFT OUTER JOIN comments cc ON cc.logo_id = logos.id
LEFT OUTER JOIN life_p lc ON lc.logo_id = logos.id
WHERE logos.active = '1'
AND logos.pubpriv = '0'
GROUP BY
logos.id
, logos.in_gallery
, logos.active
, logos.pubpriv
, logos.logo_name
, logos.logo_image
ORDER BY logos.in_gallery DESC
LIMIT 0, 30
If you continue to have performance issues then use a execution plan and consider adding indexes to suit.
You can create some indexes on the joining fields:
ALTER TABLE table ADD INDEX idx__tableName__fieldName (field)
In your case will be something like:
ALTER TABLE cc ADD INDEX idx__cc__logo_id (logo_id);
I dont really like it because ive always read that sub queries are bad and that joins perform better under stress, but in this particular case subquery seems to be the only way to pull the correct data in under half a sec consistently. Thanks for the suggestions everyone.
SELECT
logos.id,
logos.in_gallery,
logos.active,
logos.pubpriv,
logos.logo_name,
logos.logo_image,
(Select COUNT(comments.logo_id) FROM comments
WHERE comments.logo_id = logos.id) AS coms,
(Select COUNT(life_p.logo_id) FROM life_p
WHERE life_p.logo_id = logos.id) AS floats
FROM logos
WHERE logos.active = '1' AND logos.pubpriv = '0'
ORDER BY logos.in_gallery desc
LIMIT ". $start .",". $pageSize ."
Also you can create a mapping tables to speed up your query try:
CREATE TABLE mapping_comments AS
SELECT
comments.logo_id,
count(*) AS Count
FROM
comments
GROUP BY
comments.logo_id
) cc ON cc.logo_id = logos.id
Then change your code
left outer join(
should become
inner join mapping_comments as mp on mp.logo_id =cc.id
Then each time a new comment are added to the cc table you need to update your mapping table OR you can create a stored procedure to do it automatically when your cc table changes

select last record in sub query

have read all similar qs but cant apply to my sql
Would like to select all customers AND the last action record inserted.
The sql below first selects the max actionid then uses that on another sub query - this takes 5+secs to run ;(
Please advise TQ
SELECT cus.cusid,cus.FirstName,cus.Surname,
lastact.actionid, lastact.actiondate, lastact.siteid
FROM cus
LEFT JOIN(
SELECT MAX(actionid) AS maxactionid, cusid
FROM `action`
INNER JOIN `event` ON event.eventid = action.`eventid`
GROUP BY cusid
) AS maxactionid ON maxactionid.cusid = cus.cusid
LEFT JOIN (
SELECT
action.actionid,
action.actiondate,
event.cusid,
event.siteid
FROM
`action`
INNER JOIN `event`
ON event.eventid = action.eventid
ORDER BY actionid DESC
) AS lastact ON lastact.actionid = maxactionid
WHERE UCASE(CONCAT(firstname, surname)) LIKE '%JIM%HEMM%'
TQ for ideas - please see following:
1) the limit idea, provides null results for lastact.actionid, lastact.actiondate, lastact.siteid - but does run in 0.075 secs!
Such a shame this idea fails
SELECT cus.cusid,cus.FirstName,cus.Surname, lastact.actionid, lastact.actiondate, lastact.siteid
FROM cus
LEFT JOIN (SELECT action.actionid, action.actiondate, event.cusid, event.siteid
FROM action
INNER JOIN event ON event.eventid = action.eventid
ORDER BY actionid DESC LIMIT 1
) AS lastact ON lastact.cusid = cus.cusid
WHERE UCASE(CONCAT(firstname, surname)) LIKE '%JIM%HEMM%'
2) EXPLAIN results of original query are:
3) Adding LIKE 'JIM%' AND cus.surname LIKE 'HEMM%' doesn't affect query time much but will include as per suggestion
Hi - have got great result by using ideas from everyone - Thank you
1) Changed WHERE to cus.FirstName LIKE 'JIM%' AND cus.surname LIKE 'H%'
2) Added index on firstname, surname
3) Added cusid in action table (don't need event table anymore)
4) Moved lookup tables (not in orig question) outside of action sub query
Finished sql looks like (runs in 0.063 secs - tested with a surname of only one letter!)
SELECT cus.cusid,cus.FirstName,cus.Surname, lastact.actionid, lastact.actiondate, lastact.siteid, actiontype.action,
FROM cus
LEFT JOIN (
SELECT action.actionid, action.actiondate, event.cusid, event.siteid
FROM action
ORDER BY actionid DESC
) AS lastact ON lastact.cusid = cus.cusid
LEFT JOIN actiontype ON actiontype.actiontypeid = lastact.typeid
WHERE cus.FirstName LIKE 'JIM%' AND cus.surname LIKE 'H%'
GROUP BY lastact.cusid
As JC Sama said "change select MAX(actionid) by select actionid and adding a limit 1 and order by desc", helps indexed
searchs.
As David K-J said "run an EXPLAIN first to see what the planner is trying to do. I would suspect it's the (non-indexable) search on concatenation strings".
You shouldn't put jokers '%' at the begining of a string when comparing, that disables indexed search.
You shouldn't use functions when comparing (at least, avoid them if you can), also for the indexed search.
Now that you can use indexes, add them if you haven't done it yet.
I may be wrong here, but I don't see the point of the last LEFT JOIN, as far as I'm concerned. You could withdraw that data from the first LEFT JOIN. Neither why are you grouping by cusid.
With all, the sql I made is (obviously not tested, you may have to fix some thing):
SELECT cus.cusid,cus.FirstName,cus.Surname,
maxaction.actionid, maxaction.actiondate, maxaction.siteid
FROM cus
LEFT JOIN(
SELECT actionid AS maxaction, action.actiondate, event.cusid, event.siteid
FROM `action`
INNER JOIN `event` ON event.eventid = action.eventid
order by actionid desc limit 1
) AS maxaction ON maxaction.cusid = cus.cusid
WHERE cus.FirstName like 'JIM%' and cus.surname like 'HEMM%'

MySQL difficult query

i delete my previous question to make it easier to understand.
I'm developing a forum with this database schema:
ID:INT->PRIMARY-KEY->AUTO_INCREMENT
IDTOPIC:INT (0 if it is the "father" topic OR father's ID if it's a reply)
IDUSER:INT (ID of user who posted)
CONTENT:MEDIUMTEXT
DATE:TIMESTAMP
I need to make a query ordered by date, where to get only last thread reply (not if "i am the last user who replied", IDUSER<>$userid) or father thread topic if there are no replies.
Even topic or reply results, i need to print first thread IDTOPIC/IDUSER
My ideal results should be like this:
ID:IDTOPIC:IDUSER:CONTENT:DATE:IDLASTREPLY:IDLASTUSERREPLY:LASTCONTENTREPLY:LASTDATEREPLY
there is no problem if the last four fields are NULL.
I need the fastest query possible.
Please help me!
SELECT fa.ID, fa.IDTOPIC, fa.IDUSER, fa.CONTENT, fa.DATE,
re.ID as IDLASTREPLY,
re.IDUSER as IDLASTUSERREPLY,
re.CONTENT as LASTCONTENTREPLY,
re.DATE as LASTDATEREPLY,
CASE WHEN (re.DATE is NULL) THEN fa.DATE ELSE re.DATE END as LASTUPDATE
FROM post fa
LEFT JOIN post re ON re.ID =
(SELECT ID FROM post WHERE IDTOPIC = fa.ID ORDER BY DATE DESC LIMIT 1)
WHERE fa.IDTOPIC = 0
ORDER BY LASTUPDATE DESC
and ORDER as you like.
But it's not so good performance. I think you could improve your table structure.
EDIT: add LASTUPDATE for ORDER
Unfortunately, this select is not so good after some posts...
Today, with a total of about 900 records on the table, when i'm filtering threads by my user id (only 45 distinct threads!), MySQL needs 0.30 sec (at the begin, there were needed only 0.04/0.05 sec... the deterioration i think is proportional to how many threads i subscribed)
This could means that when i will have 300 distinct threads subscribed i should wait about 2 seconds for this query... it's too much.
The strange thing is if i "limit 0,10" this query or get full query, the execution speed does not change! This is why i think it's not good... because i suppose it have to select the whole data even if i limit.
No way to solve. This is how i setup query. There is a new notications table, because i want to use it as notification query (to show ONLY replies not made by $USERID)
NOTIFICATIONS TABLE FIELDS: id,idcontent,userid
SELECT
CASE WHEN (re.id is NULL) THEN fa.id ELSE re.id END AS id,
fa.id as idtopic,
CASE WHEN (re.userid is NULL) THEN fa.userid ELSE re.userid END AS userid,
CASE WHEN (re.content is NULL) THEN fa.content ELSE re.content END AS content,
n.notify,
u.id as reuserid, u.name, u.surname, u.photo,
CASE WHEN (re.date is NULL) THEN unix_timestamp(fa.date) ELSE unix_timestamp(re.date) END AS LASTUPDATE
FROM notifications AS n
LEFT JOIN post AS fa
ON fa.id = n.idcontent
LEFT JOIN post AS re
ON re.id=(SELECT ID FROM post WHERE IDTOPIC = fa.ID AND userid <> $USERID ORDER BY DATE DESC LIMIT 1)
LEFT JOIN users AS u
ON u.id = ( CASE WHEN (re.userid is NULL) THEN fa.userid ELSE re.userid END )
WHERE
n.userid = $USERID
AND NOT (fa.userid = $USERID AND re.userid = $USERID)