MySQL Join 3 Tables on Single Resource ID - mysql

I have three tables
resources_connection
resource_id
resource_tag_id
resources_flags
user_id
resource_id
resources_votes
user_id
resource_id
Each is a 2 column table, int (11) for both designed to allow me to query the number of tags, flags and votes based on a single 'resource id'
I am currently using this query to try and get a count of tags (resources_connection), flags(resources_flags) and votes(resources_votes):
SELECT COUNT(DISTINCT t1.resource_id) as votes,
COUNT(DISTINCT t2.resource_id) as flags,
COUNT(DISTINCT t3.resource_tag_id) as tags
FROM ecruit_demo.resources_votes t1
LEFT JOIN ecruit_demo.resources_flags t2
ON (t1.resource_id = t2.resource_id)
JOIN ecruit_demo.resources_connection t3
ON (t1.resource_id = t3.resource_id) WHERE t1.resource_id = 4
The problem is that this query returns proper results for resource_id = 1 but when I set resource_id to 4 (for which there is a tag) it returns all zeros. What would be the proper query structure to ensure that this query always returns the proper count of tags, flags and votes for a given resource_id?
I should also add that the only place resource_id = 4 occurs in the database is in resources_connection, the other two tables do not have this value

SELECT resource_ID,
MAX(CASE WHEN types = 'votes' THEN totals ELSE NULL END) votes,
MAX(CASE WHEN types = 'flags' THEN totals ELSE NULL END) flags,
MAX(CASE WHEN types = 'tags' THEN totals ELSE NULL END) tags
FROM
(
SELECT resource_ID, 'votes' types,
COUNT(DISTINCT resource_ID) totals
FROM resources_votes
GROUP BY resource_ID
UNION
SELECT resource_ID, 'flags' types,
COUNT(DISTINCT resource_ID) totals
FROM resources_flags
GROUP BY resource_ID
UNION
SELECT resource_ID, 'tags' types,
COUNT(DISTINCT resource_tag_id) totals
FROM resources_connection
GROUP BY resource_ID
) s
-- WHERE resource_ID = 1
GROUP BY resource_ID

I'm not really good in mysql but maybe you can try:
change JOIN to LEFT JOIN so the query would be like this.
SELECT COUNT(DISTINCT t1.resource_id) as votes,
COUNT(DISTINCT t2.resource_id) as flags,
COUNT(DISTINCT t3.resource_tag_id) as tags
FROM ecruit_demo.resources_votes t1
LEFT JOIN ecruit_demo.resources_flags t2
ON (t1.resource_id = t2.resource_id)
LEFT JOIN ecruit_demo.resources_connection t3
ON (t1.resource_id = t3.resource_id) WHERE t1.resource_id = 4

Related

get records by the lowest rank with nested tables and group by - MYSQL

I have a query problem that I previously solved with 2 tables and different db structure, and now I have 3 tables and I cant get the right result
leads table - regular leads table with id, firstname, etc...
statuses table - id, lead_id, brand_id, map_id
lead_id is relationship key for id in lead
map_id is relationship key for map_keys table
map_keys table - id, name, rank
the rules:
each lead can have more than one status or it can be without status
the query should print only the lowest status even if there is two or more statuses for the same lead
example:
lead: id = 3,
statuses: id = 7, lead_id = 3, map_id = 5
and another record:
statuses: id = 10, lead_id = 3, map_id = 1
map_keys:
id = 5, rank = 5
id = 1, rank = 1
in the result I should get
lead_id = 3, map_id = 1
In the past I had the rank within the statuses table, so my solution was
SELECT IF(s2.rank IS NULL, s.rank, s2.rank) AS rank FROM `leads` l
LEFT JOIN statuses s ON s.lead_id = l.id
LEFT JOIN statuses s2 ON s2.lead_id = s.lead_id AND s.rank > s2.rank
GROUP BY l.id
but with the new db structure I cant get the right result, I hope that my question is understandable, If not I will try my best to explain it better, Thanks!
I solved my problem, my final query is
SELECT * FROM leads
LEFT JOIN (
SELECT lead_id, mk.name, mk.rank FROM `statuses` s
JOIN map_keys mk ON mk.id = s.map_id
JOIN statuses_maps sm ON sm.id = s.status_id
ORDER BY rank ASC ) as s ON lead_id = lead.id
GROUP BY leads.id

Sql conditional count with join

