How can i optimize MySQL query, event if it have index - php

Here is my query which is taking 17.9397 sec time to get response:
SELECT allbar.iBarID AS iBarID,
allbar.vName AS vName,
allbar.tAddress AS tAddress,
allbar.tDescription AS tDescription,
(SELECT COUNT(*)
FROM tbl_post p
WHERE p.vBarIDs = allbar.iBarID) AS `total_post`,
allbar.bar_usbg AS bar_usbg,
allbar.bar_enhance AS bar_enhance,
(SELECT count(*)
FROM tbl_user
WHERE FIND_IN_SET(allbar.iBarID,vBarIDs)
AND (eType = 'Bartender'
OR eType = 'Bar Manager'
OR eType = 'Bar Owner')) AS countAss,
allbar.eStatus AS eStatus
FROM
(SELECT DISTINCT b.iBarID AS iBarID,
b.vName AS vName,
b.tAddress AS tAddress,
(CASE LENGTH(b.tDescription) WHEN 0 THEN '' WHEN LENGTH(b.tDescription) > 0
AND LENGTH(b.tDescription) < 50 THEN CONCAT(LEFT(b.tDescription, 50),'...') ELSE b.tDescription END) AS tDescription,
b.usbg AS bar_usbg,
b.enhance AS bar_enhance,
b.eStatus AS eStatus
FROM tbl_bar b,
tbl_user u
WHERE b.iBarID <> '-10') AS allbar
I have tried EXPLAIN, here is the result of that:
Can anyone explain me this EXPLAIN result?

