SQL: Count in join not working - sql

I have an SQL query where I simply join two tables. One table contain comments and the other is the user table. I join the tables to in a simple manner get user information about the user who wrote the comment (username) at the same time get the comment (comment text etc.).
Now I want to count the number of comments to write the correct number of comments on the top of the page. I do this by adding a COUNT, and an alias to save the value.
When I echo numCount, I get the correct value of comments, but I get no comments in my comment loop. As soon as I remove the count, I get all comments again. What am I doing wrong?
SELECT
ncID, ncText, ncDate,
uID, uName, uImageThumb,
COUNT(a.ncID) AS numComments
FROM tblNewsComments a LEFT JOIN tblUsers b
ON a.ncUserID = b.uID
WHERE a.ncNewsID = $newID
ORDER BY ncDate DESC

I am going to assume this is MySQL (or maybe SQLite), since most other RDBMS would fail on this query. The issue is that you are missing a GROUP BY clause, which is required when using an aggregate function like COUNT() unless it is to operate over the entire rowset. MySQL's unusual behavior is to allow the absence of a GROUP BY, or to allow columns in SELECT which are not also in the GROUP BY, producing unusual results.
The appropriate way to do this would be to join in a subquery which returns the COUNT() per ncID.
SELECT
ncID,
ncText,
ncDate,
uID,
uName,
uImageThumb,
/* The count returned by the subquery */
ccount.numComments
FROM
tblNewsComments a
LEFT JOIN tblUsers b ON a.ncUserID = b.uID
/* Derived table returns only ncID and count of comments */
LEFT JOIN (
SELECT ncID, COUNT(*) AS numComments
FROM tblNewsComments
GROUP BY ncID
) ccount ON a.ncID = ccount.ncID
WHERE a.ncNewsID = $newID
ORDER BY ncDate DESC
Edit Whoops - looks like you wanted the count per ncID, not the count per ncUserID as I originally had it.

I don't know what SQL engine you are using, but what you have here is not valid SQL and should be flagged as such.
COUNT is an aggregate function and you can only apply those to groups or a whole table, so in your case you would probably do
SELECT
ncID, ncText, ncDate,
uID, uName, uImageThumb,
COUNT(a.ncID) AS numComments
FROM tblNewsComments a LEFT JOIN tblUsers b
ON a.ncUserID = b.uID
WHERE a.ncNewsID = $newID
GROUP BY ncID, ncText, ncDate,
uID, uName, uImageThumb
ORDER BY ncDate DESC

You're using an AGGREGATE function (Count) but you're needing a GROUP BY to make any sense from that count.
I suggest adding "GROUP BY [all other field names except the COUNT]" to your query

Try this:
SELECT
ncID, ncText, ncDate,
uID, uName, uImageThumb,
(SELECT COUNT(ncID)
FROM
tblNewsComments a
INNER JOIN
tblUsers b
ON a.ncUserID = b.uID)
AS numComments
FROM tblNewsComments a LEFT JOIN tblUsers b
ON a.ncUserID = b.uID
WHERE a.ncNewsID = $newID
ORDER BY ncDate DESC

Related

Remove duplicates from result in sql

i have following sql in java project:
select distinct * from drivers inner join licenses on drivers.user_id=licenses.issuer_id
inner join users on drivers.user_id=users.id
where (licenses.state='ISSUED' or drivers.status='WAITING')
and users.is_deleted=false
And result i database looks like this:
And i would like to get only one result instead of two duplicated results.
How can i do that?
Solution 1 - That's Because one of data has duplicate value write distinct keyword with only column you want like this
Select distinct id, distinct creation_date, distinct modification_date from
YourTable
Solution 2 - apply distinct only on ID and once you get id you can get all data using in query
select * from yourtable where id in (select distinct id from drivers inner join
licenses
on drivers.user_id=licenses.issuer_id
inner join users on drivers.user_id=users.id
where (licenses.state='ISSUED' or drivers.status='WAITING')
and users.is_deleted=false )
Enum fields name on select, using COALESCE for fields which value is null.
usually you dont query distinct with * (all columns), because it means if one column has the same value but the rest isn't, it will be treated as a different rows. so you have to distinct only the column you want to, then get the data
I suspect that you want left joins like this:
select *
from users u left join
drivers d
on d.user_id = u.id and d.status = 'WAITING' left join
licenses l
on d.user_id = l.issuer_id and l.state = 'ISSUED'
where u.is_deleted = false and
(d.user_id is not null or l.issuer_id is not null);

