Cannot update table by counting fields in related table - mysql

I'd like to update followers in profile table by counting the followed_id on follow table.
mysql> explain follow;
+-------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| id | int(11) | NO | | NULL | |
| followed_id | int(11) | NO | | NULL | |
| follower_id | int(11) | NO | | NULL | |
+-------------+---------+------+-----+---------+-------+
And
mysql> explain profile;
+----------------+---------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(10) | NO | MUL | 0 | |
| followers | int(7) | NO | | 0 | |
| following | int(7) | NO | | 0 | |
+----------------+---------------+------+-----+-------------------+----------------+
Here is the query that I came up with:
UPDATE profile A
INNER JOIN (SELECT id,COUNT(*) idcount FROM follow GROUP BY id) as B
ON B.id = A.user_id
SET A.followers = B.idcount
But the query does not work as it should. It adds only 1 when profile has followers.
How can I fix this?

You are currently counting the number of rows for each id value in follow, which is always going to be 1. What you need to do is count the number of follower_id values for each followed_id. Also, as #juergend pointed out, you should use a LEFT JOIN so that you can get 0 values for users with no followers. Change your query to this:
UPDATE profile A
LEFT JOIN (SELECT followed_id, COUNT(DISTINCT follower_id) AS idcount
FROM follow
GROUP BY followed_id) as B ON B.followed_id = A.user_id
SET A.followers = COALESCE(B.idcount, 0)
You can use a similar query to update following:
UPDATE profile A
LEFT JOIN (SELECT follower_id, COUNT(DISTINCT followed_id) AS idcount
FROM follow
GROUP BY follower_id) as B ON B.follower_id = A.user_id
SET A.following = COALESCE(B.idcount, 0)

Related

SQL query on a JOINED table with multiple conditions

I have two sql tables: The wall table and the tag table. Each of them is linked with has_and_belongs_to_many relationship. Also the tag table has unique names.
Here are the tables in sql
mysql> describe tags;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | UNI | NULL | |
| count | int(11) | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
mysql> describe tags_walls;
+---------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------+------+-----+---------+-------+
| tag_id | bigint(20) | NO | MUL | NULL | |
| wall_id | bigint(20) | NO | | NULL | |
+---------+------------+------+-----+---------+-------+
mysql> describe walls;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
I am in rails 5 and i want to query a wall that has multiple tags.
I'm trying to do
result = Wall.all.includes(:tags).where(tags: {name: 'TAG1'})
result = result.where(tags: {name: 'TAG2'})
and the query that is constructed by rails is
SELECT DISTINCT `walls`.`id`
FROM `walls`
LEFT OUTER JOIN `tags_walls` ON `tags_walls`.`wall_id` = `walls`.`id`
LEFT OUTER JOIN `tags` ON `tags`.`id` = `tags_walls`.`tag_id`
WHERE `tags`.`name` = 'TAG1' AND `tags`.`name` = 'TAG2'
It should give me multiple walls as a results but the return is #<ActiveRecord::Relation []>
I want to build a custom sql query and just do a
Wall.includes(:tags).where query
How can i do a WHERE query on a joined table with multiple conditions linked by an AND ?
I would write this as:
SELECT tw.id
FROM tags_walls tw JOIN
tags t
ON t.id = tw.tag_id
WHERE t.name IN ('TAG1', 'TAG2')
GROUP BY tw.id
HAVING COUNT(*) = 2;
This assumes that tags are not duplicated on a wall. If that is possible, then use COUNT(DISTINCT t.name) = 2.
Notes:
walls is not needed, so that JOIN is removed.
You are looking for matches, so INNER JOIN is more appropriate than LEFT JOIN.
Table aliases make the query easier to write and to read.
Unnecessary backticks make the query harder to write and to read.
SELECT w.id
FROM walls w
JOIN tags_walls tw
ON tw.wall_id = w.id
JOIN tags t
ON t.id = tw.tag_id
AND t.name IN('TAG1','TAG2')
GROUP
BY w.id
HAVING COUNT(DISTINCT t.name) = 2 -- where '2' equals the number of arguments in IN()
WHERE tags.name = 'TAG1' OR tags.name = 'TAG2'
or
WHERE tags.name IN ('TAG1','TAG2')

Find unique id from two tables and corresponding counts from both tables