You should totaly rewrite that query, it's complete nonsense.
In this part
(SELECT DISTINCT b.<whatever>
FROM tbl_bar b,
tbl_user u
WHERE b.iBarID <> '-10') AS allbar
what you're basically doing is connecting every row from table tbl_bar with every row from tbl_user. Then filter tbl_bar, and when everything is selected (maybe MySQL has to write everything in a temporary table before doing this) return the result set without duplicates. You don't ever want to do that. Especially when you don't even select anything from tbl_user. When there's a connection, specify it. If there's none, don't join those tables or create a connection. I don't know if or how your tables are connected, but it should look something like this:
(SELECT DISTINCT b.<whatever>
FROM tbl_bar b
JOIN tbl_user u ON b.user_id = u.id /*or whatever the connection is*/
WHERE b.iBarID <> '-10') AS allbar
Then you have this ugly subquery.
(SELECT COUNT(*)
FROM tbl_post p
WHERE p.vBarIDs = allbar.iBarID) AS `total_post`,
allbar.bar_usbg AS bar_usbg,
allbar.bar_enhance AS bar_enhance,
which is by the way dependent (see your explain output). Which means, that this subquery is executed for every row of your outer query (yes, the one with the cross join as discussed above). Instead of this subquery, join the table in the outer query and work with GROUP BY.
So far the query should look something like this:
SELECT
b.iBarID AS iBarID,
b.vName AS vName,
b.tAddress AS tAddress,
b.tDescription AS tDescription,
COUNT(*) AS `total_post`,
allbar.bar_usbg AS bar_usbg,
allbar.bar_enhance AS bar_enhance
FROM
tbl_bar b
JOIN tbl_user u ON b.user_id = u.id
JOIN tbl_post p ON p.vBarIDs = b.iBarID
WHERE b.iBarID <> '-10'
GROUP BY b.iBarID
(In fact, this is not really right. Rule is, every column in the SELECT clause should either be in the GROUP BY clause as well or have an aggregate function (like count() or max() applied to it. Otherwise a random row of each group is displayed. But this is just an example. You will have to work out the details.)
Now comes the worst part.
(SELECT count(*)
FROM tbl_user
WHERE FIND_IN_SET(allbar.iBarID,vBarIDs)
AND (eType = 'Bartender'
OR eType = 'Bar Manager'
OR eType = 'Bar Owner')) AS countAss,
allbar.eStatus AS eStatus
The use of FIND_IN_SET() suggests, that you're storing multiple values in one column. Again, you never ever want to do that. Please read this answer to Is storing a delimited list in a database column really that bad? and then redesign your database. I won't help you with this one, as this clearly is stuff for a separate question.
All this didn't really explain the EXPLAIN result. For this question, I would have to write a whole tutorial, which I won't do, since everything is in the manual, as always.

Related

How to have Multiple Select queries in php?

Hello my issue is I do not know how to use select multiple times in a query when searching through a data base. Here is what I have below
$statement = $db->query(" SELECT M.name
FROM actors A, roles R, movies M
WHERE M.id = R.movie_id AND R.actor_id = A.id AND A.first_name = '$fname' AND A.last_name = '$lname';");
Right now this statement works, it is fine. but for another part of my project I would like for it to look like
SELECT ...
FROM ...
WHERE ... = (SELECT ...
FROM ...
WHERE...)
I know that looks bad, but I am a beginner in SQL, and I can't seem to code this without errors. Any tips would be great! If any other information is needed just ask :)
Thank you very much
You can use such constructs as
SELECT something
FROM sometable
WHERE column = (SELECT a FROM anothertable WHERE b = constant)
or
SELECT something
FROM sometable
WHERE column IN (SELECT value FROM anothertable WHERE c = constant)
In the first case column = the second, inner, SELECT query must return precisely one value.
In the second case column IN the inner query may return a bunch of values in one column. It can return no values at all.

How do I set the value of a column be a result of a SQL query using one of the columns of the same table

I have two tables
Table 1 : Users
Has columns id,Name,institution
Institution is a sting showing institution name.
Table 2: Institutions.
Has columns id,institution,count
I need the count column to be recording the number of rows in table 1 where institution is same as the institution in each row of Table 2. This should happen for all the rows of Table 2.
Please help me get the right MySQL queries or table relations to achieve this.
Join both the tables on instutions and get the count like below
select i.institution,
X.ins_count
from Institutions i
left join
(
select institution,
count(*) as ins_count
from Users
group by institution
) X on i.institutions = X.institutions
this question doesn't have a lot of detail but from what I understand you just want to do this.
UPDATE TABLE institutions,
(SELECT
i.id,
COUNT(u.id) as user_count
FROM Users u
JOIN Institutions i on i.institution = u.institution
GROUP BY i.institution) as temp
SET institutions.count = temp.user_count
WHERE temp.id = institutions.id;
I'd do something like this:
UPDATE institutions i
LEFT
JOIN ( SELECT u.institution
, COUNT(1) AS user_cnt
FROM users u
GROUP BY u.institution
) c
ON c.institution = i.institution
SET i.count = IFNULL(c.user_cnt,0)
The inline view (aliased as c) gets a count of users for each "institution". This result is outer joined to institutions. (We reference the result from that inline view query like it was a table; MySQL actually refers to the inline view as a "derived table".
We assign the value derived user_cnt value to the count column, and substituting 0 for NULL (which will happen if there are no users in a given "institution".)
To see this in action BEFORE you run the update, you can run a similar SELECT (remove the SET clause and change the UPDATE keyword to be SELECT <expression_list> FROM, optionally add an ORDER BY clause.
SELECT i.institution
, i.count AS old_count
, IFNULL(c.user_cnt,0) AS new_count
FROM institutions i
LEFT
JOIN ( SELECT u.institution
, COUNT(1) AS user_cnt
FROM users u
GROUP BY u.institution
) c
ON c.institution = i.institution
ORDER BY i.institution
As another possible approach to achieve the same result, but with (likely) less efficiency (if this is actually valid), would be to use a correlated subquery to return the value:
UPDATE institutions i
SET i.count = ( SELECT COUNT(1)
FROM users u
WHERE u.institution = i.institution
)
I know that approach "works" in a SELECT statement. I expect it to work in an UPDATE statement, but I haven't tested that. Equivalent SELECT would be:
SELECT i.institution
, i.count AS old_count
, ( SELECT COUNT(1)
FROM users u
WHERE u.institution = i.institution
) AS new_count
FROM institutions i
Absent any compelling reason to use a correlated subquery, I'd avoid that and go with the JOIN operation instead.

mysql left join with 9 tables

So the situation is I have a query that involves 9 tables and I need to write it so it returns all records even when the impactid in the workorderstates table is NULL.
Previous to the below query I noticed I wasn't getting all results that were "open" because initially I just had where workorderstates.impactid = impactdefiniton.impactid and in the situations where impactid is NULL in the workorderstates table this condition would not be true, thus eliminating records that should be returned because they are in fact "open".
So I devised this query below but every time I run it it will not work. It will will return not unique table alias workorder. If I use aliases for tables it just moves on the right tables in the join as not being unique. Can anyone offer me any help on restructuring the query so it will work? I've tried a lot of variations and interestingly enough the second query ALMOST works but it returns duplicate records (in this case four of the same record)
select workorder.workorderid, workorder.siteid,
FROM_UNIXTIME(workorder.CREATEDTIME/1000, '%m-%d-%Y %H:%i:%s') as createdate,
categoryname, IFNULL(workorderstates.impactid, "No Set") as impactid,
IFNULL(impactdefinition.name, "Not Set") as impactname, first_name,
sdorganization.name, statusname, title
from workorder, statusdefinition, sitedefinition, sdorganization,
prioritydefinition, categorydefinition, sduser, aaauser, workorderstates
left Join impactdefinition on workorderstates.impactid = impactdefinition.impactid
left join workorder on workorder.workorderid = workorderstates.workorderid
left join workorderstates on workorderstates.statusid = statusdefinition.statusid
left join workorder on workorder.siteid = sitedefinition.siteid
left join sitedefinition on sitedefinition.siteid = sdorganization.org_id
left join workorderstates on workorderstates.categoryid = categorydefinition.categoryid
left join workorder on workorder.requesterid = sduser.userid
left join sduser on sduser.userid = aaauser.user_id
where statusname='Open' and workorder.createdtime >= '1352678400000'
and sdorganization.name='MAPL'
order by workorder.workorderid
Query that almost works but is ugly (returns duplicated records):
select workorder.workorderid, workorder.siteid,
FROM_UNIXTIME(workorder.CREATEDTIME/1000, '%m-%d-%Y %H:%i:%s') as createdate,
categoryname, IFNULL(workorderstates.impactid, "No Set") as impactid,
IFNULL(impactdefinition.name, "Not Set") as impactname, first_name,
sdorganization.name, statusname, title
from workorder, statusdefinition, sitedefinition, sdorganization,
prioritydefinition, categorydefinition, sduser, aaauser, workorderstates
left Join impactdefinition on workorderstates.impactid = impactdefinition.impactid
where workorder.workorderid = workorderstates.workorderid
and workorderstates.statusid = statusdefinition.statusid
and workorder.siteid = sitedefinition.siteid
and sitedefinition.siteid = sdorganization.org_id
and workorderstates.categoryid = categorydefinition.categoryid
and workorder.requesterid = sduser.userid and sduser.userid = aaauser.user_id
and statusname='Open' and workorder.createdtime >= '1352678400000'
and sdorganization.name='MAPL'
order by workorder.workorderid
Any ideas of how to get this query working??? Thanks guys!
I took a look at your query and I think you have some basic misunderstandings about JOINs and how to write them. It's like you're just guessing at syntax at random, and that's not the way to write code.
I examined your query and converted it into SQL-92 syntax. I had to make some inferences about join conditions, so I can't guarantee it's correct for your application, but it's a lot closer to a legal query.
Only I couldn't find any condition in your example for the join to your prioritydefinition table. That's likely to be the cause of your duplicate rows. You're generating what's called a Cartesian product.
select workorder.workorderid, workorder.siteid,
FROM_UNIXTIME(workorder.CREATEDTIME/1000, '%m-%d-%Y %H:%i:%s') as createdate,
categoryname, IFNULL(workorderstates.impactid, "No Set") as impactid,
IFNULL(impactdefinition.name, "Not Set") as impactname, first_name,
sdorganization.name, statusname, title
from workorder
inner join statusdefinition on workorderstates.statusid = statusdefinition.statusid
inner join sitedefinition on workorder.siteid = sitedefinition.siteid
inner join sdorganization on sitedefinition.siteid = sdorganization.org_id
inner join prioritydefinition ...NO JOIN CONDITION FOUND...
inner join categorydefinition on workorderstates.categoryid = categorydefinition.categoryid
inner join sduser on workorder.requesterid = sduser.userid
inner join aaauser on sduser.userid = aaauser.user_id
inner join workorderstates on workorder.workorderid = workorderstates.workorderid
left Join impactdefinition on workorderstates.impactid = impactdefinition.impactid
where statusname='Open'
and workorder.createdtime >= '1352678400000'
and sdorganization.name='MAPL'
order by workorder.workorderid
You really need to get someone who knows your application and also knows how to write SQL to tutor you before you write any more SQL joins.
I too reformatted your query but have it more visually hierarchical to show relations from the first (left-side) table to what it is getting its details from (right-side) table. As Bill mentioned, you had an extra table that was not doing anything and thus your Cartesian product.
Now, if you ARE stuck and have no one else to really help, here is a basic of LEFT-JOIN vs INNER-JOIN. Left-join basically says I want every record from the table on the left (as I have listed first) REGARDLESS of there being a record found on the right side. If there IS a match, great, no problem... but if no match, your query will still run.
So, I've set to all LEFT-JOINs. You can change as needed for those you know MUST always exist... such as a work order is entered by a "user". So that you could change. Hopefully this helps you out. Also look at how I've nested from work order to work order states and from work order states to get the corresponding status definition and other things associated with the work order states table. Others were directly related with the work order, so those are at THAT hierarchical level.
One last note... not all your fields were table.field reference (which I changed to aliases to help readability). QUALIFY ALL your fields so you and others trying to help, or read your code in the future know the origin of the field, not just guessing (its in one of the tables)
select
WO.workorderid,
WO.siteid,
FROM_UNIXTIME(WO.CREATEDTIME/1000, '%m-%d-%Y %H:%i:%s') as createdate,
categoryname,
IFNULL(WOS.impactid, "No Set") as impactid,
IFNULL(ImpD.name, "Not Set") as impactname, first_name,
SDO.name,
statusname,
title
from
workorder WO
LEFT JOIN workorderstates WOS
ON WO.workorderid = WOS.workorderid
LEFT JOIN statusdefinition StatD
ON WOS.statusid = StatD.statusid
LEFT JOIN categorydefinition CatD
ON WOS.categoryid = CatD.categoryid
LEFT JOIN impactdefinition ImpD
ON WOS.impactid = ImpD.impactid
LEFT JOIN sitedefinition SiteD
ON WO.siteid = SiteD.siteid
LEFT JOIN sdorganization SDO
ON SiteD.siteid = SDO.org_id
and SDO.name = 'MAPL'
LEFT JOIN sduser U
ON WO.requesterid = U.userid
LEFT JOIN aaauser AU
ON U.userid = AU.user_id
where
statusname = 'Open'
and WO.createdtime >= '1352678400000'
order by
WO.workorderid

How to do a MySQL query without creating temp tables?

I have a bit of a situation here.
I have a query:
SELECT DISTINCT (testrecurring.id), testrecurring.cxl, testci.cd
FROM testci, testrecurring
WHERE (testci.id = testrecurring.id)
AND testci.x_origin='1'
ORDER BY testrecurring.id DESC;
Now, if a var is not set, I need to do a select on this query, and here is the catch. I need to exclude some id's. Here is how I'm doing it now.
I create a table with that query: create table xxx SELECT * ..... and now the results from my previous query are inside another table called xxx.
Then:
if (!isset($var)) {
$delete = mysql_query("delete from xxx USING xxx, future_recurring where xxx.id = future_recurring.id");
}
and after the records have been deleted I do my final select * from xxx.
This works just fine, the only thing is that I need to redo all this logic by not creating any tables. Maybe doing some joins, I'm not sure how to proceed.
I hope this is not very confusing.
Any ideas?
And now how about this?:
SELECT tr.id, tr.cxl, tci.cd
FROM testci AS tci
INNER JOIN testrecurring AS tr
ON tci.id = tr.id
LEFT OUTER JOIN future_recurring AS fr
ON tr.id = fr.id
WHERE tci.x_origin='1'
AND fr.id IS NULL
GROUP BY tr.id, tr.cxl, tci.cd
ORDER BY tr.id DESC
This only includes results in which the testrecurring.id is NOT FOUND in future_recurring
You just need to add a where condition to exclude the rows you don't want:
SELECT *
FROM testci
JOIN testrecurring on testrecurring.id = testci.id
WHERE testci.x_origin='1'
AND testci.id NOT IN (SELECT id FROM future_recurring)
ORDER BY testrecurring.id DESC;
Or you could try this which might give better performance:
SELECT *
FROM testci
JOIN testrecurring on testrecurring.id = testci.id
LEFT JOIN future_recurring on future_recurring.id = testci.id
WHERE testci.x_origin='1'
AND future_recurring id IS null
ORDER BY testrecurring.id DESC;
Although, if your indexes are good and sane and your data sets aren't enormous then the performance should be close.

PHP / MySQL - Confusing Query

Im trying to construct a query that goes over 3 tables and im COMPLETELY stumped ... my knowledge limit is basic 1 table query and i need some help before i stick my head in a blender.
I have the following query
SELECT * FROM internalrole WHERE introle = $imarole
Im fine with that part .. its the next thats getting me all stressed.
That query returns the following columns ( id, user_id, introle, proven, used )
What i then need to do is take the user_id from the results returned and use it to get the following
SELECT * FROM users WHERE id = user_id(from previous query) AND archive = 0 and status = 8
I need to put that into 1 query, but wait, theres more .... from the results there, i need to check if that user's 'id' is in the availability table, if it is, check the date ( column name is date ) and if it matches todays date, dont return that one user.
I need to put all that in one query :S ... i have NO IDEA how to do it, thinking about it makes my head shake ... If someone could help me out, i would be eternaly grateful.
Cheers,
Use INNER JOIN, which links tables to each other based on a common attribute (typically a primary - foreign key relationship)
say an attribute, 'id', links table1 and table2
SELECT t1.att1, t2.att2
FROM table1 t1
INNER JOIN table2 t2
ON t1.id = t2.id --essentially, this links ids that are equal with each other together to make one large table row
To add more tables, just add more join clauses.
SELECT u.*
FROM internalrole ir
INNER JOIN users u
ON ir.user_id = u.id
AND u.archive = 0
AND u.status = 8
LEFT JOIN availability a
ON ir.user_id = a.user_id
AND a.date = CURDATE()
WHERE ir.introle = $imarole
AND a.user_id IS NULL /* User does NOT exist in availability table w/ today's date */
EDIT: This second query is based on the comments below, asking to show only users who do exist in the availability table.
SELECT u.*
FROM internalrole ir
INNER JOIN users u
ON ir.user_id = u.id
AND u.archive = 0
AND u.status = 8
INNER JOIN availability a
ON ir.user_id = a.user_id
WHERE ir.introle = $imarole
Hmm, maybe something like this
SELECT * FROM users WHERE id IN (SELECT user_id FROM internalrole WHERE introle = $imarole) AND archive = 0 and status = 8;
A handy thing for me to remember is that tables are essentially arrays in SQL.
HTH!
Nested queries are your friend.
SELECT * FROM users WHERE id in (SELECT user_id FROM internalrole WHERE introle = $imarole) AND archive = 0 and status = 8
Alternatively joins:
SELECT * FROM users INNER JOIN internalrole ON users.id = internalrole.user_id WHERE internalrole.user_id = $imarole AND users.archive = 0 and users.status = 8

Categories