Is there a way to optimize this mysql code in php? - php

SELECT tblstud.studid,
tblsched.ip
FROM tblstud
LEFT JOIN tblstudsched ON (tblstud.studid=tblstudsched.studid)
LEFT JOIN tblsched ON (tblstudsched.schedid=tblsched.schedid)
WHERE tblstud.rfidid='".$rfid."'
AND tblsched.ip='".$ip."'
AND EXISTS
(SELECT tblsched.schedid
FROM tblsched
INNER JOIN tblstudsched
WHERE tblstudsched.schedid=tblsched.schedid
AND NOW() BETWEEN tblsched.timein AND tblsched.timeout)
This is my code so far and it is designed to be done after Arduino microcontroller sends data to PHP. IT takes me about 8 seconds before another data is to be accepted. I haven't checked the Arduino side but I think it's fast enough when I did another different mysql query but that one is relatively shorter than the one above.
The tables are in Inno DB but I'll try MyISAM later since the latter is the best when there are more readings than adding data.
The data types are Varchar with ranges varying from 3-30 characters each.
I'm confident also with the speed of the laptop I'm using.

This is your query:
SELECT tblstud.studid, tblsched.ip
FROM tblstud LEFT JOIN
tblstudsched
ON (tblstud.studid = tblstudsched.studid) LEFT JOIN
tblsched
ON (tblstudsched.schedid = tblsched.schedid)
WHERE tblstud.rfidid = '".$rfid."' AND tblsched.ip = '".$ip."' AND
EXISTS (SELECT tblsched.schedid
FROM tblsched INNER JOIN
tblstudsched
ON tblstudsched.schedid = tblsched.schedid AND
NOW() BETWEEN tblsched.timein AND tblsched.timeout
);
First, the LEFT JOIN in the outer query is unnecessary, because the WHERE conditions just turn it into an INNER JOIN anyway. Second, your EXISTS looks suspicious. It is just looking to see if there are any active schedules at all in the data. So, it is going to either filter everything out or nothing out. It is possible that you intend:
SELECT tblstud.studid, tblsched.ip
FROM tblstud INNER JOIN
tblstudsched
ON (tblstud.studid = tblstudsched.studid) INNER JOIN
tblsched
ON (tblstudsched.schedid = tblsched.schedid)
WHERE tblstud.rfidid = '".$rfid."' AND tblsched.ip = '".$ip."' AND
EXISTS (SELECT 1
FROM tblsched
WHERE tblstudsched.schedid = tblsched.schedid AND
NOW() BETWEEN tblsched.timein AND tblsched.timeout
);
However, I suspect that you want schedules that meet the where conditions. If so, this may be the logic that you want:
SELECT tblstud.studid, tblsched.ip
FROM tblstud LEFT JOIN
tblstudsched
ON (tblstud.studid = tblstudsched.studid) LEFT JOIN
tblsched
ON (tblstudsched.schedid = tblsched.schedid)
WHERE tblstud.rfidid = '".$rfid."' AND tblsched.ip = '".$ip."' AND
NOW() BETWEEN tblsched.timein AND tblsched.timeout;
If this suspicious is true, then you want indexes on tblstud(rfidid, studid), tblsched(schedid, ip, timein, timeout) and, of course, tblstudsched(studid, schedid). Note that these are composite indexes with multiple keys.

Related

Optimizing mysql IN query having large values

