I have three tables that are structured like this:
http://i41.tinypic.com/2bt9aq.png
What I am trying to do is retrieve the joke id, title, and average rating of all jokes in a certain category and order them alphabetically. I have this query:
$result = mysql_query("
SELECT jokedata.id AS joke_id,
jokedata.joketitle AS joke_title,
SUM(ratings.rating) / COUNT(ratings.rating) AS average
FROM jokedata
INNER JOIN ratings ON ratings.content_type = 'joke' AND ratings.relative_id = jokedata.id
WHERE jokecategory = '$cur_category'
GROUP BY jokedata.id
ORDER BY jokedata.joketitle
LIMIT $offset, $jokes_per_page
");
However it is not selecting any jokes.
What is wrong with that query? Thankyou.
First, you probably want to use AVG() instead of SUM()/COUNT().
Your problem is the inner join - if no ratings where submitted for a joke then that joke would not be returned as only jokes with a rating value match the inner join.
I would recommend using a left join instead, or even a sub-select. While I normally prefer JOINs as they are usually faster, I would have tried something like this:
SELECT id AS joke_id,
joketitle AS joke_title,
(
SELECT AVG(rating) AS avgrating FROM ratings
WHERE content_type = 'joke' AND relative_id = joke_id
GROUP BY relative_id
) AS average
FROM jokedata
WHERE jokecategory = '$cur_category'
GROUP BY id
ORDER BY joketitle
LIMIT $offset, $jokes_per_page
An inner join will not return a row if one of the joins cannot be fulfilled. So odds are good that the criteria ratings.relative_id = jokedata.id is causing the query to return 0 jokes. Try replacing the INNER JOIN with LEFT JOIN and you'll see how many of the jokedata's rows don't have matching id's in ratings.relative_id.
I would first narrow down the problem by taking out everything except the join and seeing what the result is:
SELECT *
FROM jokedata
INNER JOIN ratings ON ratings.content_type = 'joke' AND ratings.relative_id = jokedata.id
If this gives you results, I would then add back in the WHERE criteria. Do this step by step, and whenever you get a query where you have no rows returned, then look at the previous set of results, and think about what is happening when you add the additional criteria. Being able to see this "intermediate" set of results will help you identify and understand the problem.
Continue adding back to the query piece-wise until you start getting no results, at least then you've narrowed down to what aspect of the query is causing the problem.
Related
Here is my query so far:
SELECT
b.cs_bidding_id, b.cs_bidding_user_id,
floor(AVG(u.cs_rating)) AS cs_user_rating
FROM cs_biddings b LEFT JOIN
cs_user_ratings u ON u.cs_user_rated_id = b.cs_bidding_user_id
I would like to get the avg rating of the user's per bidding post.
However this does not work for multiple biddings because whenever the join condition is satisfied, it wont let me fetch other avg rating for other biddings that shares the same bidding_user_id
desired result:
Unsummarized Query:
SELECT
b.cs_bidding_id,
b.cs_bidding_title,
b.cs_bidding_details,
b.cs_bidding_user_id,
b.cs_bidding_permalink,
b.cs_bidding_added,
b.cs_bidding_picture,
b.cs_bidding_status,
b.cs_bidding_location,
floor(AVG(u.cs_rating)) AS cs_owner_rating
FROM cs_biddings b LEFT JOIN
cs_user_ratings u ON u.cs_user_rated_id = b.cs_bidding_user_id
Your query is malformed. It is an aggregation query (because of the AVG()) but the SELECT columns are inconsistent with the aggregation columns (well, there are none of those).
Fixing the group by might fix your problem:
SELECT b.cs_bidding_id, b.cs_bidding_user_id,
floor(AVG(u.cs_rating)) AS cs_user_rating
FROM cs_biddings b LEFT JOIN
cs_user_ratings u
ON u.cs_user_rated_id = b.cs_bidding_user_id
GROUP BY b.cs_bidding_id, b.cs_bidding_user_id;
I'm not sure if you want both columns in the GROUP BY (and the result set). However, without sample data and desired results, it is unclear what you actually intend.
well, I want to get the best selling product, but when I want to get them. it just return the best selling product, but not idventa,idproduct,and description. well return the values but they are wrong values which they belong to other one. I need someone help me to correct me statement on sql because I want to return the right values like: idventa: 7 - idproducto: 10 , descripcion: IPHONE 4S, best_selling_product: 5000, and not the other values which they are not belong to the sentence
SELECT
idventa,idproducto,descripcion,MAX(venta_detalle.cantidad) AS best_selling_product
FROM venta_detalle
INNER JOIN producto
ON venta_detalle.idproducto = producto.id
Without knowing your table structures, it's a little difficult to guess what you're looking for. Sometimes you can get away with adding a group by clause assuming distinct values exist for that field.
Other times you need to join the table back to itself using aggregation in a subquery:
select
p.idproducto,
p.descripcion,
vd.idventa,
vd.cantidad
from producto p
join venta_detalle vd on vd.idproducto = p.id
join (select idproducto, max(cantidad) best_selling_product
from venta_detalle
group by idproducto) vd2 on vd.idproducto = vd2.idproducto and
vd.cantidad = vd2.best_selling_product
Although max() will return the highest number and without a group by it will also collapse the resultset into a single record, however, there is no guarantee that the other fields will come from the same record as the max. I suggest a simple order by and limit combo to get the record with the highest value:
SELECT
idventa,idproducto,descripcion, venta_detalle.cantidad AS best_selling_product
FROM venta_detalle
INNER JOIN producto
ON venta_detalle.idproducto = producto.id
ORDER BY venta_detalle.cantidad DESC
LIMIT 1
I have this problem that when i run this one.
$results = mysqli_query($connecDB,"SELECT COUNT(*) FROM customers
RIGHT JOIN orders on customers.serial=orders.serial
RIGHT JOIN order_detail on orders.serial=order_detail.orderid
LEFT JOIN inventory on order_detail.productid=inventory.prod_id");
$get_total_rows = mysqli_fetch_array($results); //total records
it shows additional pages even if it's blank. i've tried on other tables that doesnt require me to join and it works on them. But with this one, it does not work perfectly.
How do i fix my count function?
here is my original query before tweaking the pagination codes.
SELECT DISTINCT customers.order_status,customers.serial,customers.name,customers.address,customers.phone,customers.email,customers.payment,customers.carrier,customers.tracking_no, orders.date, order_detail.productid, order_detail.quantity, order_detail.price, inventory.prod_name
FROM customers
RIGHT JOIN orders on customers.serial=orders.serial
RIGHT JOIN order_detail on orders.serial=order_detail.orderid
LEFT JOIN inventory on order_detail.productid=inventory.prod_id
GROUP BY orders.date, order_detail.orderid
ORDER BY order_detail.orderid DESC";
To much to put in a comment:
There are three ways to get the total records for use with pagination.
Execute the query without the limit and get the number of records in the resultset. It maybe obvious that this isn't the way to go.
Execute the query with the limit and use SQL_CALC_FOUND_ROWS.
Replace the colums in the SELECT clause with COUNT(1) and fetch the (single) row.
In many situations the third is the best choice, however sometimes the second can out perform the third.
Finally, see this predicates
A product which isn't in inventory can't be ordered
An order can't be submitted by non-customers
I think inner joins are sufficient
I'm working on a custom forum system and I'm trying to figure out how to put a thread on the top of the list if a user posts in it.
I've got this for my query
SELECT
user_threads.threadID,
user_threads.title,
user_threads.uid,
user_threads.postDate,
thread_messages.posted
FROM
user_threads,
thread_messages
WHERE
parent = :parent
GROUP BY
user_threads.title
ORDER BY
thread_messages.posted
DESC
Which doesn't appear to be working. if I post in a new thread, it remains where it is on the list.
You need to join the tables by the threadID. Also, if you just want one row per thread, you need to use the date of the last post.
SELECT
user_threads.threadID,
user_threads.title,
user_threads.uid,
user_threads.postDate,
MAX(thread_messages.posted) AS last_post
FROM
user_threads
LEFT JOIN
thread_messages ON thread_messages.threadID = user_threads.threadID
WHERE
parent = :parent
GROUP BY
user_threads.threadID
ORDER BY
last_post DESC
I used LEFT JOIN so that threads will be shown even if they don't have anything in thread_messages. If this is not needed, you can use a regular JOIN (aka INNER JOIN).
You are missing the JOIN condition. Currently you are getting the cross product of user_threads and thread_messages. That means you got rows for each thread and post in the result.
Try adding something link this
FROM
user_threads
JOIN
thread_messages USING(threadID)
to get each thread only once in your result set.
Use aliases for the tables 'user_threads' and 'thread_messages' i.e.
SELECT
A.threadID,
A.title,
A.uid,
A.postDate,
B.posted
FROM
user_threads A,
thread_messages B
WHERE
parent = :parent
//also do it with 'parent' field if parent is a field of user_threads
//A.parent else thread_messages B.parent
GROUP BY
A.title
ORDER BY
B.posted
DESC
You need to tie the tables together. You can do this by adding something like user_threads.threadID = thread_messages.threadID (second column name is guessed and needs to be checked) to the WHERE or by rewriting the query like this:
SELECT
user_threads.threadID,
user_threads.title,
user_threads.uid,
user_threads.postDate,
thread_messages.posted
FROM
user_threads
JOIN
thread_messages ON thread_messages.threadID = user_threads.threadID
WHERE
parent = :parent
GROUP BY
user_threads.title
ORDER BY
thread_messages.posted
DESC
And also not, that you probably should replace
GROUP BY
user_threads.title
by something like
GROUP BY
user_threads.threadID
or
GROUP BY
user_threads.threadID, user_threads.title
as sooner or later your thread-titles may be reused while the ID should be "forever" unique. Also threadID will most probably lead to a faster query as the database only needs to compare numbers versus text of various lengths.
I'm still having problems understanding how to read, understand and optimize MySQL explain. I know to create indices on orderby columns but that's about it. Therefore I am hoping you can help me tune this query:
EXPLAIN
SELECT specie.id, specie.commonname, specie.block_description, maximage.title,
maximage.karma, imagefile.file_name, imagefile.width, imagefile.height,
imagefile.transferred
FROM specie
INNER JOIN specie_map ON specie_map.specie_id = specie.id
INNER JOIN (
SELECT *
FROM image
ORDER BY karma DESC
) AS maximage ON specie_map.image_id = maximage.id
INNER JOIN imagefile ON imagefile.image_id = maximage.id
AND imagefile.type = 'small'
GROUP BY specie.commonname
ORDER BY commonname ASC
LIMIT 0 , 24
What this query does is to find the photo with the most karma for a specie. You can see the result of this live:
http://www.jungledragon.com/species
I have a table of species, a table of images, a mapping table in between and an imagefile table, since there are multiple image files (formats) per image.
Explain output:
For the specie table, I have indices on its primary id and the field commonname. For the image table, I have indices on its id and karma field, and a few others not relevant to this question.
This query currently takes 0.8 to 1.1s which is too slow in my opinion. I have a suspicion that the right index will speed this up many times, but I don't know which one.
I think you'd go a great way by getting rid of the subquery. Look at the first and last rows of the "explain" result - it's copying the entire "image" table to a temporary table. You could obtain the same result by replacing the subquery with INNER JOIN image and moving ORDER BY karma DESC to the final ORDER BY clause:
SELECT specie.id, specie.commonname, specie.block_description, maximage.title,
maximage.karma, imagefile.file_name, imagefile.width, imagefile.height,
imagefile.transferred
FROM specie
INNER JOIN specie_map ON specie_map.specie_id = specie.id
INNER JOIN image AS maximage ON specie_map.image_id = maximage.id
INNER JOIN imagefile ON imagefile.image_id = maximage.id
AND imagefile.type = 'small'
GROUP BY specie.commonname
ORDER BY commonname ASC, karma DESC
LIMIT 0 , 24
The real problem is that there is no need to optimize MySQL explain. There is usually a query (or several queries) that you want to be efficient and EXPLAIN is a way to see if the execution of the query is going to happen as you expect it to happen.
That is you need to understand how the execution plan should look like and why and compare it with results of the EXPLAIN command. To understand how the plan is going to look like you should understand how indexes in MySQL work.
In the meantime, your query is a tricky one, since for efficient index using it has some limitations: a) simultaneous ordering and by a field from one table, and b) finding the last element in each group from another (the latter is a tricky task as itself). Since your database is rather small, you are lucky that you current query is rather fast (though you consider it slow).
I would rewrite the query in a bit hacky manner (I assume that there is at least one foto for each specie):
SELECT
specie.id, specie.commonname, specie.block_description,
maximage.title, maximage.karma,
imagefile.file_name, imagefile.width, imagefile.height, imagefile.transferred
FROM (
SELECT s.id,
(SELECT i.id
FROM specie_map sm
JOIN image i ON sm.image_id = i.id
WHERE sm.specie_id = s.id
ORDER BY i.karma DESC
LIMIT 1) as image_id
FROM specie s
ORDER BY s.commonname
LIMIT 0, 24
) as ids
JOIN specie
ON ids.id = specie.id
JOIN image as maximage
ON maximage.id = ids.image_id
JOIN imagefile
ON imagefile.image_id = ids.image_id AND imagefile.type = 'small';
You will need the following indexes:
(commonname) on specie
a composite (specie_id, image_id) on specie_map
a composite (id, karma) on image
a composite (image_id, type) on imagefile
Paging now should happen within the subquery.
The idea is to make complex computations within a subquery that operates with ids only and join for the rest of the data at the top. The data would be ordered in the order of the results of the subquery.
It would be better if you could provide the table structures and indexes. I came up with this alternative, it would be nice if you could try this and tell me what happens (I am curious!):
SELECT t.*, imf.* FROM (
SELECT s.*, (SELECT id FROM image WHERE karma = MAX(i.karma) LIMIT 1) AS max_image_id
FROM image i
INNER JOIN specie_map smap ON smap.image_id = i.id
INNER JOIN specie s ON s.id = smap.specie_id
GROUP BY s.commonname
ORDER BY s.commonname ASC
LIMIT 24
) t INNER JOIN imagefile imf
ON t.max_image_id = imf.image_id AND imf.type = 'small'