I am running a mysql database. I have two tables that store the likes and comments on posts respectively.
Likes table:
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| post_id | int(11) | NO | | NULL | |
| user_id | varchar(255) | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+--------------+------------------+------+-----+---------+----------------+
Comments Table:
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| post_id | int(11) | NO | | NULL | |
| user_id | varchar(255) | NO | | NULL | |
| comment | varchar(255) | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+--------------+------------------+------+-----+---------+----------------+
I need to get unique user ids and their likes and comment counts.
I ran the following query:
select * from
(select user_id, count(*) as like_count from post_likes group by user_id) as a,
(select user_id, count(*) as comment_count from post_comments group by user_id) as b
limit 5
The query took 66 seconds on 1.8 million records (both tables combined). Also, it didn't give correct result.
If I do a LEFT JOIN, it returns only matching records from the left table, whereas I need from both, i.e. if a user id has only liked and not commented or only commented and not liked.
What I am trying to get is something like:
+--------------+------------------+------+-----+---------+
| user_id | like_count | comment_count |
+--------------+------------------+------+-----+---------+
| 1 | 1213 | 310 |
| 2 | 1098 | 0 |
| 3 | 0 | 115 |
+--------------+------------------+------+-----+---------+
An ideal way would be to LEFT JOIN on user table, e.g.:
SELECT u.user_id, COUNT(l.id), COUNT(c.id)
FROM users u LEFT JOIN Likes l ON u.user_id = l.user_id
LEFT JOIN Comments c ON u.user_id = c.user_id;
Do a UNION ALL instead, and then merge the rows:
select user_id, sum(like_count), sum(comment_count)
from
(
select user_id, count(*) as like_count, null as comment_count
from post_likes
group by user_id
union all
select user_id, null, count(*)
from post_comments
group by user_id
) as dt
group by user_id
One way is to use UNION ALL and conditional aggregation:
SELECT user_id,
SUM(type='likes') AS like_count,
SUM(type='comments') AS comment_count
FROM (
SELECT user_id, 'likes' AS type
FROM post_likes
UNION ALL
SELECT user_id, 'comments' AS type
FROM post_comments) AS t
GROUP BY user_id
This query will not return users having no likes and no comments.

How to return NULL values when joining multiple tables

here are the contents of the tables.
mysql> desc student;
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| name | varchar(20) | NO | | NULL | |
| sex | enum('F','M') | NO | | NULL | |
| student_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+------------+------------------+------+-----+---------+----------------+
mysql> desc grade_event;
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| date | date | NO | | NULL | |
| category | enum('T','Q') | NO | | NULL | |
| event_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+----------+------------------+------+-----+---------+----------------+
mysql> desc score;
+------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+-------+
| student_id | int(10) unsigned | NO | PRI | NULL | |
| event_id | int(10) unsigned | NO | PRI | NULL | |
| score | int(11) | NO | | NULL | |
+------------+------------------+------+-----+---------+-------+
What I'm trying to accomplish is to display which students missed a quiz/test, found under 'category' in the grade_event table.
Here's what I've come up with, but am not generating any results;
select name, category, sc.event_id
from student s
join score sc on s.student_id=sc.student_id
join grade_event ge on sc.event_id=ge.event_id
where score is NULL
group by name, event_id;
I've also gone the route of attempting a subquery;
select name, category, sc.event_id
from student s
join score sc on s.student_id=sc.student_id
join grade_event ge on sc.event_id=ge.event_id
where score not in (select score from score)
group by name, event_id;
Any help would be appreciated.
I think you should just replace your join by left join, join is an inner join in MySQL : http://dev.mysql.com/doc/refman/5.7/en/join.html
And take care with group by event_id, it can be useful to precise group by sc.event_id. I don't know in MySQL but in sql server it wouldn't work.
Your second query is necessarily empty as you ask a column to have its values not in its values :)
You need to use an outer join instead of an inner join to get list of students that do not have a corresponding record in the events after creating a carthesian join of students and events:
select name, category, ge.event_id
from (student s
join grade_event ge) --no join condition creates a carthesian join
left join score sc on s.student_id=sc.student_id and sc.event_id=ge.event_id
where sc.score is NULL

EAV - Add rows with null value if row does not exist