I have a mysql query like
SELECT `tbl_ticket`.`id`, `tbl_ticket`.`hd_user_username`,
`tbl_ticket`.`hd_user_email`, `tbl_ticket`.`ticket_title`,
`tbl_complain_type`.`complains` FROM `tbl_ticket` LEFT JOIN
`tbl_ticket_complain` ON tbl_ticket_complain.ticket_id=tbl_ticket.id
LEFT JOIN `tbl_complain_type` ON tbl_complain_type.id=tbl_ticket_complain.complain_id
LEFT JOIN `tbl_assignment` ON tbl_assignment.ticket_id=tbl_ticket.id
WHERE ((((`hd_user_username` LIKE '%searchterm%')
AND (`tbl_assignment`.`id` IN ($array)))
OR (`hd_user_email`='searchterm'))
OR (`ticket_title`='searchterm')) OR (`tbl_complain_type`.`complains`='searchterm')
$array contains around 7000 values like `$array=array(1,2,3,..)`
This query takes around 8 seconds to execute. Is there any alternative solution for this query ?
The value of $array is got from another query
select max(id) from tbl_assignment group by ticket_id
The slowness of query is due to multiple joins between tables
If the values in the array use in you IN clause come from a select you could use the fact that
An IN clause is equivalent to an inner join so you could use a inner join between your_table_with_id and the table.column you need for match eg:
SELECT `
tbl_ticket`.`id`
, `tbl_ticket`.`hd_user_username`
, `tbl_ticket`.`hd_user_email`
, `tbl_ticket`.`ticket_title`
, `tbl_complain_type`.`complains`
FROM `tbl_ticket`
LEFT JOIN `tbl_ticket_complain` ON tbl_ticket_complain.ticket_id=tbl_ticket.id
LEFT JOIN `tbl_complain_type` ON tbl_complain_type.id=tbl_ticket_complain.complain_id
LEFT JOIN `tbl_assignment` ON tbl_assignment.ticket_id=tbl_ticket.id
INNER JOIN your_table_with_id ON `tbl_assignment`.`id` = JOIN your_table_with_id.id
WHERE ((((`hd_user_username` LIKE '%searchterm%')
OR (`hd_user_email`='searchterm'))
OR (`ticket_title`='searchterm')) OR (`tbl_complain_type`.`complains`='searchterm')
Remeber also that the content of values use IN clause is limited and fail when the limit is exceeded
and in your case
SELECT `
tbl_ticket`.`id`
, `tbl_ticket`.`hd_user_username`
, `tbl_ticket`.`hd_user_email`
, `tbl_ticket`.`ticket_title`
, `tbl_complain_type`.`complains`
FROM `tbl_ticket`
LEFT JOIN `tbl_ticket_complain` ON tbl_ticket_complain.ticket_id=tbl_ticket.id
LEFT JOIN `tbl_complain_type` ON tbl_complain_type.id=tbl_ticket_complain.complain_id
LEFT JOIN `tbl_assignment` ON tbl_assignment.ticket_id=tbl_ticket.id
INNER JOIN (
select max(id) as id
from tbl_assignment
group by ticket_id
) t ON `tbl_assignment`.`id` = t.id
WHERE ((((`hd_user_username` LIKE '%searchterm%')
OR (`hd_user_email`='searchterm'))
OR (`ticket_title`='searchterm')) OR (`tbl_complain_type`.`complains`='searchterm'))
This is basically your query:
SELECT . . .
FROM tbl_ticket t LEFT JOIN
tbl_ticket_complain tc
ON tc.ticket_id = t.id LEFT JOIN
tbl_complain_type tct
ON tct.id = tc.complain_id LEFT JOIN
tbl_assignment a
ON a.ticket_id = t.id
WHERE (((hd_user_username LIKE '%searchterm%' AND
a.id IN ($array)
) OR
`hd_user_email`='searchterm'
) OR
ticket_title = 'searchterm'
) OR
tct.complain` = 'searchterm';
The issue with performance has nothing to do with IN. In fact, MySQL optimizes IN, as explained in the documentation:
If all values are constants, they are evaluated according to the type
of expr and sorted. The search for the item then is done using a
binary search. This means IN is very quick if the IN value list
consists entirely of constants.
You are not going to get faster than an IN list with constants.
The problem with your query is the string of ORs. These make is almost impossible for the optimizer to use indexes -- so the full result set has to be created and then filtered down.
It is hard for me to see how to improve this in your query. Sometimes, splitting a query into simpler chunks and connecting them using union or union all does the trick. Your conditions are a bit hard to follow, making that approach difficult for an outsider.

nested queries and calculations all in the same query - is it possible?

I am running a MySQL query to get all "users" with current orders.
(It is possible for a user to have more than 1 associated orders in the db/query).
However i also want to get the total order value for each user and total order count for each user that is being returned (within the below query).
I could do these calculations in PHP, but feel it is possible and would be neater all done within the same SQL query (if possible).
This is the basic query with no attempt to make the above calculations
SELECT u.UserID, u.UserName,
o.OrdersID, o.OrderProductName, o.OrderProductQT, o.OrderTotalPrice, o.tUsers_UserID, o.tOrderStatus_StatusID, o.OrderDate, o.OrderDateModified, o.OrderVoid, o.tProducts_ProductID,
os.OrderStatusName,
ud.UserDetailsName, ud.UserDetailsPostCode,
p.ProductName, p.ProductImage1
FROM tusers u
INNER JOIN torders o ON o.tUsers_UserID = u.UserID
INNER JOIN torderstatus os ON os.OrderStatus_StatusID = o.tOrderStatus_StatusID
INNER JOIN tuserdetails ud ON ud.tUsers_UserID = u.UserID
LEFT JOIN tproducts p ON p.ProductID = o.tProducts_ProductID
WHERE o.tOrderStatus_StatusID = ?
GROUP BY u.UserID
ORDER BY OrdersID DESC
I have tried various nested select queries, but none of them work (right)
Is what i want to do possible in SQL or should i just do it all in PHP once i have the returned query results?
Any advice is much appreciated
You can embed the slightly modified queries into another query. For instance:
SELECT userid, SUM(orderid) FROM orders GROUP BY userid
and
SELECT userid, SUM(distinct productid)
FROM
orders o
INNER JOIN orderlines ol on ol.orderid = o.orderid
GROUP BY
userid
can be combined to:
SELECT
u.userid
u.fullname,
(SELECT SUM(orderid)
FROM orders o
WHERE o.userid = u.userid) as ORDERCOUNT,
(SELECT SUM(distinct productid)
FROM
orders o
INNER JOIN orderlines ol on ol.orderid = o.orderid
WHERE
o.userid = u.userid) as UNIQUEPRODUCTS
FROM
users u
Note that the latter query will return all users and will return NULL for ORDERCOUNT or UNIQUEPRODUCTS when the subquery doesn't return anything (when a user doesn't have orders). Also, the query will fail when a subquery returns more than 1 row, which should never happen in the example I posted.

Faster search query for IN statement with SELECT in MySQL

I'm currently doing some query for my app and I need to get the nearest store on my current position and to do this first I need to get all the item that has the same name then get it's information and trim down that query. Now I used IN statement for this but since the items being searched are also based on a list I need to make use of another select for this here is my code so far:
select *
from product p,
store s,
branches b
where 1 = 1
and b.idproduct = p.idproduct
and p.store = s.idstore
and common_name IN(SELECT p.common_name
FROM shopping_list_content s, product p
WHERE 1 =1
AND s.iditem = p.idproduct
AND s.idlist =$listid)
Now it works as I wanted it to be but I wanted it to do the query faster than this. For now it takes more than 3 seconds for this query to run faster than this. much better if it is less than a second. Any other option I can use for this?
MySQL has difficulty optimising subqueries, when you write something like:
SELECT *
FROM T
WHERE T.ID (SELECT ID FROM T2);
It is sometimes rewritten as
SELECT *
FROM T
WHERE EXISTS
( SELECT 1
FROM T2
WHERE T.ID = T2.ID
);
The subquery is then executed once per row in T, whereas if you write:
SELECT T.*
FROM T
INNER JOIN
( SELECT DISTINCT ID
FROM T2
) T2
ON T2.ID = T.ID;
Your result set will be the same, but MySQL will first fill an in memory table with the results of the subquery and hash it on T2.ID, it then just needs to lookup against this hash table for each row in T.
Which behaviour you want really depends on how much data you are expecting from each table/subquery. If you have 1 million rows in T2, and 10 in T then there is no point in filling a temporary table with 1 million rows, only to subsequently only use it 10 times, whereas if you have a large number of rows in T and only a small amount in T2 the additional cost of materialising the subquery will be beneficial in the long run.
Another thing to point out (which has no impact on performance), the JOIN syntax you are using is the ANSI 89 syntax and was replaced by ANSI 92 explicit JOIN syntax over 20 years ago. Although directed at SQL Server, I think this article summarises the reasons to switch to the newer join syntax very well. Making your final query:
SELECT *
FROM product p,
INNER JOIN store s
ON p.store = s.idstore
INNER JOIN branches b
ON b.idproduct = p.idproduct
INNER JOIN
( SELECT DISTINCT p.common_name
FROM shopping_list_content s
INNER JOIN product p
ON s.iditem = p.idproduct
WHERE s.idlist =$listid
) s
ON s.common_name = p.common_name;
N.B. Most of the above does not apply if you are using MySQL 5.6.5 or later. In this version they introduced more Subquery Optimization that solved a lot of the above issues
This is your query fixed up to use proper join syntax:
select *
from product p join
store s
on p.store = s.idstore join
branches b
on b.idproduct = p.idproduct
where p.common_name IN (SELECT p.common_name
FROM shopping_list_content slc join
product p
ON slc.iditem = p.idproduct AND
slc.idlist = $listid
);
Assuming that the same common_name does not appear on multiple products and that shopping_list_content has no duplicate rows, you can replace this with a simple join:
select *
from product p join
store s
on p.store = s.idstore join
branches b
on b.idproduct = p.idproduct join
shopping_list_content slc
on slc.iditem = p.idproduct and
slc.idlist = $listid;
However, those assumptions may not be true. In that case, changing the subquery to use exists may help performance:
select *
from product p join
store s
on p.store = s.idstore join
branches b
on b.idproduct = p.idproduct
where exists (SELECT 1
FROM shopping_list_content slc join
product p2
on slc.iditem = p2.idproduct AND
slc.idlist = $listid
WHERE p.common_name = p2.common_name
);
For this latter query, an index on product(common_name, idproduct) along with shopping_list_content(iditem, idlist) should help.

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

Avoid multiple LEFT JOIN

Is there a way to avoid adding a second LEFT JOIN for the table "social_mcouple" to query where social_members.m_id = social_mcouple.c_id below?
$res = #mysql_query("SELECT *, DATE_FORMAT(m_lld,'%m/%d/%y') AS m_lld_formatted FROM social_members
LEFT JOIN social_member_types ON t_id=m_type WHERE m_user='".$en['user']."'");
If there will always be a social_mcouple that corresponds to social_members, or you're only interested in rows where there is a correspondence then you may use an INNER JOIN. If you need all social_members regardless of whether there is a corresponding social_mcouple then you will need a LEFT JOIN. The LEFT JOIN will give you all rows with social_mcouple.* set to NULL where there is not a match.
The performance hit will really depend on the size of your datasets.
EDIT: adding a sample UNION query.
$res = #mysql_query("
(SELECT social_members.*, social_member_types.*, DATE_FORMAT(m_lld,'%m/%d/%y') AS m_lld_formatted,
NULL AS mcouple1, NULL AS mcouple2, NULL AS mcouple3
FROM social_members
LEFT JOIN social_member_types ON t_id=m_type
WHERE m_user='".$en['user']."' AND m_type != 2)
UNION
(SELECT social_members.*, social_member_types.*, DATE_FORMAT(m_lld,'%m/%d/%y') AS m_lld_formatted,
social_mcouple.mcouple1, social_mcouple.mcouple2, social_mcouple.mcouple3
FROM social_members
LEFT JOIN social_member_types ON t_id=m_type
JOIN social_mcouple ON social_members.m_id = social_mcouple.c_id
WHERE m_user='".$en['user']."' AND m_type = 2)
");

Categories