SELECT products.*
FROM products
LEFT JOIN product_category
ON products.id_product = product_category.id_produs
LEFT JOIN categories
ON product_category.id_category = categories.id_category
INNER JOIN options
ON products.id_product = options.id_product
WHERE categories.id_category = '472'
AND product_category.id_category = categories.id_category
LIMIT 0, 20
Hello, I'm working on a ecommerce website for a programming contest and I have some issues with the category page, where the products will be listed.
In MySQL there are 4 tables: products, categories, product_category (which contains the relation of category and products) and options. The options table contains various attributes for a product that you can choose to buy (like weight, height, color). I need to call these on the category page because there is a product filter, and when I chose color: red, it will show all the products with the attribute color: red.
If I have 10 000 rows in products table, and each product have 5 options, so the options table will have 50 000 rows, the problem is that the query will be very slow, and it will take more than 30 seconds to display the products.
I'm sure the problem is with the query, but I don't how to make it. What should I write in the query in order to get the products on the page in under 5 seconds?
Make sure that you have indexes on id_category and id_product for those tables, then stick EXPLAIN in front of it, and post the output, to be honest, if you don't have indexes, that might do the job anyway.
A few things: You're specifying this relationship twice:
AND product_category.id_category = categories.id_category
and
ON product_category.id_category = categories.id_category
This may be confusing your database optimizer.
Another potential optimization is to move the column you are doing your WHERE clause on up a table. Turn WHERE categories.id_category = '472' into product_category.id_category = '472'
` and it will reduce the number of records being joined in.
As mentioned in some of the questions, an EXPLAIN plan will show you what part of your query is slow, and give you an indication if there are indexes missing.
Related
I've got two tables:
products_tb - holds set brief information about a certain product which isn't going to change
marketplace_tb - holds all records of listings, linking to the products_tb on Product_ID
I have a query where I am trying to get all records of data where the marketplace is a certain country (GB / US etc.) and the category is the type of product (Book, Laptop etc.)
It works, however as I am inner joining two tables based on a certain ID (Product_ID) it's only outputting 1 record per individual product_id, is there a way to get it to output all records of data regardless if its got the same product listing? (as the same product could be listed multiple times under different prices for examples)
Thanks for your help guys.
SELECT * FROM products_tb as a
INNER JOIN marketplace_tb as b ON a.Product_ID = b.Product_ID
WHERE Product_Category_p = '$category'
AND b.Country_Name_m = '$country'
It turns out the query was correct. The method for inputting the Product_IDs was incorrect and it was inputting the same ID (8, which was the last row) regardless of which product i chose which meant the right product didnt show up under the category.
Thanks a lot for your great help.
This is an issue that I've deemed impractical to implement but I would like to get some feedback to confirm.
I have a product and users database, where users can like products, the like data is stored in a reference table with just pid and uid.
The client request is to show 3 users who have liked every product in the product listing.
The problem is, its not possible to get this data in one query for the product listing,
How I once implemented and subsequently un-implemented it was to perform a request for the users who have liked the products during the loop through the product list.
ie.
foreach($prods as $row):
$likers = $this->model->get_likers($row->id);
endforeach;
That works, but obviously results in not only super slow product listings, and also creates a big strain on the database/cpu.
The final solution that was implemented was to only show the latest user who has liked it (this can be gotten from a join in the products list query) and have a link showing how many people have liked, and upon clicking on it, opens a ajax list of likers.
So my question is, is there actually a technique to show likers on the product list, or is it simply not possible to execute practically? I notice actually for most social media sites, they do not show all likers on the listings, and do employ the 'click to see likers' method. However, they do show comments per items on the listing, and this is actually involves the same problem doesn't it?
Edit: mock up attached on the desired outcome. there would be 30 products per page.
By reading your comment reply to Alex.Ritna ,yes you can get the x no. of results with per group ,using GROUP_CONCAT() and the SUBSTRING_INDEX() it will show the likers seperated by comma or whatever separator you specified in the query (i have used ||).ORDER BY clause can be used in group_concat function.As there is no schema information is available so i assume you have one product table one user table and a junction table that maintains the relation of user and product.In the substring function i have used x=3
SELECT p.*,
COUNT(*) total_likes,
SUBSTRING_INDEX(
GROUP_CONCAT( CONCAT(u.firstname,' ',u.lastname) ORDER BY some_column DESC SEPARATOR '||'),
'||',3) x_no_of_likers
FROM product p
LEFT JOIN junction_table jt ON(p.id=jt.product_id)
INNER JOIN users u ON(u.id=jt.user_id)
GROUP BY p.id
Fiddle
Now at your application level you just have to loop through the products and split the x_no_of_likers by separator you the likers per product
foreach($prods as $row):
$likers=explode('||',$row['x_no_of_likers']);
$total_likes= $row['total_likes'];
foreach($likers as $user):
....
endforeach;
endforeach;
Note there is a default 1024 character limit set on GROUP_CONCAT() but you can also increase it by following the GROUP_CONCAT() manual
Edit from comments This is another way how to get n results per group, from this you can get all the fields from your user table i have used some variables to get the rank for product group ,used subquery for junction_table to get the rank and in outer select i have filtered records with this rank using HAVING jt.user_rank <=3 so it will give three users records per product ,i have also used subquery for products (SELECT * FROM product LIMIT 30 ) so the first 30 groups will have 3 results for each,for below query limit cannot be used at the end so i have used in the subquery
SELECT p.id,p.title,u.firstname,u.lastname,u.thumbnail,jt.user_rank
FROM
(SELECT * FROM `product` LIMIT 30 ) p
LEFT JOIN
( SELECT j.*,
#current_rank:= CASE WHEN #current_rank = product_id THEN #user_rank:=#user_rank +1 ELSE #user_rank:=1 END user_rank,
#current_rank:=product_id
FROM `junction_table` j ,
(SELECT #user_rank:=0,#current_rank:=0) r
ORDER BY product_id
) jt ON(jt.product_id = p.id)
LEFT JOIN `users` u ON (jt.`user_id` = u.`id`)
HAVING jt.user_rank <=3
ORDER BY p.id
Fiddle n results per group
You should be able to get a list of all users that have liked all products with this sql.
select uid,
count(pid) as liked_products
from product_user
group by uid
having liked_products = (select count(1) from products);
But as data grows this query gets slow. Better then to maintain a table with like counts that is maintained through a trigger or separately. On every like/dislike the counter is updated. This makes it easy to show the number of likes for each product. Then if the actual users that liked that product is wanted do a separate call (on user interaction) that fetches the specific likes for one product). Don't do this for all products on a page until actually requested.
I am assuming the size of both these tables is non-trivially large. You should create a new table (say LastThreeLikes), where the columns would be pid,uid_1,uid_2 and uid_3, indexed by pid. Also, add a column to your product table called numLikes.
For each "like" that you enter into your reference table, create a trigger that also populates this LastThreeLikes table if the numLikes is less than 3. You can choose to randomly update one of the values anyway if you want to show new users once in a while.
While displaying a product, simply fetch the uids from this table and display them back.
Note that you also need to maintain a trigger for the "Unlike" action (if there is any) to re-populate the LastThreeLikes table with a new user id.
Problem
The problem is the volume of data. From the point of view that you need two integer value as a answer you should forget about building a heavy query from your n<->n relations table.
Solution
Generates a storable representation using the file_put_contents() with append option each time a user likes a product. I don't have enough room to write the class in here.
public function export($file);
3D array format
array[product][line][user]
Example:
$likes[1293][1][456]=1;
$likes[82][2][656]=1;
$likes[65][3][456]=1;
.
.
.
Number of users who like this particular product:
$number_users_like_this_product = count($likes[$idProduct]);
All idUser who like this particular product:
$users_like_this_product = count($likes[$idProduct][$n]);
All likes
$all_likes = count($likes);
Deleting a like
This loop will unset the only line where $idProduct and $IdUser you want. Since all the variables are unsigned integer it is very fast.
for($n=1, $n <= count($likes[$idProduct]), $n++)
{
unset($likes[$idProduct][$n][$idUser]);
}
Conclusion
Get all likes will be easy as:
include('likes.php');
P.S If you want to give a try i will be glad to optimize my stuff and share it. I've created the class in 2012.
I am building an auction website. Right now, I am building the item description page, that has item details, as well as current bid history. My bids table has a FK of Item_id.
My current query looks something like this:
SELECT bids.Item_id, bids.User_email, bids.Bid_amount, products.*
FROM bids
INNER JOIN products
ON bids.Item_id=products.Item_id;
This returns all of the bid information I need - but also returns the item description for every bid row. I only need the product information once. Is it best to just use two queries on this?
Any help is appreciated
If you need the bids data separately from the products data, then you should use two queries.
One query cannot really be arrange to return different columns for different rows.
SELECT b.Item_id, b.User_email, b.Bid_amount, p.*
FROM bids b
INNER JOIN products p
ON b.Item_id=p.Item_id
WHERE p.Item_id=something;
This will not repeat products..
I have a quick question in regards to the approach to displaying products on an e-commerce package I am putting together. The problem that I facing is that I would like visitors of the site narrow their search.
For example, a use case would be:
A Visitor is currently browsing a season of products (eg. Summer Collections)
He / She should then be able to filter by category and brand within that season, so for example they might decide that they only want to see Pants from Clothes Galore.
The problem, I ma facing is that doing a single SQL query in order to find products that match all three of these factors (so the product is in the summer collection, is a pair of pants and made by clothes galore). The thing that makes this overly difficult is that products can be in multiple categories and seasons. So there would need to be an insane amount joins in order to grab the right result.
My database structure is as follows:
product -> product_category <- category
product -> product_season <- season
product <- brand (a product can only be made by one brand)
Hope someone can share their wisdom on this...
If you have a big catalog, you might be better to use Apache Solr ( http://lucene.apache.org/solr/ ). Otherwise, there are a few approaches.
You don't need the overhead and straight SQL isn't that insane, will perform reasonably well:
SELECT product.*
FROM product
LEFT JOIN product_category
LEFT JOIN category
LEFT JOIN product_season
LEFT JOIN season
WHERE season = ? AND category = ?
Alternatively, If you don't like the number of rows returned, you can aggregate the category and season into the product table (new columns), then use like queries to find things:
SELECT product.*
FROM product
where product.categories like '% category %'
and product.seasons like '% season %'
Select product.* From product
Left Join product_category On (product.product_id = product_category.product_id)
Left Join product_season On (product.product_id= product_season.product_id)
Left Join brand On (product.product_id = brand.product_id)
Where product_category.category_id = '$catId'
And product_season.season_id = '$seasonId'
And brand.brand_id = '$brandId'
Order By product.product_name
Ok, this is assuming product, product_category, product_season, brand, season, category are all tables. You should pass in the ID's of the season and category table directly from whatever you are using to filter the search by so that you don't have to join these tables.
this site is like an ecommerce site and i have two tables that i'm stuck on.
tbl_products
---------------
phash
product
price
desc
maxcount
tbl_product_images
-------------------
phash
thumb
large
tbl_products stores the products description
tbl_product_images stores the path of the thumb and large image of eac product.
phash is an md5 of the products name its what i use rather than the id or product name when matching things together.
what i'm having throuble with is say tbl_products has one record for a product and tbl_product_images
in relation to that product has 5 rows for its images.
how would i run the query?
$sql = "select
tbl_products.phash
tbl_products.product
tbl_products.price
tbl_products.desc
tbl_products.maxcount
tbl_product_images.phash
tbl_product_images.thumb
tbl_product_images.large
from tbl_products
inner join tbl_product_images
on tbl_products.phash = tbl_products_images.phash";
this will display 5 rows of records since tbl_product_images has 5 records.
the way i output is my traditional way
$query = $db->query("$sql");
while($row = $db->fetch($query))
{
....
}
i am really unsure how i'm going to do this, if you need more explanation to what i'm trying to explain please let me know. thanks
The simplest way is to limit the number of results coming from the images table - though that's only appropriate if you don't care which image is returned for each product:
select
tbl_products.phash
tbl_products.product
tbl_products.price
tbl_products.desc
tbl_products.maxcount
tbl_product_images.phash
tbl_product_images.thumb
tbl_product_images.large
from tbl_products
inner join (SELECT * FROM tbl_product_images LIMIT 1) images
on tbl_products.phash = images.phash
You can add an ORDER BY [col] DESC to that LIMITed query if there's some other column in the images table (like a date) you could use to determine which image appears.
(Note: If there were some column in tbl_product_images that determined which image to return, you could either put that in a WHERE clause on the query, or include it in the join criteria, like: ON tbl_products.phash = tbl_product_images.phash AND tbl_product_images.color = 'Blue')
Further, if you are trying to get a product with all of its images, but only want one row in the resultset, it may be possible to "pivot" the set of images into columns. However, this is tricky if you can have an arbitrary (or simply large) number of images per product. You may be better off just returning multiple rows per product, and handling the results in your application code...