I have the following table storing data in the EAV model:
+-------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| user_id | int(11) | NO | MUL | NULL | |
| question_id | int(11) | NO | MUL | NULL | |
| answer | blob | YES | | NULL | |
+-------------+---------+------+-----+---------+-------+
With a table to hold the different types of question:
+----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| question | blob | YES | | NULL | |
+----------+---------+------+-----+---------+----------------+
As well as a users table:
+------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_name | varchar(128) | YES | | NULL | |
+------------------+--------------+------+-----+---------+----------------+
How would I write a query to insert a row with a null value for answer for each question_id that each user does not currently have a row for?
For example, if I have question_ids 1,2,3,4 and my table storing data looks like:
+--------------+---------------+--------+
| user_id | question_id | answer |
+--------------+---------+-----+--------+
| 1 | 1 | example|
| 1 | 3 | example|
| 1 | 4 | example|
+--------------+---------+-----+--------+
I want to insert a row that looks like :
+--------------+---------------+--------+
| user_id | question_id | answer |
+--------------+---------+-----+--------+
| 1 | 2 | NULL |
+--------------+---------+-----+--------+
I tried something like this:
INSERT INTO profile_answers
(
user_id,
question_id,
answer
)
SELECT
id,
profile_answers.question_id,
null
FROM users
LEFT JOIN profile_answers ON profile_answers.user_id = users.id
WHERE NOT EXISTS (
SELECT answer
FROM profile_answers
WHERE user_id = id
AND question_id IN (
SELECT GROUP_CONCAT(id SEPARATOR ',') FROM profile_questions
)
)
But I ended up inserting rows with a question id of 0.
I've given this some thought, but I couldn't find anything better than using a cartesian product between users and questions, and a filtering subquery:
SELECT u.id, q.id
FROM users u,
questions q
WHERE NOT EXISTS(
SELECT *
FROM profile_answers a
WHERE a.question_id = q.id AND a.user_id = u.id
);
Demo

Join 2 select queries with 2 results into one query with one result?

What I try to accomplish is that I want to select one row from tableA and one row from tableB and combine the results (comlumn names are different).
The layout of tableA is this:
+----------------------------+--------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------------+--------------+------+-----+---------------------+----------------+
| user_id | int(11) | NO | PRI | NULL | auto_increment |
| nickname | varchar(32) | NO | | NULL | |
| password | varchar(129) | NO | | NULL | |
| mafia_id | int(11) | NO | | 0 | |
+----------------------------+--------------+------+-----+---------------------+----------------+
and of tableB this:
+-------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+----------------+
| mafia_id | int(11) | NO | PRI | NULL | auto_increment |
| mafia_name | varchar(32) | NO | | | |
| mafia_tag | varchar(5) | NO | | | |
| mafia_color | int(11) | NO | | 0 | |
| mafia_car | int(11) | NO | | 0 | |
| mafia_base | int(11) | NO | | 0 | |
+-------------+-------------+------+-----+---------+----------------+
I tried queries like:
SELECT *
FROM users
WHERE users.nickname = 'Gamer_Z'
UNION
SELECT * FROM mafia
WHERE mafia.mafia_id = (
SELECT users.mafia_id
FROM users
WHERE users.nickname = 'Gamer_Z'
);
and this:
SELECT *
FROM users
WHERE nickname = 'Gamer_Z'
INNER JOIN mafia ON mafia.mafia_id = users.mafia_id;
But those queries give errors, I tried several others but these ones look, well, good?
I read these questions too:
How can I join two tables with different number of rows in MySQL?
Combine Multiple Query Results in MySQL (by column)
But I can't seem to fix my problem.
The output format I would like is this:
|users.user_id|users.nickname|users.password|users.mafia_id|mafia.mafia_name|mafia.mafia_tag|mafia.mafia_color|mafia.mafia_car|mafia.mafia_base|
How can I build the correct query for what I try to accomplish in MySQL(5)?
Thanks in advance!
\EDIT::
I almost forgot one thing! IF the mafia_id doesn't exists in tableB then there stil has to be the tableA row retured. (so the user is not in a mafia, I just load the default user data)
You have to put the WHERE condition after the JOIN, not before it:
SELECT *
FROM users a
LEFT JOIN mafia b ON a.mafia_id = b.mafia_id
WHERE a.nickname = 'Gamer_Z'
the join is wrong
should be
SELECT *
FROM users a
JOIN mafia b ON a.mafia_id = b.mafia_id and a.nickname = 'Gamer_Z'