This query returns records of user's friend from friendlist table and then get records of each user from user table.
OPTION # 1
SELECT
f.status,
f.user_to_id,
f.user_from_id,
u.user_id,
u.registration_date,
u.nickname,
u.email
FROM ti_friend_list AS f
LEFT JOIN ti_user u
ON u.user_id = (CASE f.user_from_id
WHEN 7 THEN f.user_to_id
ELSE f.user_from_id END)
WHERE (f.user_from_id = 7
OR f.user_to_id = 7)
OPTION # 2
SELECT
f.status,
f.user_to_id,
f.user_from_id,
u.user_id,
u.registration_date,
u.nickname,
u.email
FROM ti_friend_list AS f
LEFT JOIN ti_user u
ON u.user_id = f.user_to_id
WHERE f.user_from_id = 7
UNION ALL
SELECT
f.status,
f.user_to_id,
f.user_from_id,
u.user_id,
u.registration_date,
u.nickname,
u.email
FROM ti_friend_list AS f
LEFT JOIN ti_user u
ON u.user_id = f.user_from_id
WHERE f.user_to_id = 7
Which One is more optimal solution. Basically a comparison between CASE and UNION
I think like Brian :
I think it is more difficult for the engine to optimize a UNION because you can unifiate very different things !
The problem with UNION is that you must repeat the SELECT part of the query and don't make mistakes
The engine will perform the first part of the union, then the second part and then combine. I think some of the steps will be done twice.
You can try the "explain" command on your queries to see the plan the engine is using.
Run them both and see if there's a time difference!
That aside, I would suggest use of the CASE approach is a clearer one in terms of the intention and ease of extension in the future, and I would use that unless you find clear evidence it's not performing. Off the top of my head I think it would be faster though.
Related
I have a pretty big SQL query to get data from multiple database tables. I use the ON condition to check if the guild_ids are always the same and in some cases, he check's for an user_id too.
That is my query:
SELECT
SUM( f.guild_id = 787672220503244800 AND f.winner_id LIKE '%841827102331240468%' ) AS guild_winner,
SUM( f.winner_id LIKE '%841827102331240468%' ) AS win_sum,
m.message_count,
r.bypass_role_id,
i.real_count,
i.total_count,
i.bonus_count,
i.left_count
FROM
guild_finished_giveaways AS f
JOIN guild_message_count AS m
JOIN guild_role_settings AS r
JOIN guild_invite_count AS i ON m.guild_id = f.guild_id
AND m.user_id = 841827102331240468
AND r.guild_id = f.guild_id
AND i.guild_id = f.guild_id
AND i.user_id = m.user_id
But it runs pretty slow, with over 15s. I can't see why it needs so long.
I figured out that if I remove the "guild_invite_count" JOIN, it's pretty fast again. Do I have some simple error here that I don't see? Or what could be the issue?
Each JOIN expression needs it's own ON. Don't wait until the end for this. As it was, the server was forced to build up a cartesian product of all those tables before narrowing them down again, and I'm surprised the query ran at all (I'd expect a syntax error for missing ON clauses).
FROM guild_finished_giveaways AS f
JOIN guild_message_count AS m ON m.guild_id = f.guild_id
JOIN guild_role_settings AS r ON r.guild_id = f.guild_id
JOIN guild_invite_count AS i ON i.guild_id = f.guild_id
AND i.user_id = m.user_id
WHERE m.user_id = 841827102331240468
It's also more than a little odd to use SUM() or any other aggregate function in the same query as non-aggregated values without a GROUP BY clause.
Are you using InnoDB?
Does every table have a PRIMARY KEY?
These may help:
m: PRIMARY KEY(user_id) -- assuming that is unique in that table
f: INDEX(guild_id, winner_id)
r: INDEX(guild_id, bypass_role_id)
i: INDEX(user_id,)
It looks like some tables should not be separate -- perhaps r,i,f could be combined? (I need to see SHOW CREATE TABLE to say more.)
Do NOT have a commalist in winner_id. Instead have another table with one row per winner per game (or whatever it is a winner of). Perhaps just to columns like a Many-to-many mapping table.
Noting that the execution is likely to start with m and then go next to i let's improve on Joel's suggestion:
FROM guild_message_count AS m
JOIN guild_invite_count AS i ON i.user_id = m.user_id
JOIN guild_finished_giveaways AS f ON f.guild_id = m.guild_id
JOIN guild_role_settings AS r ON r.guild_id = m.guild_id
WHERE m.user_id = 841827102331240468
Note that 3 tables are joined on guild_id; but only 2 = are needed.
SUM without GROUP BY sums up the entire resultset (after JOINing). But you have 6 non-aggregates, so you need to GROUP BY all 6.
But that may lead to grossly inflated sums. Maybe you need to do the aggregation just over f first since that is where you are summing. Then JOIN to the rest??
I have the below query, which I appreciate probably isn't well written, but on my local PC with Xampp and MariaDB it executes in 0.1719 seconds, which is about the speed I would hope for.
However, on my development server with Plesk and MariaDB the same query with the same data takes over 12 seconds. Obviously would be no use.
Probably the query could be modified to make it better, but can somebody explain why the performance difference? The server is a VPS, it has no shortage of resources - it isn't live so usage is almost none at all, yet still 12+ seconds for this query.
The query:
SELECT m.id AS match_id, e.event AS event1
FROM matches m
JOIN competitions co ON co.id = m.competition
JOIN clubs h ON h.id = m.hometeam
JOIN clubs a ON a.id = m.awayteam
LEFT JOIN match_events e ON e.match = m.id
AND e.player = '7138'
WHERE (m.hometeam = '1'
OR m.awayteam = '1'
)
AND m.season = '121'
Are you sure you need AND e.player = '7138' in the ON clause of a LEFT JOIN and not in the WHERE clause?
Better indexing
Recommend these composite, covering, indexes:
m: (season, awayteam, hometeam, competition, id)
e: (player, match, event)
Avoiding OR
OR optimizes poorly. A common trick is to turn it into UNION. Such may work for your query:
SELECT ...
FROM matches JOIN ...
WHERE m.season = 121
AND m.hometeam = 1
UNION ALL
SELECT ...
FROM matches JOIN ...
WHERE m.season = 121
AND m.awayteam = 1
And have these two indexes:
INDEX(season, hometeam) -- will be used by one part of the UNION
INDEX(season, awayteam) -- will be used by the other
I chose UNION ALL because it is faster than UNION DISTINCT. But if you get unwanted dups, change it.
I have the following query:
SELECT
u.username as username,
s.campaignno as campaign,
if(f.hometeamscore>f.awayteamscore,1,0) as Win,
if(f.hometeamscore=f.awayteamscore,1,0) as Draw,
if(f.hometeamscore<f.awayteamscore,1,0) as Loss,
f.hometeamscore as Goals,
ss.seasonid as Season,
av.avatar as Avatar
FROM
avatar_avatar av,
straightred_fixture f,
straightred_userselection s,
auth_user u,
straightred_season ss
WHERE
av.user_id = u.id
AND ss.seasonid = 1025
AND f.soccerseasonid = ss.seasonid
AND s.fixtureid = f.fixtureid
AND s.teamselectionid = f.hometeamid
AND s.user_id = u.id;
This query is working as expected but I have now realised that a user may not have uploaded a profile picture. So the following part av.user_id = u.id is excluding anyone who has NOT uploaded a profile picture. I feel i need to use a left join after reading the following https://www.w3schools.com/sql/sql_join.asp but I just keep going around in circles and get nowhere.
Any guidance on this would be greatly appreciated, many thanks, Alan.
First and foremost: avoid implicit JOINs. Make JOINs explicit and you will make much more clear which entity relates to which entity, and you'll never forget to add one of the AND conditions in your WHERE and get a cartesian product.
Second: try to put your tables in the FROM using an order that follows a certain logic. In your case, you seem to start looking for ss.seasonid = 1025... (it's the only condition on the WHERE having a constant). Then, your list of conditions produces a certain logical order... Each table in the FROM has a relationship with the previous one...
That said, I think you need this query:
SELECT
u.username as username,
s.campaignno as campaign,
if(f.hometeamscore>f.awayteamscore,1,0) as Win,
if(f.hometeamscore=f.awayteamscore,1,0) as Draw,
if(f.hometeamscore<f.awayteamscore,1,0) as Loss,
f.hometeamscore as Goals,
ss.seasonid as Season,
av.avatar as Avatar
FROM
straightred_season ss
JOIN straightred_fixture f
ON f.soccerseasonid = ss.seasonid
JOIN straightred_userselection s
ON s.fixtureid = f.fixtureid AND s.teamselectionid = f.hometeamid
JOIN auth_user u
ON u.id = s.user_id
-- This last table is the one that needs to be LEFT-joined
-- if the avatar is *optional*. If it isn't there, av.avatar will just
-- be shown as NULL
LEFT JOIN avatar_avatar av
ON av.user_id = u.id
WHERE
ss.seasonid = 1025 ;
If the content of more tables is optional, you may need more than one LEFT JOIN. In order to find out what makes sense, we would need to have the full data model, or the ERD, that represents your scenario. That is, which relationships are 1 to 1, which are 1 to Many, which are 1 to (0 or 1), which are Many-to-Many, etc.
I'm a fan of using JOIN's so I rewrote your query like this.
Be advised however that I user SQL SERVER / ORACLE and not MYSQL so not sure if my semantics are correct. I use the IFNULL function since at least in my world, using a column where the row isn't available can cause the entire result to filter out.
Also by moving ss.seasonid = 1025 into the join, rather than leaving it in the where, you should get results regardless of there existing an ss record.
That said, this should resolve your issues:
EDIT - replace ISNULL with IFNULL
select
u.username as username
,s.campaignno as campaign
,if(ifnull(f.hometeamscore,0)>ifnull(f.awayteamscore,0),1,0) as Win
,if(ifnull(f.hometeamscore,0)=ifnull(f.awayteamscore,-1),1,0) as Draw
,if(ifnull(f.hometeamscore,0)<ifnull(f.awayteamscore,0),1,0) as Loss
,f.hometeamscore as Goals
,ss.seasonid as Season
,av.avatar as Avatar
from
auth_user u
Left Join
avatar_avatar av on u.id = av.user_id
Left Join
straightred_userselection s on u.id = s.user_id
Left Join
straightred_fixture f on f.hometeamid = s.teamselectionid
and f.fixtureid = s.fixtureid
Left Join
straightred_season ss on f.soccerseasonid = ss.seasonid
and ss.seasonid = 1025
I have the following query:
SELECT PKID, QuestionText, Type
FROM Questions
WHERE PKID IN (
SELECT FirstQuestion
FROM Batch
WHERE BatchNumber IN (
SELECT BatchNumber
FROM User
WHERE RandomString = '$key'
)
)
I've heard that sub-queries are inefficient and that joins are preferred. I can't find anything explaining how to convert a 3+ tier sub-query to join notation, however, and can't get my head around it.
Can anyone explain how to do it?
SELECT DISTINCT a.*
FROM Questions a
INNER JOIN Batch b
ON a.PKID = b.FirstQuestion
INNER JOIN User c
ON b.BatchNumber = c.BatchNumber
WHERE c.RandomString = '$key'
The reason why DISTINCT was specified is because there might be rows that matches to multiple rows on the other tables causing duplicate record on the result. But since you are only interested on records on table Questions, a DISTINCT keyword will suffice.
To further gain more knowledge about joins, kindly visit the link below:
Visual Representation of SQL Joins
Try :
SELECT q.PKID, q.QuestionText, q.Type
FROM Questions q
INNER JOIN Batch b ON q.PKID = b.FirstQuestion
INNER JOIN User u ON u.BatchNumber = q.BatchNumber
WHERE u.RandomString = '$key'
select
q.pkid,
q.questiontext,
q.type
from user u
join batch b
on u.batchnumber = b.batchnumber
join questions q
on b.firstquestion = q.pkid
where u.randomstring = '$key'
Since your WHERE clause filters on the USER table, start with that in the FROM clause. Next, apply your joins backwards.
In order to do this correctly, you need distinct in the subquery. Otherwise, you might multiply rows in the join version:
SELECT q.PKID, q.QuestionText, q.Type
FROM Questions q join
(select distinct FirstQuestion
from Batch b join user u
on b.batchnumber = u.batchnumber and
u.RandomString = '$key'
) fq
on q.pkid = fq.FirstQuestion
As to whether the in or join version is better . . . that depends. In some cases, particularly if the fields are indexed, the in version might be fine.
Is it good if i write query like this:- (see query in where condition)
SELECT distinct(id) "idea_id"
FROM ideas
WHERE deleted_by_user = 0 AND moderation_flag = 1 AND
user_id in (select id
from users
where confirm like "yes")
ORDER BY time_of_creation DESC
let me know if there is some issue in this query :
thanx in advance..
You can wirte this query in two ways:
SELECT DISTINCT(i.id) "idea_id"
FROM ideas i
INNER JOIN users u ON i.user_id = u.id
WHERE i.deleted_by_user = 0 AND i.moderation_flag = 1 AND u.confirm = 'yes'
ORDER BY i.time_of_creation DESC;
And
SELECT DISTINCT(i.id) "idea_id"
FROM ideas i
WHERE i.deleted_by_user = 0 AND i.moderation_flag = 1 AND
EXISTS (SELECT * FROM users u WHERE i.user_id = u.id AND u.confirm = 'yes')
ORDER BY i.time_of_creation DESC;
SELECT distinct a.ID idea_id
FROM ideas a
INNER JOIN users b
ON a.user_id = b.id
WHERE a.deleted_by_user = 0 AND
a.moderation_flag = 1
b.confirm = 'YES'
ORDER BY time_of_creation DESC
To answer your question - there are no problems with using subqueries.
On the other hand, you have (at least) three different things to think about when writing a query in one way or another:
How efficient will the data base run my query? (If the data base is small, this may not matter at all)
How easy is this to formulate and write? - which often connects to
How easy is this to understand for someone else who reads my code? (and I may myself count as "somebody else" if I look into code I've written a year ago...)
If you have a database of a size where efficiency counts, the best way to select how to formulate a query is normally to write it in different ways and test it on the data base. (but often the query optimizer in the data base is so good, it does not matter)
SELECT distinct i.id "idea_id"
FROM ideas i join users u
on i.user_id=u.id and u.confirm ='yes'
WHERE i.deleted_by_user = 0
AND i.moderation_flag = 1
ORDER BY i.time_of_creation DESC