I cannot find the answer to my problem here on stackoverflow. I have a query that spans 3 tables:
newsitem
+------+----------+----------+----------+--------+----------+
| Guid | Supplier | LastEdit | ShowDate | Title | Contents |
+------+----------+----------+----------+--------+----------+
newsrating
+----+----------+--------+--------+
| Id | NewsGuid | UserId | Rating |
+----+----------+--------+--------+
usernews
+----+----------+--------+----------+
| Id | NewsGuid | UserId | ReadDate |
+----+----------+--------+----------+
Newsitem obviously contains newsitems, newsrating contains ratings that users give to newsitems, and usernews contains the date when a user has read a newsitem.
In my query I want to get every newsitem, including the number of ratings for that newsitem and the average rating, and how many times that newsitem has been read by the current user.
What I have so far is:
select newsitem.guid, supplier, count(newsrating.id) as numberofratings,
avg(newsrating.rating) as rating,
count(case usernews.UserId when 3 then 1 else null end) as numberofreads from newsitem
left join newsrating on newsitem.guid = newsrating.newsguid
left join usernews on newsitem.guid = usernews.newsguid
group by newsitem.guid
I have created an sql fiddle here: http://sqlfiddle.com/#!9/c8add/8
Both count() calls don't return the numbers I want. numberofratings should return the total number of ratings for that newsitem (by all users). numberofreads should return the number of reads for the current user for that newsitem.
So, newsitem with guid d104c330-c319-40e8-8be3-a7c4f549d35c should have 2 ratings and 3 reads for the current user with userid = 3.
I have tried conditional counts and sums, but no success yet. How can this be accomplished?
The main problem that I see is that you're joining in both tables together, which means that you're going to effectively be multiplying out by both numbers, which is why your counts aren't going to be correct. For example, if the Newsitem has been read 3 times by the user and rated by 8 users then you're going to end up getting 24 rows, so it will look like it has been rated 24 times. You can add a DISTINCT to your COUNT of the ratings IDs and that should correct that issue. Average should be unaffected because the average of 1 and 2 is the same as the average of 1, 1, 2, & 2 (for example).
You can then handle the reads by adding the userid to the JOIN condition (since it's an OUTER JOIN it shouldn't cause any loss of results) instead of in a CASE statement for your COUNT, then you can do a COUNT on distinct id values from Usernews. The resulting query would be:
SELECT
I.guid,
I.supplier,
COUNT(DISTINCT R.id) AS number_of_ratings,
AVG(R.rating) AS avg_rating,
COUNT(DISTINCT UN.id) AS number_of_reads
FROM
NewsItem I
LEFT OUTER JOIN NewsRating R ON R.newsguid = I.guid
LEFT OUTER JOIN UserNews UN ON
UN.newsguid = I.guid AND
UN.userid = #userid
GROUP BY
I.guid,
I.supplier
While that should work, you might get better results from a subquery, as the above needs to explode out the results and then aggregate them, perhaps unnecessarily. Also, some people might find the below to be a little clearer.
SELECT
I.guid,
I.supplier,
R.number_of_ratings,
R.avg_rating,
COUNT(*) AS number_of_reads
FROM
NewsItem I
LEFT OUTER JOIN
(
SELECT
newsguid,
COUNT(*) AS number_of_ratings,
AVG(rating) AS avg_rating
FROM
NewsRating
GROUP BY
newsguid
) R ON R.newsguid = I.guid
LEFT OUTER JOIN UserNews UN ON UN.newsguid = I.guid AND UN.userid = #userid
GROUP BY
I.guid,
I.supplier,
R.number_of_ratings,
R.avg_rating
I'm with Tom you should use a subquery to calculate the user count.
SQL Fiddle Demo
SELECT NI.guid,
NI.supplier,
COUNT(NR.ID) as numberofratings,
AVG(NR.rating) as rating,
user_read as numberofreads
FROM newsitem NI
LEFT JOIN newsrating NR
ON NI.guid = NR.newsguid
LEFT JOIN (SELECT NewsGuid, COUNT(*) user_read
FROM usernews
WHERE UserId = 3 -- use a variable #user_id here
GROUP BY NewsGuid) UR
ON NI.guid = UR.NewsGuid
GROUP BY NI.guid,
NI.supplier,
numberofreads;

MySQL counting and sorting rows returned from a query

