PHP "Simple" query takes massive time - php

I have a question about performance, I guess that I am doing something wrong.
I have one table with Items, and another one with Categories, any item can be in multiple categories. Now I want to show all Items with are not included in any category.
Connections are in table Items_Categories (just with ID_item and ID_category)
I made this query:
SELECT *
FROM Items AS i
WHERE ID_item NOT
IN (
SELECT DISTINCT ID_item
FROM Items_Categories
)
It works, but it's very slow. It takes a few seconds, and my DB is not so big. I have about 3000 Items and maybe 200 categories.
Any better solutions?

You can probably use a JOIN instead and find the ones that don't have any matching.
SELECT i.*
FROM items i
LEFT JOIN Items_Categories ic
ON i.ID_item = ic.ID_Item
WHERE
ic.ID_Item IS NULL

Try this way:
SELECT i.*
FROM Items AS i
LEFT JOIN Items_Categories IC on IC.ID_item = I.ID_item
where IC.ID_item is null

You could take this approach and check the execution plan of yours and mine with EXPLAIN, as Mark Baker recommended:
SELECT
*
FROM
Items i
WHERE NOT EXISTS (
SELECT 1 FROM Items_Categories WHERE ID_item = i.ID_item
);
You should have indexed the ID_item column in both tables of course.

Related

Combining Two Select Queries MySQL

I have two tables : items and comments.
I want to select all items which a user commented on.
For simplicity, lets assume the items table has two columns : item_id and item_content. Let the comments table have 3 columns user_id, item_id and comment_content.
I am given the user_id of the commenting user, I need to first select all the item_id from the comments table, where user_id = myUserId.
This is a basic query SELECT item_id FROM comments WHERE user_id = '$myUserId'.
Then I need to select the item_content for each item_id returned by the previous query.
I was thinking of doing a while($row = $my_first_query->fetch_array()) loop, and inside of it doing something like SELECT item_content FROM item WHERE item_id = $row["item_id"]
however this is a bit messy and I was wondering if there was a simpler way of doing this, by combining the two queries into one.
Use an INNER JOIN:
SELECT t1.*
FROM items t1
INNER JOIN comments t2
ON t1.item_id = t2.item_id
WHERE t2.user_id = myUserId
The approach you suggested of first querying the comments table and then looping over the result set is inefficient. In a join, MySQL can handle this algebra much faster than your PHP code.
Depending of what you want to do exactly, you can just use a JOIN clause. The "header" table info will be found within all rows, so it might not be what you want to do.
Another way would be to run two distinct queries, the first one unchanged and the second one with a join. You would then have one result with the header, and the other with all the details that you could go through. It's more performant than run the same query over and over network wise.

Get a picture for each album

