Faster search query for IN statement with SELECT in MySQL - php

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.

Related

Combine two mysql group queries

I'm making a web app to create tournaments and as i have learned PHP in the course of this project, so my skills aren't probably the best.
I have an identifier in my database day2_semifinal or day2_additional which basically identifies the type of semifinal.
So my first query is:
$numberquery = mysql_query("
SELECT *
FROM tourneyplayers
INNER JOIN results
on (resultid=r_id)
INNER JOIN players
ON (p_id=playerid)
INNER JOIN tourneys
on (T_Id=tourneyid)
WHERE tourneyid='$tourneyid' and
in_day2 = 1 and
day2_semifinal IS NOT NULL
GROUP BY day2_semifinal
ORDER BY agegroupid",$connection);
This will get me all the semifinal groups, i'll iterate over them and query all the players in group:
$semigroup = $group['day2_semifinal'];
$playerQuery = mysql_query("
SELECT *
FROM tourneyplayers
INNER JOIN results
on (r_id=resultid)
INNER JOIN players
on (p_id=playerid)
WHERE tourneyid='$tourneyid' AND
day2_semifinal = '$semigroup' and
in_day2 = 1
ORDER BY day2startplace",$connection);
Now after i've created tables and echoed all the data from player queries for day2_semifinal, i run another query:
$numberquery = mysql_query("SELECT * FROM tourneyplayers INNER JOIN results on (resultid=r_id) INNER JOIN players ON (p_id=playerid) WHERE tourneyid='$tourneyid' and in_day2 = 1 and day2_additional_nosemi IS NOT NULL AND day2_additional_nosemi <> 0 GROUP BY day2_additional_nosemi ORDER BY agegroupid",$connection);
Which is fairly similar to the first one, only thing different is day2_semifinal identifiers have changed to day2_additional. After that query, i'll again, iterate over the day2_additional_nosemi groups and query the players inside of them:
$additionalgroup = $group['day2_additional_nosemi'];
$playerQuery = mysql_query("SELECT * FROM tourneyplayers INNER JOIN results on (r_id=resultid) INNER JOIN players on (p_id=playerid) WHERE tourneyid='$tourneyid' AND day2_additional_nosemi = '$additionalgroup' and in_day2 = 1 ORDER BY day2startplace",$connection);
Now this works, but this creates an issue with ordering, since the first query orders them by agegroupid but only for players in day2_semifinal (and i'd like to have day2_additional players ordered together with day2_semifinal). If i run another query the previous data has already been echoed and ordering is not right. How could i concatenate two $numberquery queries in order to select players after them as well?
I'm answering my own question as i figured out a way to do this. What i did, was removed ORDER BYfrom both queries and created a new query which concatenated the two with UNION:
SELECT * FROM (
SELECT *
FROM tourneyplayers as tp1
INNER JOIN results as r1
on (tp1.resultid=r1.r_id)
INNER JOIN players as p1
ON (p1.p_id=tp1.playerid)
WHERE tp1.tourneyid=96 and
tp1.in_day2 = 1 and
r1.day2_semifinal IS NOT NULL
GROUP BY r1.day2_semifinal
UNION ALL
SELECT *
FROM tourneyplayers as tp2
INNER JOIN results as r2
on (tp2.resultid=r2.r_id)
INNER JOIN players as p2
ON (p2.p_id=tp2.playerid)
WHERE tp2.tourneyid=96 and
tp2.in_day2 = 1 and
r2.day2_additional_nosemi IS NOT NULL AND
r2.day2_additional_nosemi <> 0
GROUP BY r2.day2_additional_nosemi
) t ORDER BY t.agegroupid;

MySQL several inner joins vs subquery

i have a problem with making a complicated query easier without the need to build the query with PHP.
My problem:
A product can have several properties eg. a color, a size and a state.
To get a product which has all 3 properties i can:
products p
INNER JOIN product_propeties p1 on p.pid = p1.pid AND p1.property = 1 (color)
INNER JOIN product_propeties p2 on p.pid = p2.pid AND p2.property = 2 (size)
INNER JOIN product_propeties p3 on p.pid = p3.pid AND p3.property = 3 (state)
This works fine. I will get all products which have all this 3 properties.
My problem is now that i dont want to generate p1,p2,p3 with PHP. The properties are listed in a table "property_groups". In this table i can group properties.
proberty|title|group_name
1|color|winterspecial
2|size|winterspecial
3|state|winterspecial
I want to join the "property_groups" table with "winterspecial" and my example from above i dont know how. Problem is that each property needs to exists. Several single joins do the job. But how to do it in a single MySQL Query.
With PHP i select all "winterspecial" and then i build the query with p1,p2...
There must be a better way. Beware that the properties must be AND connectet.
OR is easy this would be a simple subselect.
INNER JOIN product_propeties p1 on p.pid = p1.pid AND product_propeties IN (
SELECT * FROM property_groups WHERE "winterspecial"
)
This may look a bit clumsy, but is the only thing I can come up with at the moment...
In order to know whether a product has all winterspecial properties, we could count all existing winterspecial properties and the product's winterspecial properties and then compare the two numbers.
Then we can select from products and product_properties where the product ID is in the found set:
select ...
from products p
join product_properties pp on pp.pid = p.pid and pp.property in
(select property from property_groups where group_name = 'winterspecial')
where p.pid in
(
select pid
from product_properties
where property in
(select property from property_groups where group_name = 'winterspecial')
group by pid
having count(*) =
(select count(*) from property_groups where group_name = 'winterspecial')
);
Ahhh, I think I get the problem now. You seem to want all products that have the properties in the property_groups table, for a given group.
Here is an approach. Do a cross join to generate the list of products and properties. Then do a left join to match to product_properties. With an aggregation, you can easily tell if all the desired properties match an existing property:
select p.*
from products p cross join
property_groups g left join
product_properties pp
on pp.pid = p.id and pp.property = g.property
where g.group_name = 'winterspecial'
group by p.id
having count(distinct pp.property) = count(distinct g.property)
You can actually simply the having clause to one of these:
having sum(pp.property is null) = 0 -- no null values
having count(pp.property) = count(g.property) -- all match
These should all be equivalent.

mysql query dont gets all posts

I have a MYSQL query who have to list all post i want it to post. But it dont do it. It shows posts when i have more then one post in the table "meaOrder" with the same "ordCode". But when i have only on post in meaOrder, i don't show it. What can i do?
SELECT koden, wish, rnamn, bild, pris, cname, onsktext
FROM (
SELECT m.wishText as onsktext, m.meaOID as midn, m.ordcode as koden, w.wish as wish, r.meaName as rnamn, r.meaImg as bild,
r.meaPrice as pris, k.catName as cname from cats k, meals r, wishes w,
meaOrder m
join orders c on c.ordNR=4401
WHERE c.ordStatus=1 AND m.ordNR=c.ordNR AND m.meaID=r.meaID AND m.wishesID=w.id
AND r.catID=k.catID
) T
GROUP BY koden, rnamn, bild, pris, cname
ORDER BY midn DESC
TABLE orders
http://grab.by/m74E
TABLE meaOrder http://grab.by/m74Q
Try replacing the JOIN with RIGHT JOIN in this case. The difference is explained at JOIN Syntax page in MySQL docs . In short - JOIN returns row only if there are corresponding rows in both joined tables (inner join). LEFT JOIN / RIGHT JOIN return all rows from one of the tables and corresponding row if it exists from the other table (those are outer joins)
Do you need a subselect?
This seems to cover it:-
SELECT m.ordcode AS koden, w.wish AS wish, r.meaName AS rnamn, r.meaImg AS bild, r.meaPrice AS pris, k.catName AS cname, m.wishText AS onsktext
FROM cats k
INNER JOIN meals r ON r.catID = k.catID
INNER JOIN meaOrder m ON m.meaID = r.meaID
INNER JOIN wishes w ON m.wishesID = w.id
INNER JOIN orders c ON m.ordNR = c.ordNR
WHERE c.ordStatus = 1
AND c.ordNR = 4401
GROUP BY m.ordcode, r.meaName, r.meaImg, r.meaPrice, k.catName
ORDER BY midn DESC

Reducing mySQL queries and time

I am currently working on speeding up a website, that is returning 300,000+ rows from a query. While I don't think this is too much of a load on the DB server, this query is happening in a while loop depending on the number of 'galleries' a user has.
For example Joe has 10 galleries in his account. Each of those galleries has x number of images, which have x number of comments on those images. So the query that is currently being run...
SELECT count(*) as total
FROM galleryimage a
INNER JOIN imagecomments b ON a.id=b.imgId
WHERE a.galleryId='".$row['id']."'
AND b.note <> ''
...is looking through all the galleryimage table 334,000 rows and the imagecomments table 76,000 rows and returning the result on each gallery. The query run on a single gallery returns a result in about 578ms, but with many galleries, say 30-40 you could be looking at a page load time of 17+ secs. Any suggestions on how to deal with this issue?
I cannot change the DB architecture....
Query for gallery id
SELECT a.id,
a.created,
a.name,
b.clientName,
a.isFeatured,
a.views,
a.clientId
FROM gallery a
INNER JOIN client b
ON a.clientId = b.id
WHERE a.isTemp = 0
AND a.clientRef = '{$clientRef}'
AND a.finish='1'
AND a.isArchive='0'
ORDER BY created
DESC
You can consolidate the queries and eliminate the need for looping:
SELECT
a.id,
a.created,
a.name,
b.clientName,
a.isFeatured,
a.views,
a.clientId,
COALESCE(c.img_cnt, 0) AS gallery_image_count,
COALESCE(c.comment_cnt, 0) AS gallery_comment_count
FROM
gallery a
INNER JOIN
client b ON a.clientId = b.id
LEFT JOIN
(
SELECT aa.galleryId,
COUNT(DISTINCT aa.id) AS img_cnt,
COUNT(1) AS comment_cnt
FROM galleryimage aa
INNER JOIN imagecomments bb ON aa.id = bb.imgId
WHERE bb.note <> ''
GROUP BY aa.galleryId
) c ON a.id = c.galleryId
WHERE
a.isTemp = 0 AND
a.clientRef = '{$clientRef}' AND
a.finish = 1 AND
a.isArchive = 0
ORDER BY
a.created DESC

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