MySQL evaluate case with subquery - mysql

I am trying to create a custom sort that involves the count of some records in another table. For example, if one record has no records associated with it in the other table, it should appear higher in the sort than if it had one or more records. Here's what I have so far:
SELECT People.*, Organizations.Name AS Organization_Name,
(CASE
WHEN Sent IS NULL AND COUNT(SELECT * FROM Graphics WHERE People.Organization_ID = Graphics.Organization_ID) = 0 THEN 0
ELSE 1
END) AS Status
FROM People
LEFT JOIN Organizations ON Organizations.ID = People.Organization_ID
ORDER BY Status ASC
The subquery within the COUNT is not working. What is the correct way to do something like this?
Update: I moved the case statement into the order by clause and added a join:
SELECT People.*, Organizations.Name AS Organization_Name
FROM People
LEFT JOIN Organizations ON Organizations.ID = People.Organization_ID
LEFT JOIN Graphics ON Graphics.Organization_ID = People.Organization_ID
GROUP BY People.ID
ORDER BY
CASE
WHEN Sent IS NULL AND Graphics.ID IS NULL THEN 0
ELSE 1
END ASC
So if if the People record does not have any graphics, Graphics.ID will be null. This achieves the immediate need.

If what you tried does not work, it can be done by joining against a subquery, and placing the CASE expression into ORDER BY as well:
SELECT
People.*,
orgcount.num
FROM People JOIN (
SELECT Organization_ID, COUNT(*) AS num FROM Graphics GROUP BY Organization_ID
) orgcount ON People.Organization_ID = orgcount.num
ORDER BY
CASE WHEN Sent IS NULL AND orgcount.num = 0 THEN 0 ELSE 1 END,
orgcount.num DESC

You could use an outer join to the Graphics table to get the data needed for your sort.
Since I don't know your schema, I made an assumption that the People table has a primary key column called ID. If the PK column has a different name, you should substitute that in the GROUP BY clause.
Something like this should work for you:
SELECT People.*, (count(Distinct Graphics.Organization_ID) > 0) as Status
FROM People
LEFT OUTER JOIN Graphics ON People.Organization_ID = Graphics.Organization_ID
GROUP BY People.ID
ORDER BY Status ASC

Fairly straight forward with a LEFT JOIN provided you have some kind of primary key in the People table to GROUP on;
SELECT p.*, sent IS NOT NULL or COUNT(g.Organization_ID) Status
FROM People p LEFT JOIN Graphics g ON g.Organization_ID = p.Organization_ID
GROUP BY p.primary_key
ORDER BY Status
Demo here.

Related

How to select comment count, Sum of votes, and whether active user has voted