I have two table for gallery system :
gallery_cat(
gallery_cat_id PK,
gallery_cat_name
)
gallery(
gallery_id PK,
gallery_cat_id FK,
gallery_name,
gallery_file_name,
gallery_date
)
I need to write a SQL query that return one picture from gallery table for each album, the purpose of this that I need to list the albums with one picture for each.
gallery_name | gallery_cat_name| gallery_file_name
-------------+-----------------+------------------
pic1 | Album1 | pic1.jpg
This should do the trick:
SELECT g2.gallery_name, gc2.gallery_cat_name, g2.gallery_file_name
FROM gallery g2
INNER JOIN gallery_cat gc2 ON (g2.gallery_cat_id = gc2.gallery_cat_id)
WHERE g2.gallery_id IN (
SELECT g.gallery_id
FROM gallery g
GROUP BY g.gallery_cat_id)
Explanation:
At the end is a sub-select
IN (
SELECT g.gallery_id
FROM gallery g
GROUP BY g.gallery_cat_id) <<-- select 1 random g.id per gallery_cat.
Here I select all g.id, but because of the group by clause it will reduce the results to 1 row per grouped by item. I.e. 1 row (chosen more or less at random) per g.gallery_cat_id.
Next I do a normal select with a join:
SELECT g2.gallery_name, gc2.gallery_cat_name, g2.gallery_file_name
FROM gallery g2
INNER JOIN gallery_cat gc2 ON (g2.gallery_cat_id = gc2.gallery_cat_id)
WHERE g2.gallery_id IN (
Because I refer to the same table twice in the same query you have to use an alias(*).
I select all names and all catnames and all filenames.
However in the where clause I filter these so that only rows from the sub-select are shown.
I have to do it this way, because the group by mixes rows into one messed up ow, if I select from that directly I will get values from different rows mixed together, not a good thing.
By first selecting the id's I want and then matching full rows to those id I prevent this from happening.
*(in this case with this kind of subselect that's not really 100% true, but trust me on the point that it's always a good idea to alias your tables)
This attempts to select the most recent gallery_date for each category ID and join against gallery_cat
SELECT
c.gallery_cat_id,
c.gallery_cat_name,
i.lastimg
FROM
gallery_cat c
LEFT JOIN (
SELECT gallery_cat_id, gallery_filename AS lastimg, MAX(gallery_date)
FROM gallery
GROUP BY gallery_cat_id, gallery_filename
) i ON c.gallery_cat_id = i.gallery_cat_id
You can use SQL JOINS to do this, otherwise you would have to loop out all the albums and pick one random picture from each which would be less efficient.

inner join large table with small table , how to speed up

dear php and mysql expertor
i have two table one large for posts artices 200,000records (index colume: sid) , and one small table (index colume topicid ) for topics has 20 record .. have same topicid
curent im using : ( it took round 0.4s)
+do get last 50 record from table:
SELECT sid, aid, title, time, topic, informant, ihome, alanguage, counter, type, images, chainid FROM veryzoo_stories ORDER BY sid DESC LIMIT 0,50
+then do while loop in each records for find the maching name of topic in each post:
while ( .. ) {
SELECT topicname FROM veryzoo_topics WHERE topicid='$topic'"
....
}
+Now
I going to use Inner Join for speed up process but as my test it took much longer from 1.5s up to 3.5s
SELECT a.sid, a.aid, a.title, a.time, a.topic, a.informant, a.ihome, a.alanguage, a.counter, a.type, a.images, a.chainid, t.topicname FROM veryzoo_stories a INNER JOIN veryzoo_topics t ON a.topic = t.topicid ORDER BY sid DESC LIMIT 0,50
It look like the inner join do all joining 200k records from two table fist then limit result at 50 .. that took long time..
Please help to point me right way doing this..
eg take last 50 records from table one.. then join it to table 2 .. ect
Do not use inner join unless the two tables share the same primary key, or you'll get duplicate values (and of course a slower query).
Please try this :
SELECT *
FROM (
SELECT a.sid, a.aid, a.title, a.time, a.topic, a.informant, a.ihome, a.alanguage, a.counter, a.type, a.images, a.chainid
FROM veryzoo_stories a
ORDER BY sid DESC
LIMIT 0 , 50
)b
INNER JOIN veryzoo_topics t ON b.topic = t.topicid
I made a small test and it seems to be faster. It uses a subquery (nested query) to first select the 50 records and then join.
Also make sure that veryzoo_stories.sid, veryzoo_stories.topic and veryzoo_topics.topicid are indexes (and that the relation exists if you use InnoDB). It should improve the performance.
Now it leaves the problem of the ORDER BY LIMIT. It is heavy because it orders the 200,000 records before selecting. I guess it's necessary. The indexes are very important when using ORDER BY.
Here is an article on the problem : ORDER BY … LIMIT Performance Optimization
I'm just give test to nested query + inner join and suprised that performace increase much: it now took only 0.22s . Here is my query:
SELECT a.*, t.topicname
FROM (SELECT sid, aid, title, TIME, topic, informant, ihome, alanguage, counter, TYPE, images, chainid
FROM veryzoo_stories
ORDER BY sid DESC
LIMIT 0, 50) a
INNER JOIN veryzoo_topics t ON a.topic = t.topicid
if no more solution come up , i may use this one .. thanks for anyone look at this post

Searching multiple tables in query

SELECT DISTINCT business.name AS businessname
,business.description AS description
FROM business
, category
, sub_categories
WHERE business.cityID = '$city'
AND (category.name LIKE '%$name%'
OR sub_categories.name LIKE '%$name%')
AND business.status = 0
Pls the above SQL code is suppose to search a set of two tables the ones in the bracket and return the result, but for some reason, it's not doing so. What am i doing wrong?
Thank You.
Your query would produce a cartesian product. Depending on the size of your tables that could take a considerable amount of time.
Based on your clarification I'd use a subquery to check for matching categories, this way you don't have to use distinct in your query as it would only return each business once. I also suggest you to start with a decent SQL tutorial.
SELECT name AS businessname
,description AS description
FROM business
WHERE cityID = '$city'
AND status = 0
AND ( categoryID in (select id from category where name like '%$name%')
or subcategoryID in (select id from sub_categories where name like '%$name%')
)
Two things come to mind:
You are not joining any of the three tables together. Consider adding a few LEFT JOIN clauses.
You are selecting columns from only one table. If you wanted columns from other tables, you should add them to your SELECT clause.

INNER JOIN: limit 0,1 on the second table

I have 2 tables, one called "products" and one "images".
The table "images" hold the images of each products, so I can have 5 image per product.
I want to make a select that retrive only 1 image for each product. I'm new to joins so i dont know how to solve this.
I'm trying with:
SELECT *
FROM products
INNER JOIN images ON products.id=images.prod_id
WHERE products.cat='shoes'
I need to add a Limit 0,1 on images table. How I can do it?
Thanks in advance.
Maybe a subselect is a better solution here.
Something like this:
SELECT
productId,
productName,
(SELECT imageData FROM Image i WHERE i.productId = productId LIMIT 1) AS imageData
FROM Products
It's best to avoid subqueries because they are slow in mysql.
If you want to get any image associated to product,
you can do it in fast but not very nice way:
SELECT *
FROM products
INNER JOIN images ON products.id=images.prod_id
WHERE products.cat='shoes'
GROUP BY products.id
If you want to get a first image( by any criteria ), apply groupwise max techniques
Take a look at DISTINCT
The key here is correlated subqueries.
select
*
from
products p,
(
select
*
from
images i
where
i.prod_id = p.id
limit 1
) as correlated
where
p.cat = 'shoes'
SELECT * FROM products
LEFT JOIN images ON products.id=images.prod_id
WHERE products.id='1' LIMIT 1
This will return the first image found for your product and all the product details.
If you want to retieve multiple products then I would suggest doing 2 queries.
SELECT product data
Loop through product data {
SELECT image data LIMIT 1
}
Doing complex single queries can quite often end up being more expensive than a couple/few smaller queries.

Categories