I have a huge performance issue with my below query I figured that what is the leakage but don't know how to go around it.
the issue is that inner selects go through all the records in tables which is like 200K and then try to select and apply the filter to it so the whole data get selected in the first round.
SELECT *
FROM (
SELECT
( SELECT GROUP_CONCAT(tp.login) login
FROM tp
WHERE tp.user_id = ue.user_id ) login,
u.email as email,
ue.fname as name
FROM user_extra ue
LEFT JOIN users u ON u.id = ue.user_id
) t
WHERE
email like '%sradesign.net#gmail.com%'
OR fname like '%test%'
OR login like '%461988%
Test does I have not made a mistake:
SELECT GROUP_CONCAT(tp.login) login,
u.email as email,
ue.fname as name
FROM user_extra ue
JOIN users u ON u.id = ue.user_id -- I doubt in LEFT
JOIN tp ON tp.user_id = ue.user_id
GROUP BY ue.fname,
u.email
-- , ue.user_id -- maybe needed, but I doubt
HAVING email like '%sradesign.net#gmail.com%'
OR fname like '%test%'
OR login like '%461988%
This is intermediate variant - it must be divided on two (or maybe three - depends on your data) separate queries combined with UNION later.
sometimes it happens that one user doesn't have any login(there is no record for him in tp table) but I want to show him if the customer search term matched in the email of fname how to do this? – Ali Mahmoudi
In such case this table must be joined using outer joining (LEFT JOIN). – Akina
with left join again become like before performance issue the query run for 200 Second – Ali Mahmoudi
As I have said above - this is intermediate variant. If it is correct then let's divide taking into account possible absence in tp:
SELECT GROUP_CONCAT(tp.login) login,
u.email as email,
ue.fname as name
FROM user_extra ue
JOIN users u ON u.id = ue.user_id -- I doubt in LEFT
LEFT JOIN tp ON tp.user_id = ue.user_id
WHERE u.email like '%sradesign.net#gmail.com%'
GROUP BY ue.fname,
u.email
UNION ALL
SELECT GROUP_CONCAT(tp.login) login,
u.email as email,
ue.fname as name
FROM user_extra ue
JOIN users u ON u.id = ue.user_id -- I doubt in LEFT
LEFT JOIN tp ON tp.user_id = ue.user_id
WHERE ue.fname like '%test%'
GROUP BY ue.fname,
u.email
UNION ALL
SELECT GROUP_CONCAT(tp.login) login,
u.email as email,
ue.fname as name
FROM user_extra ue
JOIN users u ON u.id = ue.user_id -- I doubt in LEFT
JOIN tp ON tp.user_id = ue.user_id
GROUP BY ue.fname,
u.email
HAVING login like '%461988%
Test its output for correctness.
If it will produce duplicates then replace UNION ALL with UNION DISTINCT.
You seem to have an extra layer of SELECTs. This causes an extra temp table that contains a huge amount of data.
Unnecessary use of LEFT adds confusion and may prevent some optimizations.
Leading wildcards prevent usage of index.
OR is hard to optimize. But...
A common trick for optimizing OR:
( SELECT ... WHERE email like '%sradesign.net#gmail.com%' )
UNION DISTINCT
( SELECT ... WHERE fname like '%test%' )
UNION DISTINCT
( SELECT ... WHERE login like '%461988% )
However, because of the JOINs, UNION may not be practical.
Consider using FULLTEXT instead of LIKE.
Related
I am presently in a DBS course and I am working on an inline view:
SELECT userId, firstname, lastname, gender
FROM
(SELECT COUNT(dvdId) dvdId, u.userId
FROM userDVD
JOIN users u ON userDVD.userId = u.userId
GROUP BY userId) as T
WHERE gender = 'F';
When I run the Query it returns the error unknown column in field list. If I try to specify
u.firstname, u.lastname, u.gender
I return the same error. Any thoughts?
SELECT T.userId, T.firstname, T.lastname, T.gender
FROM (
SELECT users.userId, users.firstname, users.lastname, users.gender
FROM userDVD
JOIN users ON userDVD.userId = users.userId
WHERE gender = 'F' GROUP BY userId
) as T;
I worked through it and it turns out I didn't realize that because I had to alias my inline view that I needed to specify it in the original select statement. This concept was sadly not covered in my course. I appreciate those who gave helpful tips versus rude comments. So, Thank you Drew and Matt
Drew is right that you have a derived table T, but I'll attempt to add some details which might be useful to you as you are in a course.
you have a derived table T
an outer query (first select)
an inner query (second select in brackets)
the inner query uses a table alias of u for users.
the final columns of the inner query are dvdId which is a count of the dvdIds in userDvd table and userId
Because an outer query is constrained to use the final recordset of the inner query only those 2 columns are available to be selected and seeing they don't include firstname, lastname, or gender you are recieving the error.
If you want to keep your query the same but to use those columns you could join your derived table back to the user table and get the columns you desire. Such as this:
SELECT userId, firstname, lastname, gender
FROM
(SELECT COUNT(dvdId) dvdId, u.userId
FROM userDVD
JOIN users u ON userDVD.userId = u.userId
GROUP BY userId) as T
INNER JOIN users u2
ON t.user_id = u2.user_id
WHERE gender = 'F';
That technique to join back to the original table is great for when you have to optimize an aggregation that is on a large table or look for duplicates or something like that. However in your case you really just need a single query aggregating on all of the columns you want in the result set such as this:
SELECT
u.userId, u.firstname, u.lastname, u.gender, COUNT(d.dvdId) as DVDCount
FROM
userDVD d
JOIN users u
ON d.userId = u.userId
WHERE u.gender = 'F'
GROUP BY
u.userId, u.firstname, u.lastname, u.gender
;
SELECT DISTINCT u.id AS userId,u.type AS userType
FROM User AS u,Personal AS p,Company AS c
WHERE (p.realName LIKE '%adf%' AND u.type=1 AND u.id=p.userId)
OR (c.name LIKE '%grge%' AND u.id=c.userId)
LIMIT 0 , 10000
You can write your query as:
SELECT DISTINCT u.id AS userId,u.type AS userType
FROM User AS u inner join Personal AS p on u.id=p.userId
inner join Company AS c on u.id=c.userId
where p.realName LIKE '%adf%' or c.name LIKE '%grge%'
LIMIT 0 , 10000
Try to avoid comma seperated JOINS
You appear to be doing a quite hideous cross join, and then selectively narrowing down the records in the WHERE clause.
It is probably better to do 2 queries and union the results together. Each query can do one proper join. It is still going to have to access one column using the LIKE, and with a leading wild card that is not going to be quick (it can't use indexes).
SELECT u.id AS userId,
u.type AS userType
FROM User AS u
INNER JOIN Personal AS p
ON u.id = p.userId
WHERE p.realName LIKE '%adf%'
AND u.type = 1
UNION
SELECT u.id AS userId,
u.type AS userType
FROM User AS u
INNER JOIN Company AS c
ON u.id=c.userId
WHERE c.name LIKE '%grge%'
LIMIT 0 , 10000
Table 1 :- tbl_contacts
Fields
user_id
contact_id
first_name
last_name
Table 2 :- tbl_phone_details
Fields
contact_id
phone_number
phone_type
Table 3 :- tbl_email_details
Fields
contact_id
email_address
email_type
QUERY -
SELECT
tbl_contacts.*, tbl_email_details.*, tbl_phone_details.*
FROM
tbl_contacts, tbl_email_details,
tbl_phone_details
WHERE
tbl_contacts.user_id = '1'
I want to get first_name, last_name, Phone and Email details of particular user_id. I have used above query but its giving me repeated results and I am having less knowledge on DB queries like JOIN and INNER QUERY.
If anyone has any idea, please kindly help.
OUTPUT NEEDED:-
contact_id, first_name, last_name, phone_number, phone_type, email_address, email_type
(Here email and phone number can have 1 or more values for particular users).
Try like this
If you want to retrieve data for particular ID
SELECT T.contact_id,
T.first_name,
T.last_name,
P.phone_number,
P.phone_type,
E.email_address,
E.email_type
FROM tbl_contacts T LEFT JOIN tbl_phone_details P ON
T.contact_id = P.contact_id
LEFT JOIN tbl_email_details E ON
T.contact_id = E.contact_id
WHERE T.contact_id = #contact_id
If you want to retrieve all data
SELECT T.contact_id,
T.first_name,
T.last_name,
P.phone_number,
P.phone_type,
E.email_address,
E.email_type
FROM tbl_contacts T LEFT JOIN tbl_phone_details P ON
T.contact_id = P.contact_id
LEFT JOIN tbl_email_details E ON
T.contact_id = E.contact_id
SELECT tbl_contacts.*, tbl_email_details.*, tbl_phone_details.* FROM
tbl_contacts, tbl_email_details, tbl_phone_details WHERE
tbl_contacts.user_id = '1'
You forgot to mention the condition by which you are going to join all the tables!
SELECT c.first_name, c.last_name, p.phone_number, e.email_address
FROM tbl_contacts c, tbl_email_details e, tbl_phone_details p
WHERE tbl_contacts.user_id = '1'
AND c.contact_id = e.contact_id
AND e.contact_id = p.contact_id;
SELECT c.contact_id, c.first_name, c.last_name,
phone.phone_number, phone.phone_type,
email.email_address, email.email_type
FROM tbl_contacts c
LEFT JOIN tbl_email_details email ON c.contact_id = email.contact_id
LEFT JOIN tbl_phone_details phone ON c.contact_id = phone.contact_id
WHERE tbl_contacts.user_id = '1'
Sql queries are easy to learn and write and they are very useful in getting the important data from database. Joins are used to basically fetch the data from two or more tables on the bases of common column in both tables.
Inner Join will select those values that are common in both of the tables.
Left Join will select all the data from the left table and Right Join will select all the data from right table on the basis of id.
These are the basics of SQL and you must know how to fetch accurate data using them.
this is how I would make that request with JOIN ... but there might be some better or faster way to do it.
SELECT first_name, last_name, phone_number, email_address
FROM tbl_contacts
JOIN tbl_phone_details
ON tbl_contacts.contact_id=tbl_phone_details.contact_id
JOIN tbl_email_details
ON tbl_email_details.contact_id=tbl_contacts.contact_id
WHERE tbl_contacts.user_id = '1';
And just so you don't get lost in all the different answers here (which are probably all correct):
you don't need to give an aliase name to your tables (it's just for readability)
you don't need to mention the table names in your column list if the column name is unique (e.g first_name is only in tbl_contacts). Just if you want the contact_id then you should decide which one (e.g. tbl_phone_details.contact_id)
the multi-select as Jayaram proposed, is exactly the same as the JOIN. MySQL handles both queries the same way (I just didn't see his answer when I responded, sorry)
I usually go with the join approach but in this case I am a bit confused. I am not even sure that it is possible at all. I wonder if the following query can be converted to a left join query instead of the multiple select in used:
select
users.id, users.first_name, users.last_name, users.description, users.email
from users
where id in (
select assigned.id_user from assigned where id_project in (
select assigned.id_project from assigned where id_user = 1
)
)
or id in (
select projects.id_user from projects where projects.id in (
select assigned.id_project from assigned where id_user = 1
)
)
This query returns the correct result set. However, I guess the repetition of the query that selects assigned.id_project is a waste.
You could start with the project assignments of user 1 a1. Then find all assignments of other people to those projects a2, and the user in the project table p. The users you are looking for are then in either a2 or p. I added distinct to remove users who can be reached in both ways.
select distinct u.*
from assigned a1
left join
assigned a2
on a1.id_project = a2.id_project
left join
project p
on a1.id_project = p.id
join user u
on u.id = a2.id_user
or u.id = p.id_user
where a1.id_user = 1
Since both subqueries have a condition where assigned.id_user = 1, I start with that query. Let's call that assignment(s) the 'leading assignment'.
Then join the rest, using left joins for the 'optional' tables.
Use an inner join on user that matches either users of assignments linked to the leading assignment or users of projects linked to the leading project.
I use distinct, because I assumen you'd want each user once, event if they have an assignment and a project (or multiple projects).
select distinct
u.id, u.first_name, u.last_name, u.description, u.email
from
assigned a
left join assigned ap on ap.id_project = a.id_project
left join projects p on p.id = a.id_project
inner join users u on u.id = ap.id_user or u.id = p.id_user
where
a.id_user = 1
Here's an alternative way to get rid of the repetition:
SELECT
users.id,
users.first_name,
users.last_name,
users.description,
users.email
FROM users
WHERE id IN (
SELECT up.id_user
FROM (
SELECT id_user, id_project FROM assigned
UNION ALL
SELECT id_user, id FROM projects
) up
INNER JOIN assigned a
ON a.id_project = up.id_project
WHERE a.id_user = 1
)
;
That is, the assigned table's pairs of id_user, id_project are UNIONed with those of projects. The resulting set is then joined with the user_id = 1 projects to obtain the list of all users who share the projects with the ID 1 user. And now it only remains to retrieve the details for those users, which in this case is done in the same way as in your query, i.e. using an IN clause.
I'm sorry to say that I don't have MySQL to thoroughly test the performance of this query and so cannot be quite sure if it is in any way better or worse than your original query or than the one suggested both by #GolezTrol and by #Andomar. Generally I tend to agree with #GolezTrol's comment that a query with simple (semi- or whatever-) joins and repetitive parts might turn out more efficient than an equivalent sophisticated query that doesn't have repetitions. In the end, however, it is testing that must reveal the final answer for you.
The following query does what I want. It returns all the resuls in the users table and then if there is a match in the details tble, returns the relevant data
users
id|username
details
id|userid|firstname|lastname
$sql = "SELECT u.*, d.*
FROM `users` u
LEFT JOIN `details` d on
u.id = d.userid
ORDER BY $strorder";
However, when I try to join an additonal table where I want to do the same thing--return all the results of the users table and if there is a match in the third table, return the relevant data (total followers of this user)--it only returns one record.
3rd table
follow
id|followerid|followedid
$sql = "SELECT u.*, d.*, COUNT(f.id)
FROM `users` u
LEFT JOIN `details` d on
u.id = d.userid
LEFT JOIN `follow` f on
u.id = f.followedid
ORDER BY $strorder";
Can anyone see what I am doing wrong? Thanks in advance for any suggestions.
Many thanks.
Try to avoid * to select fields, it will be clearer to group your datas (even if mysql is quite permissive with groupings).
When you have an aggregate function (like COUNT, SUM), the other "non aggregated" requested fields should be in a GROUP BY clause.
Mysql don't force you to GROUP BY all the fields, but... I think it's quite a good habit to be "as ANSI as possible" (usefull when you use another DBMS)
SELECT u.id, u.username, d.firstname, d.lastname, count(*) as numberfollowers
FROM user u
LEFT JOIN details d on u.id = d.userid
LEFT JOIN follow f on u.id = f.followedid
GROUP BY u.id, u.username, d.firstname, d.lastname --or just GROUP BY u.id with Mysql
ORDER BY count(*) desc
COUNT being an aggregate function, when selected with other columns, requires you to group your results by those other columns in the select list.
You should rewrite your query with columns that you want to select from users and details and group by those columns.