SQL DISTINCT only for one column

I am trying to get the results from this query to only give me DISTINCT results by the concatenated column fullname. The results instead give me DISTINCT by both fullname and facilityname (which is 'name'). I've looked over other solutions to similar questions but don't understand them well enough to apply them to this situation.
I've tried to play with the code, but nothing worth noting.
USE country_club;
SELECT DISTINCT
CONCAT(mem.firstname, mem.surname) AS fullname,
fac.name
FROM
Bookings AS boo
JOIN
Members AS mem ON boo.memid = mem.memid
JOIN
Facilities AS fac ON boo.facid = fac.facid
WHERE
boo.facid = 0 OR boo.facid = 1
ORDER BY
fullname ASC;
No errors on this code to note. Just need to modify the conditions to arrive at the desired outcome.
If you want one row per group of columns, use group by, not distinct:
SELECT concat(m.firstname, m.surname) as fullname,
MIN(f.name)
FROM Bookings b JOIN
Members m
ON b.memid = m.memid JOIN
Facilities f
ON b.facid = f.facid
WHERE b.facid IN (0, 1)
GROUP BY concat(m.firstname, m.surname)
ORDER BY fullname ASC;
If you're using the mysql, before running your query, run this:
SET SESSION sql_mode=(SELECT REPLACE(##sql_mode,'ONLY_FULL_GROUP_BY', ''));

Not a single-group group function

I have some tables which basically look as follows:
TBL_USER
user_id - number
user_name - varchar
TBL_STUFF
stuff_id - number
stuff_user_id - number
I want to query for all user information including the number of "stuff" they have. I was trying something like this:
select user_id, user_name, count(stuff_id)
from tbl_user
left outer join tbl_stuff on stuff_user_id = user_id
where user_id = 5;
but I get an error which says "not a single-group group function"
Is there some other way I should be doing this?
Well, you are missing the group function ;-)
Try this:
select user_id, user_name, count(stuff_id)
from tbl_user left outer join tbl_stuff on stuff_user_id = user_id
where user_id = 5
group by user_id, user_name;
The last line is the group by clause that tells Oracle to count all rows with the same user_id and user_name combination.
You could also do it like this:
select
user_id,
user_name,
(
SELECT
COUNT(*)
FROM
tbl_stuff
WHERE
stuff_user_id = tbl_user.user_id
) AS StuffCount,
from
tbl_user
where
user_id = 5;
One of your comments states that you don't want to include all the field present in a GROUP BY clause.
#Arion posted a correlated-sub-query re-factor that gives the same values.
The following query uses a standard (un-correlated) sub-query (inline-view) instead. This is because using this inline-view structure can often perform correlated-sub-query equivilents. But, also, because I find them easier to maintain.
WITH
stuff_count
AS
(
SELECT
stuff_user_id AS user_id,
COUNT(*) AS val
FROM
tbl_stuff
GROUP BY
stuff_user_id
)
SELECT
tbl_user.user_id,
tbl_user.user_name,
stuff_count.val
FROM
tbl_user
LEFT JOIN
stuff_count
ON stuff_count.user_id = tbl_user.user_id
WHERE
tbl_user.user_id = 5;
NOTE: When the plan is generated, it only runs the sub-query for the user_id's necessary, not the whole table ;)

i want to modify this SQL statement to return only distinct rows of a column

select
picks.`fbid`,
picks.`time`,
categories.`name` as cname,
options.`name` as oname,
users.`name`
from
picks
left join categories
on (categories.`id` = picks.`cid`)
left join options
on (options.`id` = picks.oid)
left join users
on (users.fbid = picks.`fbid`)
order by
time desc
that query returns a result that like:
my question is.... I would like to modify the query to select only DISTINCT fbid's. (perhaps the first row only sorted by time)
can someone help with this?
select
p2.fbid,
p2.time,
c.`name` as cname,
o.`name` as oname,
u.`name`
from
( select p1.fbid,
min( p1.time ) FirstTimePerID
from picks p1
group by p1.fbid ) as FirstPerID
JOIN Picks p2
on FirstPerID.fbid = p2.fbid
AND FirstPerID.FirstTimePerID = p2.time
LEFT JOIN Categories c
on p2.cid = c.id
LEFT JOIN Options o
on p2.oid = o.id
LEFT JOIN Users u
on p2.fbid = u.fbid
order by
time desc
I don't know why you originally had LEFT JOINs, as it appears that all picks must be associated with a valid category, option and user... I would then remove the left, and change them to INNER joins instead.
The first inner query grabs for each fbid, the FIRST entry time which will result in a single entity for the FBID. From that, it re-joins to the picks table for the same ID and timeslot... then continues for the rest of the category, options, users join criteria of that single entry.
2 options, you could write a group by clause.
Or you could write a nested query joined back to itself to get pertinent info.
Nested aliased table:
SELECT
n.fBids
FROM
MyTable t
INNER JOIN
(SELECT DISTINCT fBids
FROM MyTable) n
ON n.ID = t.ID
Or group by option
SELECT fBId from MyTable
GROUP BY fBID
select picks.`fbid`, picks.`time`, categories.`name` as cname,
options.`name` as oname, users.`name` from picks left join categories
on (categories.`id` = picks.`cid`) left join options on (options.`id` = picks.oid)
left join users on (users.fbid = picks.`fbid`)
order by time desc GROUP BY picks.`fbid`
select
picks.fbid,
MIN(picks.time) as first_time,
MAX(picks.time) as last_time
from
picks
group by
picks.fbid
order by
MIN(picks.time) desc
However, if you want only distinct fbid's you cannot display cname and other columns at the same time.

mysql left join ascending

I have a query:
SELECT reply.id,
reply.message,
reply.userid,
reply.date,
medal.id,
medal.url,
medal.name,
user.id,
user.name AS username
FROM posts AS reply
LEFT JOIN users AS user ON reply.userid = user.id
LEFT JOIN medals AS medal ON medal.userid = user.id
GROUP BY reply.id
ORDER BY reply.id ASC
everything is OK, except that I get the medal ascending not descending
which mean that it grab the first medal that user got - I need to get the last one.
The fact that you are seeing the first record per group is accidental. Selecting a non-aggregate, non-group-unique column in a GROUP BY query causes an undefined record to be selected.
For a more detailed explanation read: http://dev.mysql.com/tech-resources/articles/debunking-group-by-myths.html.
One correct way to do what you're doing is by using a subquery, in which you select the maximum medal date per desired group.
This approach is outlined here: http://dev.mysql.com/doc/refman/5.6/en/example-maximum-column-group-row.html
You could perhaps make a subquery or temporary table for medals instead of joining directly on medals. The temporary table would be the medals table but sorted DESC. The subquery would be LEFT JOIN (select id, url, name from medals order by desc) but I feel like that would be very slow and not the best option.
The simplest solution is to do this:
ORDER BY reply.id ASC, medal.id DESC
AND remove your GROUP BY statement.
You can filter the posts / users who have multiple medals afterwards (skip rows that contain the medals you don't want).
The next option is to select MAX(medal.id) in a subquery, and then join on that. That gets messy, but is doable. Here is the general idea (you will probably have to rename a few table aliases, since they are used more than once):
SELECT *,
medal.url,
medal.name
FROM
(
SELECT reply.id,
reply.message,
reply.userid,
reply.date,
MAX(medal.id) AS medal_id,
user.id,
user.name AS username
FROM posts AS reply
LEFT JOIN users AS user ON reply.userid = user.id
LEFT JOIN medals AS medal ON medal.userid = user.id
GROUP BY reply.id
ORDER BY reply.id ASC
)
AS t
LEFT JOIN medals AS medal ON medal.id = t.medal_id