Im having trouble structuring my MySQL query to return an accurate comment count, sum of votes, and the active users vote.
My tables are
wall_posts ( id, message, username, etc )
comments ( id, wall_id, username, text, etc )
votes ( id, wall_id, vote (+1 or -1), username )
My query looks like this
SELECT
wall_posts.*,
COUNT( comments.wall_id ) AS comment_count,
COALESCE( SUM( v1.vote ), 0 ) AS vote_tally,
v2.vote
FROM
wall_posts
LEFT JOIN comments ON wall_posts.id = comments.wall_id
LEFT JOIN votes v1 ON wall_posts.id = v1.wall_id
LEFT JOIN votes v2 ON wall_posts.id = v2.wall_id AND v2.username=:username
WHERE
symbol =: symbol
GROUP BY
wall_posts.id
ORDER BY
date DESC
LIMIT 15
It works for always returning the correct value for the specific active users vote (+1 or -1) or null if hasnt voted. If there are no comments on an item, the total vote sum is correct. If there are any comments, the vote sum will always be equal to the comment count, possibly with a negative sign if there are down votes but always equal to the amount of comments.
I think its obviously the way ive connected my tables but i just cant figure out why its copying the comment count, 1000000 points to someone who can explain this to me :)
You need to perform the aggregate operations in subqueries. Right now instead you're JOINing all of the tables (pre-aggregation) together. If you remove the aggregates (and the GROUP BY) you'll see the large mass of data which doesn't really mean anything.
Instead, try this (note I'm using a VIEW):
CREATE VIEW walls_posts_stats AS
SELECT
wall_posts.id,
COALESCE( comments_stats.comment_count, 0 ) AS comment_count,
COALESCE( votes_stats.vote_tally, 0 ) AS vote_tally
FROM
wall_posts
LEFT OUTER JOIN
(
SELECT
wall_id,
COUNT(*) AS comment_count
FROM
comments
GROUP BY
wall_id
) AS comments_stats ON wall_posts.id = comments_stats.wall_id
LEFT OUTER JOIN
(
SELECT
wall_id,
SUM( vote ) AS vote_tally
FROM
votes
GROUP BY
wall_id
) AS votes_stats ON wall_posts.id = votes_stats.wall_id
Then you can query it JOINed with your original wall data:
SELECT
wall_posts.*, -- note: avoid the use of * in production queries
stats.comment_count,
stats.vote_tally,
user_votes.vote
FROM
wall_posts
INNER JOIN walls_posts_stats AS stats ON wall_posts.id = stats.id
LEFT OUTER JOIN
(
SELECT
wall_id,
vote
FROM
votes
WHERE
username = :username
) AS user_votes ON wall_posts.id = user_votes.wall_id
ORDER BY
date DESC
LIMIT 15
Hypothetically you could combine it into a single large query (basically copy+paste the VIEW body into the INNER JOIN walls_posts_stats clause) but I feel that would introduce maintainability issues.
While MySQL does support views, it does not support parameterized views (aka composable table-valued functions; stored procedures are not composable) so that's why the user_votes subquery isn't in the walls_posts_stats VIEW.

How to achiave this:MYSQL SELECT if true select a else select b?

I want to select user.* if chat.chat_type IS NULL, but if chat.chat_type = 1
I want to select group.*
SELECT
CASE
WHEN chat.chat_type IS NULL THEN user.*
WHEN chat.chat_type =1 THEN group.*
END,
x,y
FROM `chat` LEFT JOIN group ...
How I can achieve this?
The database design as described is not so good, because you have an ID in your chat table that can either be a user ID or a group ID. Thus you cannot have a constraint (foreign key) on this column, which makes it possible to put any value in there, i.e. even an ID that doesn't exist.
Anyway, with the design given, to get either user or group you outer join both tables on the conditions you already mentioned. Then use COALESCE to see whether you got a user or a group in the result row.
select c.from, coalesce(u.name, g.name) as to_name
from chat c
left join usr u on c.chat_type is null and c.to = u.user_id
left join grp g on c.chat_type = 1 and c.to = g.group_id
where c.chat_type is null or c.chat_type = 1;
You have to use Union:
SELECT user.* from user join ... where chat.chat_type is null
Union
SELECT `Group`.* from `Group` join .... where chat.chat_type =1
if user and Group have a different number of column, you have to use a Default where you have less columns.
Hint: because Group is a Keyword, you should rename your table

Outer join providing inner join results

This should be a very simple solution, which means you'll probably think this is a dumb question, but I've tried everything I can think of.
I have two tables, one for possible poll choices, and the other for actual responses. They're structured like so:
choices responses
----------- ----------
poll_id poll_id
choice_id user_id
choice_text choice_id
I have a poll with two choices (yes/no), so I'm trying to fetch results so that if no one has voted for a certain choice, that choice shows up in the result set with a null value. So if 3 users have voted "yes" and none have voted "no", I want the result set to be:
choice_text num
-----------------------------
yes 3
no null
I would have thought this simply an outer join like so:
select
c.choice_text,
count(*) num
from
choices c
left outer join responses r
on c.poll_id = r.poll_id
and c.choice_id = r.choice_id
where
r.poll_id = 1
group by r.choice_id
order by r.choice_id asc;
But alas, that is giving me:
choice_text num
-----------------------------
yes 3
...without any record for "no".
I've tried every language for joins I can think of, with the same erroneous result.
Thoughts?
Your where condition on the outer joined table turns the outer join into an inner join. Move that condition into the JOIN
select c.choice_text,
count(*) num
from choices c
left outer join responses r
on c.poll_id = r.poll_id
and c.choice_id = r.choice_id
and r.poll_id = 1
group by r.choice_id
order by r.choice_id asc;
Btw: your usage of group by is incorrect and every other DBMS would reject that statement. MySQL simply chooses to return random data instead of failing with an error. For more details please see these blogs:
Debunking GROUP BY myths
Wrong GROUP BY makes your queries fragile
Figured it out thanks to #a_horse_with_no_name pointing me in the right direction.
The winning query:
select c.choice_text,
count(r.user_id) num
from choices c
left outer join responses r
on c.poll_id = r.poll_id
and c.choice_id = r.choice_id
and c.poll_id = 1
group by c.choice_id
order by c.choice_id asc;
Moved the c.poll_id clause to the join, like #a_horse_with_no_name suggested, but then had to group by the choices table instead of the responses table, and finally changed my count(*) to count(r.user_id), and voila.

How to adjust this query to get the result?

I have a query which is actually a members match with initial letter 'a' and the result also contain users friend.
What I would like is that the users friend must come before in the result and then remaining users.
Here is the query
SELECT `a`.`mem_id`
FROM `members` `a`
INNER JOIN
(
SELECT DISTINCT `n2`.`mem_id`
FROM `network` `n1`,`network` `n2`
WHERE `n1`.`frd_id` = `n2`.`mem_id`
AND `n1`.`mem_id`='777'
AND `n2`.`frd_id`='777'
) `b`
WHERE `a`.`mem_id`=`b`.`mem_id`
AND `a`.`profilenam` LIKE 'a%'
AND `a`.`deleted` ='N'
ORDER BY `profilenam`
As i understand your question, here is a query that will return the data you're looking for:
SELECT M.mem_id
FROM members M
LEFT OUTER JOIN (SELECT DISTINCT N1.mem_id
,N1.frd_id
FROM network N1
INNER JOIN network N2 ON N2.mem_id = N1.frd_id
AND N2.frd_id = N1.mem_id) F ON F.mem_id = M.mem_id
AND F.frd_id = '777'
WHERE M.profilename LIKE 'a%'
AND M.deleted = 'N'
ORDER BY CASE
WHEN F.mem_id IS NOT NULL THEN 0
ELSE 1
END, M.profilename
Some explanation about my query:
The table members has a LEFT OUTER JOIN on a query that returns every existing friendships (based on your query, i supposed that to consider two members as friends it's mandatory to have a two ways connection in table network).
This jointure with the condition F.frd_id = '777 ensure that the join is only made if the active member is a friend of the member with id '777'.
The last element is the key element that you were looking for, the ORDER BY clause to have the friends first and then the other members. The first condition of the clause is a simple SWITCH statement that tests if the jointure exists or not, if yes that means that the member is a friend, otherwise not. Every friends have the value 0 and the other members have the value 1, by doing an ascending sort on this value the result will be ordered as desired.
Hope this will help.

SQL Join returns only one record

I'm writing a query whereby I'm trying to count the total number of records in report and assignment table, whiles at the same time retrieving information from the main table group. Group has a primary key id which is saved in the other tables as gid. This is the query:
SELECT `group`.`id` AS `gid`
, `group`.`name` AS `g_name`
, COUNT(`report`.`id`) AS `reports`
FROM `group`
LEFT OUTER JOIN `report` ON `report`.`gid` = `group`.`id`
LEFT OUTER JOIN `assignment` ON `assignment`.`gid` = `group`.`id`
WHERE `group`.`active` = 0
ORDER BY
`group`.`name`;
My problem is whenever I execute this only one record is returned even if theirs multiple groups.
Thanks in advance.
Well, your query is far from correct :) First of all, you should not have aggregated functions (in this case count) without a group by clause. Now, even if you have that clause the query will summarize information and you want both: the detail and a summary in the same query. I'd recommend 2 separate queries to retrieve this information, but if you want information mixed in only one query (the detail and also the "total number of records in report and assignment table") try the following query:
SELECT
`group`.id AS gid,
`group`.name AS g_name,
(SELECT COUNT(*) from report) as ReportTotalCount,
(SELECT COUNT(*) from assignment) as AssignmentTotalCount,
FROM `group`
WHERE `group`.`active` = 0
LEFT OUTER JOIN report ON report.gid = `group`.id
LEFT OUTER JOIN assignment ON assignment.gid = `group`.id
ORDER BY `group`.name;
I whish I could understand exactly what you're looking for but this might give you an idea on how to get the result you expect.
Can't see anything obvious in your query that would limit it to returning one record.
You are going to have to break it up to see where the problem is against your existing data.
So how many groups where acitive = 0, ahow many with a corresponding assignment record, etc.
maybe it will help:
SELECT
groupid,
groupname,
reports,
assignments,
FROM
(SELECT group.id, group.name, COUNT(*) AS reports from group
INNER JOIN report ON (report.gid = group.id)
WHERE group.active = 0
GROUP BY group.id ) AS ReportForGroup
CROSS JOIN
(SELECT group.id AS groupid, group.name AS groupname, COUNT(*) AS assignments from group
INNER JOIN assignmentON (assignment.gid = group.id)
WHERE group.active = 0
GROUP BY group.id ) AS AssignmentForGroup
ON (ReportForGroup.groupid = AssignmentForGroup.groupid)
ORDER BY groupname;
I'm can't check it so if LEFT JOIN returns to COUNT(*) 0 or 1. if it returns 0 just change the INNERs to LEFTs and use INNER JOIN between the two queries