I have the following MySQL tables with the following fields:
"questions"
question_id (PK, AI), author_id (FK), approved ...
"users"
user_id (PK, AI), username, role ...
I want to return the usernames of those who have the most questions and where approved equals 'Y' in descending order but the role must equal '1'.
For example, assuming all users have a role equaling '1', the following data should return the username field for author_id 2, followed by author_id 1.
question_id author_id approved
-------- ----- ----
1 1 Y
2 1 N
3 2 Y
4 2 Y
5 3 N
So far, I've got the following:
SELECT Q.question_id, Q.author_id, Q.approved, U.role
FROM p1209279x.questions Q
LEFT JOIN p1209279x.users U
ON U.user_id=Q.author_id
WHERE approved='Y' AND role='1';
But this returns multiple rows with the same user_id so how do I count the rows for each user_id returned and output their respective usernames?
Just add an aggregate function (e.g. COUNT() or SUM()) in the SELECT list, and add a GROUP BY clause to the query, and an ORDER BY clause to the query.
SELECT U.username
, COUNT(Q.question_id)
FROM ...
GROUP BY Q.author_id
ORDER BY COUNT(Q.question_id) DESC
Note that the predicate on the role column in the WHERE clause of your query negates the "outerness" of the LEFT JOIN operation. (With the LEFT JOIN, any rows from Q that don't find a matching row in U, will return NULL for all of the columns in U. Adding a predicate U.role = '0' in the WHERE clause will cause any rows with a NULL value in U.role to be excluded.
This would return distinct values of username, along with a "count" of the questions related to that user:
SELECT U.username
, COUNT(Q.question_id)
FROM p1209279x.questions Q
JOIN p1209279x.users U
ON U.user_id=Q.author_id
WHERE Q.approved='Y'
AND Q.role='0'
GROUP BY Q.author_id
ORDER BY COUNT(Q.question_id) DESC
You'd have to add a count to your query:
SELECT count(U.user_id) as UserCount, Q.question_id, Q.author_id, Q.approved, U.role
FROM p1209279x.questions Q
LEFT JOIN p1209279x.users U
ON U.user_id=Q.author_id
WHERE approved='Y' AND role='0'
GROUP BY UserCount, Q.question_id, Q.author_id, Q.approved, U.role;
But this may still return too many rows, as they may all still be unique. You'd have to drop some rows out of the select if you want just raw counts. But that's the basic idea.

Group by grouped column?

I have a table of node likes, which looks roughly like this:
lid nid uid type
1 23 3 like
2 23 1 like
3 49 3 dislike
4 11 6 like
lid = unique ID for this table, nid = "node" (content) ID, uid = user ID and type is self explanatory.
With this query:
SELECT nid, COUNT(lid) AS score, type
FROM node_likes
INNER JOIN users ON node_likes.uid = users.uid
GROUP BY nid, type
I can get each node with its like and dislike scores. The inner join is irrelevant; some (dis)likes are from users that no longer exist, and the join is to eliminate them.
The result looks like this:
nid score type
307 4 like
307 1 dislike
404 24 like
How can I then sub-group this query by type, and return the top-scoring node ID for each "like" type (like/dislike)?
Ie.
nid score type
404 24 like
307 1 dislike
SELECT
SUBSTRING_INDEX(GROUP_CONCAT(nid ORDER BY likes DESC),',',1) as most_likes_nid,
MAX(likes) as most_likes,
SUBSTRING_INDEX(GROUP_CONCAT(nid ORDER BY dislikes DESC),',',1) as most_dislikes_nid,
MAX(dislikes) as most_dislikes
FROM (
SELECT
nid,
COUNT(IF(type = 'like', 1, null)) as likes,
COUNT(IF(type = 'dislike', 1 ,null)) as dislikes
FROM node_likes
GROUP BY nid
) as t
SELECT nid, COUNT(lid) AS score, type
FROM node_likes
INNER JOIN users ON node_likes.uid = users.uid
GROUP BY nid, type
ORDER BY type DESC, score DESC;
may do the trick.
Try this:
SELECT
nid, max(score) as score, type
FROM (
SELECT nid, COUNT(lid) AS score, type
FROM node_likes
INNER JOIN users ON node_likes.uid = users.uid
GROUP BY nid, type
) results
GROUP BY type
ORDER BY type DESC, score DESC

Organizing data output with MySQL

I have the following table with data:
t1 (results): card_id group_id project_id user_id
The tables that contain actual labels are:
t2 (groups): id project_id label
t3 (cards): id project_id label
There could be multiple entries by different users.
I need help with writing a query to display the results in a table format with totals counts corresponding card/group. Here's my start but I'm not sure that I'm on the right track...
SELECT COUNT(card_id) AS cTotal, COUNT(group_id) AS gTotal
WHERE project_id = $projID
Unless I'm mistaken, it seems that all you need to do is group by card_id and group_id for the given project_id and pull out the count for each group
SELECT card_id, group_id, COUNT(user_id) FROM mytable
WHERE project_id = 001
GROUP BY (card_id, group_id);
EDIT:
Taking into account the card and group tables involves some joins, but the query is fundamentally the same. Still grouping by card and group, and constraining by project id
SELECT c.label, g.label, COUNT(t1.user_id) FROM mytable t1
JOIN groups g ON t1.group_id=g.id
JOIN cards c ON t1.card_is=c.id
WHERE t1.project_id = 001
GROUP BY (c.card_id, g.group_id)
ORDER BY (c.card_id, g.group_id);
I don't think you can get a table as you want with just SQL. You'll have to render the table in code by iterating over the results. How you do that depends on what language/platform you are using.
if you know for a fixed fact that there are nine groups, then just include those groups in subqueries - similar to this:
select cTotal, g1.gTotal as Group1, g2.gTotal as Group2... etc
from
( SELECT COUNT(card_id) AS cTotal
, COUNT(group_id) AS gTotal
WHERE project_id = $projID
AND group_id = 1 ) g1
, ( SELECT COUNT(card_id) AS cTotal
, COUNT(group_id) AS gTotal
WHERE project_id = $projID
AND group_id = 2 ) g2
etc.