SELECT s.*,
u.username,
u.fullname,
c.title AS ctitle,
c.description AS cdescription,
sa.attention,
sp.popularity,
COUNT(DISTINCT f.id) AS favorites,
COUNT(DISTINCT st.id) AS stars,
COUNT(DISTINCT v.id) AS views
FROM shots s
INNER JOIN users u ON u.id = s.user_id
INNER JOIN categories c ON c.id = s.cat_id
LEFT OUTER JOIN(
SELECT shot_id, round(AVG(count),2) AS attention
FROM points
WHERE date > DATE_SUB(CURDATE(),INTERVAL 2 DAY)
GROUP BY shot_id
) sa ON sa.shot_id = s.id
LEFT OUTER JOIN(
SELECT shot_id, SUM(count) AS popularity
FROM points
GROUP BY shot_id
) sp ON sp.shot_id = s.id
LEFT OUTER JOIN favorites f ON f.shot_id = s.id
LEFT OUTER JOIN stars st ON st.shot_id = s.id
LEFT OUTER JOIN views v ON v.shot_id = s.id
**WHERE s.library = 1 AND sa.attention > 40
ORDER BY sa.attention DESC
LIMIT 0,50**
GROUP BY s.id
I can't use the sa.attention in a condition and for ordering. Why?
(I removed the marked part, and the query works!)
What do I have to change in my Query? And if you could give a explanation for it, that would be very nice!
You are negating your OUTER JOIN by putting that in your WHERE criteria. Move it to your JOIN and you'll get your NULL records back:
SELECT s.*,
u.username,
u.fullname,
c.title AS ctitle,
c.description AS cdescription,
sa.attention,
sp.popularity,
COUNT(DISTINCT f.id) AS favorites,
COUNT(DISTINCT st.id) AS stars,
COUNT(DISTINCT v.id) AS views
FROM shots s
INNER JOIN users u ON u.id = s.user_id
INNER JOIN categories c ON c.id = s.cat_id
LEFT OUTER JOIN(
SELECT shot_id, round(AVG(count),2) AS attention
FROM points
WHERE date > DATE_SUB(CURDATE(),INTERVAL 2 DAY)
GROUP BY shot_id
) sa ON sa.shot_id = s.id AND sa.attention > 40
LEFT OUTER JOIN(
SELECT shot_id, SUM(count) AS popularity
FROM points
GROUP BY shot_id
) sp ON sp.shot_id = s.id
LEFT OUTER JOIN favorites f ON f.shot_id = s.id
LEFT OUTER JOIN stars st ON st.shot_id = s.id
LEFT OUTER JOIN views v ON v.shot_id = s.id
WHERE s.library = 1
GROUP BY s.id
ORDER BY sa.attention DESC
LIMIT 0,50
A second note, GROUP BY cannot go at the end. I moved that to the correct spot as well.
Related
I am trying to get a count of the number of logins during a given timeframe, currently my SQL query displays only results that had at least one login, I'd like it to display even those which have zero logins.
Query i'm using:
SELECT c.FullName, COUNT(l.Id)
FROM LoginsTable l JOIN UsersTable u ON u.Email = l.Email JOIN Organisations c ON c.Id = u.OrganisationId
WHERE l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1
GROUP BY c.Name
ORDER BY c.Name ASC;
You have a few issues. Firstly, you either need to use a RIGHT JOIN from LoginsTable or reorder the JOINs to put the JOIN to LoginsTable last and use a LEFT JOIN. Given the nature of your query the latter probably makes more sense.
Secondly, you need to put any conditions on fields from a table which has been LEFT JOINed into the join condition, otherwise MySQL converts the LEFT JOIN into an INNER JOIN (see the manual). Finally, you should GROUP BY the same fields as specified in your SELECT. This should work:
SELECT c.FullName, COUNT(l.Id)
FROM Organisations c
JOIN UsersTable u ON u.OrganisationId = c.Id
LEFT JOIN LoginsTable l ON u.Email = l.Email AND l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1
GROUP BY c.FullName
ORDER BY c.FullName
I found 2 issues here:
your group by column is not listed on your column
date condition is using double quotes.
try below query.
SELECT c.FullName, COUNT(l.Id)
FROM LoginsTable l
LEFT JOIN UsersTable u ON u.Email = l.Email
LEFT JOIN Organisations c ON c.Id = u.OrganisationId
WHERE l.AttemptTime between '2019-10-01' AND '2019-11-01' AND l.Success = 1
GROUP BY c.FullName
ORDER BY c.FullName ASC;
As Roman Hocke said you need to use left join as below :
SELECT c.FullName, COUNT(l.Id)
FROM UsersTable u
JOIN Organisations c ON c.Id = u.OrganisationId
LEFT JOIN LoginsTable l ON u.Email = l.Email
WHERE l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1
GROUP BY c.Name
ORDER BY c.Name ASC;
Moreover, you should fix your group by or select using the same field : SELECT c.Name or GROUP BY c.FullName ORDER BY c.FullName
EDIT : Nick's answer is the one. As he said perfectly well, you need to put your conditions in the on clause of your left join.
SELECT c.FullName, COUNT(l.Id)
FROM UsersTable u
JOIN Organisations c ON c.Id = u.OrganisationId
LEFT JOIN LoginsTable l ON (u.Email = l.Email AND l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1)
GROUP BY c.FullName
ORDER BY c.FullName ASC;
I have total 6 tables in which different info has been saved
Now i need a result in which get count from 5 tables and select all info from main table but if record does not exist than it must be need to return 0 instead of no row found that's the problem here
I have tried below query but didn't get success
SELECT
u.*,
COUNT(DISTINCT c.id) as comments,
COUNT(DISTINCT d.id) as dislikes,
COUNT(DISTINCT l.id) as likes,
COUNT(DISTINCT s.id) as shares,
COUNT(DISTINCT t.id) as tags
FROM
job_details as u
JOIN job_comments as c ON u.id = c.job_id
JOIN job_dislike as d ON u.id = d.job_id
JOIN job_like as l ON u.id = l.job_id
JOIN job_share as s ON u.id = s.job_id
JOIN job_tags as t ON u.id = t.job_id
WHERE
u.id = c.job_id AND
u.id = d.job_id AND
u.id = l.job_id AND
u.id = s.job_id AND
u.id = t.job_id
GROUP BY
u.id
This query is executed, but didn't get exact result.
I don't quite understand why.
I was hoping somebody here could help me out?
Thanks!
You probably didn't get the exact result because some tables may be missing values.
Although you can solve this problem with a LEFT JOIN, the safer solution is to pre-aggregate the data:
SELECT u.*, c.comments, d.dislikes, l.likes, s.shares, t.tags
FROM job_details as u LEFT JOIN
(select c.job_id, count(*) as comments from job_comments group by c.job_id
) c
ON u.id = c.job_id LEFT JOIN
(select d.job_id, count(*) as dislikes from job_dislike d group by d.job_id
) d
ON u.id = d.job_id LEFT JOIN
(select l.job_id, count(*) as likes from job_like l group by l.job_id
) l
ON u.id = l.job_id LEFT JOIN
(select s.job_id, count(*) as shares from job_share s group by s.job_id
) s
ON u.id = s.job_id LEFT JOIN
(select t.job_id, count(*) as tags from job_tags t group by t.job_id
) t
ON u.id = t.job_id;
Why is this better? Consider an id that has 5 comments, likes, dislikes, shares and tags. The JOIN approach produces an intermediate result with 5*5*5*5*5 = 3,125 intermediate rows. Things can really get out of hand for popular ids.
Use LEFT JOIN instead of JOIN. and you don't need WHERE clause since you have joined those tables. And, use IFNULL function to return 0 for null values. You need to modify you query like this :
SELECT u.id,
IFNULL(COUNT(DISTINCT c.id),0) as comments,
IFNULL(COUNT(DISTINCT d.id),0) as dislikes,
IFNULL(COUNT(DISTINCT l.id),0) as likes,
IFNULL(COUNT(DISTINCT s.id),0) as shares,
IFNULL(COUNT(DISTINCT t.id),0) as tags
FROM job_details as u
LEFT JOIN job_comments as c ON u.id = c.job_id
LEFT JOIN job_dislike as d ON u.id = d.job_id
LEFT JOIN job_like as l ON u.id = l.job_id
LEFT JOIN job_share as s ON u.id = s.job_id
LEFT JOIN job_tags as t ON u.id = t.job_id
GROUP BY u.id
SELECT
u.*,
GROUP_CONCAT(DISTINCT f.shot_id SEPARATOR ",") AS ownFavorites,
GROUP_CONCAT(DISTINCT st.shot_id SEPARATOR ",") AS ownStars,
GROUP_CONCAT(DISTINCT s.id SEPARATOR ",") AS ownShots,
( SELECT AVG(p.count)
FROM points p
LEFT JOIN shots s ON s.user_id = **U.ID** AND p.shot_id = s.id
WHERE date >= DATE_SUB(CURDATE(),INTERVAL 2 DAY)
) AS attention,
( SELECT SUM(p.count)
FROM points p
LEFT JOIN shots s ON s.user_id = **U.ID** AND s.id = p.shot_id
) AS popularity
FROM users u
LEFT OUTER JOIN shots s ON s.user_id = u.id
LEFT OUTER JOIN favorites f ON f.user_id = u.id
LEFT OUTER JOIN stars st ON st.user_id = u.id
WHERE u.username = ?;
I got two subselects which use the parameter u.id (marked in the query). If i do the sql like this it will generate somthing like that:
#1054 - Unknown column 'u.id' in 'on clause'
Means, the u.id id is NOT defined in the SubSelects. But in the MainSelect I choose from the users table, where u.id exists.
To my question: Is there a way to pass the selected u.id value to the Subselects with common sql?
Don't forget GROUP BY in the subqueries:
SELECT
u.*,
COALECSE(a.average, 0) attention,
COALESCE(p.total, 0) popular,
GROUP_CONCAT(DISTINCT f.shot_id) AS ownFavorites,
GROUP_CONCAT(DISTINCT st.shot_id SEPARATOR ",") AS ownStars,
GROUP_CONCAT(DISTINCT s.id SEPARATOR ",") AS ownShots
FROM
users u
LEFT JOIN
(
SELECT
s.user_id,
AVG(p.count) average
FROM
shots s
JOIN
points p
ON s.id = p.shot_id
WHERE
s.date >+ CURRENT_DATE - INTERVAL 2 DAY
GROUP BY s.user_id
) a
ON u.id = a.user_id
LEFT JOIN
(
SELECT
s.user_id,
SUM(p.count) total
FROM
shots s
JOIN
points p
ON s.id = p.shot_id
GROUP BY s.user_id
) p
ON u.id = p.user_id
LEFT OUTER JOIN shots s ON s.user_id = u.id
LEFT OUTER JOIN favorites f ON f.user_id = u.id
LEFT OUTER JOIN stars st ON st.user_id = u.id
WHERE u.username = 'user'
Seems like this may work. The select doesn't have knowledge of the of the users table the way you had it. I believe this would have knowledge of Users.
SELECT
u.*,
GROUP_CONCAT(DISTINCT f.shot_id SEPARATOR ",") AS ownFavorites,
GROUP_CONCAT(DISTINCT st.shot_id SEPARATOR ",") AS ownStars,
GROUP_CONCAT(DISTINCT s.id SEPARATOR ",") AS ownShots,
A.Attention, P.Popularity
FROM users u
LEFT OUTER JOIN shots s ON s.user_id = u.id
LEFT OUTER JOIN favorites f ON f.user_id = u.id
LEFT OUTER JOIN stars st ON st.user_id = u.id
LEFT OUTER JOIN
( SELECT AVG(p.count) attention
FROM points p
LEFT JOIN shots s ON s.user_id = **U.ID** AND p.shot_id = s.id
WHERE date >= DATE_SUB(CURDATE(),INTERVAL 2 DAY)
) AS A,
( SELECT SUM(p.count) popularity
FROM points p
LEFT JOIN shots s ON s.user_id = **U.ID** AND s.id = p.shot_id
) AS P
WHERE u.username = ?;
Try turning the selects into a subselect join.
FROM users u
LEFT OUTER JOIN shots s ON s.user_id = u.id
LEFT OUTER JOIN favorites f ON f.user_id = u.id
LEFT OUTER JOIN stars st ON st.user_id = u.id
LEFT OUTER JOIN ( SELECT AVG(p.count) AverageOfP, p.shot_id
FROM points p
WHERE date >= DATE_SUB(CURDATE(),INTERVAL 2 DAY)
) p ON p.shot_id = s.id
LEFT OUTER JOIN ( SELECT SUM(p.count) SumOfP, p.shot_id
FROM points p
) p2 ON p2.shot_id = s.id
The s table is already joined to u and should be good. Then in your select you can just select AverageOfP and SumOfP.
In query:
SELECT
i.id, i.title, i.description,
cities.name as city,
GROUP_CONCAT(DISTINCT station.name) as station,
GROUP_CONCAT(DISTINCT p.url) as photos
FROM
items i
INNER JOIN
cities ON cities.id = i.city_id
LEFT JOIN
item_photos p ON p.item_id = i.id
LEFT JOIN
item_stations s ON s.item_id = i.id
INNER JOIN
stations ON stations.id = s.station_id
WHERE i.id = ?
LIMIT 1
if rows in table item_stations don't exists, both LEFT JOINs works: returns photos and NULL for stations. but with INNER JOIN in this case query will return NULL for photos and station. How should I rewrite query to say INNER JOIN not to join tables if no needed rows in item_stations with s.item_id = i.id?
If you truly need to do an INNER JOIN between the item_stations and the stations table, then you might want to consider using an INNER JOIN in a subquery:
SELECT
i.id, i.title, i.description,
cities.name as city,
GROUP_CONCAT(DISTINCT s.name) as station,
GROUP_CONCAT(DISTINCT p.url) as photos
FROM items i
INNER JOIN cities
ON cities.id = i.city_id
LEFT JOIN item_photos p
ON p.item_id = i.id
LEFT JOIN
(
select item_id, name
from item_stations s
INNER JOIN stations
ON stations.id = s.station_id
)
ON s.item_id = i.id
WHERE i.id = ?
LIMIT 1
Otherwise I would suggest using a LEFT JOIN between the two:
SELECT
i.id, i.title, i.description,
cities.name as city,
GROUP_CONCAT(DISTINCT stations.name) as station,
GROUP_CONCAT(DISTINCT p.url) as photos
FROM items i
INNER JOIN cities
ON cities.id = i.city_id
LEFT JOIN item_photos p
ON p.item_id = i.id
LEFT JOIN item_stations s
ON s.item_id = i.id
LEFT JOIN stations
ON stations.id = s.station_id
WHERE i.id = ?
LIMIT 1
$q = "SELECT s.id, s.title, s.description,
(SELECT COUNT(*) FROM ".FORUM_THREADS." t WHERE t.cat_id = s.id) AS topics,
(SELECT COUNT(*) FROM ".FORUM_REPLIES." r INNER JOIN ".FORUM_THREADS." t ON r.thread_id = t.id
WHERE t.cat_id = s.id) AS replies,
(SELECT r.date FROM ".FORUM_REPLIES." r INNER JOIN ".FORUM_THREADS." t ON r.thread_id = t.id
WHERE t.cat_id = s.id ORDER BY r.date DESC LIMIT 1) AS last_post
FROM ".FORUM_SUBCATEGORIES." s WHERE s.parent = '$catid' AND s.status = '0' ORDER BY s.id";
I am attempting to select more than one field on the following part of the query
(SELECT r.date FROM ".FORUM_REPLIES." r INNER JOIN ".FORUM_THREADS." t ON r.thread_id = t.id
INNER JOIN ".TBL_USERS." u ON u.id = r.author WHERE t.cat_id = s.id ORDER BY r.date DESC LIMIT 1) AS last_post
Along with r.date, I want to select u.username and r.author.
How can I go about doing this?
Thanks!
Just add them to the SELECT:
(SELECT r.date, r.author, u.username FROM ".FORUM_REPLIES." r INNER JOIN ".FORUM_THREADS." t ON r.thread_id = t.id
INNER JOIN ".TBL_USERS." u ON u.id = r.author WHERE t.cat_id = s.id ORDER BY r.date DESC LIMIT 1) AS last_post
UPDATED after comment from OP:
You need to do 3 separate selects OR (depending on your data model) change the query so that the last_post query ends up after/in the FROM clause (there it can have as many columns as you want)...
Luke, you have a central select statement which uses nested select statements for getting the count. You can't depend on the nested select statements to count as the inner join, so you're going to have to add them to the central select statement instead.
In other words, join ".FORUM_REPLIES." and "u" (not sure what that's supposed to represent) with ".FORUM_SUBCATEGORIES.". I'd write the query for you, but I don't know how to link subcategories with replies and subcategories with u.