Query to select from multiple tables MySQL - mysql

I have two tables. My task is to choose the last person comment.
db_user (db_user_id, name, balance)
db_comment (db_comment_id, db_user_id, text)
My query:
SELECT db_user.name,db_comment.text
FROM db_user INNER JOIN db_comment ON db_user.db_user_id = db_comment.db_user_id
ORDER BY db_comment.db_user_id DESC
Tried to use LIMIT but failed.
A table with values ​​has already been created here: http://sqlfiddle.com/#!9/badaf/14
My data sampling should receive the last comment (db comment.text) from each person (db_user.name).
Сondition, you cannot add new fields.

You can use the following solution using a additional JOIN:
SELECT dbu.name, dbc.text
FROM db_user dbu INNER JOIN (
SELECT MAX(db_comment_id) AS db_comment_id, db_user_id
FROM db_comment
GROUP BY db_user_id
) dbc_max ON dbu.db_user_id = dbc_max.db_user_id
INNER JOIN db_comment dbc ON dbu.db_user_id = dbc_max.db_user_id
AND dbc.db_comment_id = dbc_max.db_comment_id
ORDER BY dbu.db_user_id DESC
... or using a sub-select directly on the SELECT:
SELECT dbu.name, (
SELECT `text`
FROM db_comment dbc
WHERE dbu.db_user_id = dbc.db_user_id
ORDER BY dbc.db_comment_id DESC
LIMIT 1
) AS `text`
FROM db_user dbu
ORDER BY dbu.db_user_id DESC
demo on dbfiddle.uk

I would use a correlated subquery for filtering. In many cases, this is approach that has the best performance, especially with an index on db_comment(db_user_id, db_comment_id):
select u.name, c.text
from
db_user u
inner join db_comment c on c.db_user_id = u.db_user_id
where c.db_comment_id = (
select max(c1.db_comment_id)
from db_comment c1
where c1.db_user_id = c.db_user_id
)
This assumes that the last comment is the one that has the highest db_comment_id.
Updated demo on DB Fiddle
name text
Ivan Message3 ivan
Petr Message3 Petr
Artur Message2 Artur
John Message2 John

The last comment is the one with the highest ID. So, make sure there doesn't exist a higher one for the user:
SELECT
u.name,
c.text
FROM db_user u
JOIN db_comment c ON c.db_user_id = u.db_user_id
AND NOT EXISTS
(
SELECT *
FROM db_comment c2
WHERE c2.db_user_id = c.db_user_id
AND c2.db_comment_id > c.db_comment_id
);
Or work with a list of highest comment IDs per user:
SELECT
u.name,
c.text
FROM db_user u
JOIN db_comment c ON c.db_user_id = u.db_user_id
AND (c.db_user_id, c.db_comment_id) IN
(
SELECT db_user_id, MAX(db_comment_id)
FROM db_comment
GROUP BY db_user_id
);
As of MySQL 8 you can also use window function. E.g.:
SELECT
u.name,
c.text
FROM db_user u
JOIN
(
SELECT
db_user_id,
text,
ROW_NUMBER() OVER (PARTITION BY db_user_id ORDER BY db_comment_id DESC) AS rn
FROM db_comment
) c ON c.db_user_id = u.db_user_id AND c.rn = 1;
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=2c2c67cdba80a992b593e4c74201fa61

Try it -
SELECT db_user.name,db_comment.text
FROM db_user
INNER JOIN db_comment
ON db_user.db_user_id = db_comment.db_user_id
ORDER BY db_user.db_user_id DESC, db_comment.db_comment_id desc
limit 1
OR
SELECT db_user.name,db_comment.text
FROM db_user
JOIN (
select db_comment_id as maxId, db_user_id, text
from db_comment
order by db_comment_id desc
limit 1
) as db_comment ON db_comment.db_user_id = db_user.db_user_id
ORDER BY db_user.db_user_id DESC`
if the limit is not working then you can use the MAX function and make join self table

Other approach without the need to use MAX(db_comment_id) or GROUP BY db_user_id and complety works by filtering
Query
SELECT
db_user.name
, db_comment1.text
FROM
db_comment db_comment1
LEFT JOIN
db_comment db_comment2
ON
db_comment1.db_user_id = db_comment2.db_user_id
AND
db_comment1.db_comment_id < db_comment2.db_comment_id
INNER JOIN
db_user
ON
db_comment1.db_user_id = db_user.db_user_id
WHERE
db_comment2.db_user_id IS NULL
Note: Using db_comment1.db_comment_id > db_comment2.db_comment_id instead would do MIN(db_comment_id) yes the operator direction can feel pretty bit counterintuitive and it is very easy to get wrong and write the wrong one (that's why i needed to edit mine answer...) , see demo..
Result
| name | text |
|-------|----------------|
| Ivan | Message3 ivan |
| Petr | Message3 Petr |
| Artur | Message2 Artur |
| John | Message2 John |
Performance note: it needs to have the INDEX(db_user_id, db_comment_id) on the db_comment table otherwise it will not be very fast. If you have that index, MySQL should be able to handle (very) large tables when running this query..
see demo

Related

Improving a SQL query when I need to select IN multiple fields combined together

Here is my SQL query
SELECT date(metrics_session.created_at) as day, COUNT(metrics_session.user_id) as total_logins,
sum(TIMESTAMPDIFF(MINUTE,metrics_session.created_at,metrics_session.completed_at)) as total_time_spent
FROM metrics_session
inner join metrics_training on metrics_training.id = metrics_session.training_id
inner join metrics_course on metrics_course.id = metrics_training.course_id
inner join metrics_user_training_cohort on metrics_training.id = metrics_user_training_cohort.training_id
inner join auth_user on auth_user.id = metrics_user_training_cohort.user_id
WHERE metrics_session.created_at >= '2021-01-15'
AND metrics_session.created_at <= '2022-10-15'
AND metrics_session.completed_at IS NOT NULL
AND metrics_session.user_id In (SELECT user_id from metrics_user_training_cohort where user_id = 44 and training_id = 4)
AND metrics_session.training_id In (SELECT training_id from metrics_user_training_cohort where user_id = 44 and training_id = 4)
#AND EXISTS(SELECT user_id,training_id from metrics_user_training_cohort where user_id = 44 and training_id = 4)
GROUP BY date(metrics_session.created_at) ORDER BY date(metrics_session.created_at)
the goal of this query is to select the sessions that were created by some user_id and are linked to some training_id , but only if in the table metrics_user_training_cohort I have that same user_id and training_id in the same row registered.
I managed to achieve that with the 2 last lines before GROUP BY:
AND metrics_session.user_id In (SELECT user_id from metrics_user_training_cohort where user_id = 44 and training_id = 4)
AND metrics_session.training_id In (SELECT training_id from metrics_user_training_cohort where user_id = 44 and training_id = 4)
however the repeated subquery used for the IN statement seems unnecessary to me and likely degrading performance, but I can't quite figure out a better way since the IN statement can only be used with 1 column.
The commented line is not the solution because it just checks for the existence of the row in the table in isolation without association to the sessions, but I left it there to give you a better idea what I'm trying to achieve.
It is usually better to use JOIN or EXISTS instead of IN ( SELECT ... )
These may help:
metrics_session: INDEX(created_at, completed_at, user_id, training_id)
metrics_user_training_cohort: INDEX(training_id, user_id)

How to count rows that aren't match between two tables in MySQL?

I have the following tables:
Users:
ID LastPaymentDate
1 2017-01-01
2 2018-02-05
3 2018-04-06
5 NULL
ActivityLog:
ID ActivityDate
1 2017-01-01
1 2017-05-17
3 2018-05-20
I need to find out the number of users that have LastPaymentDate but doesn't have matched ActivityDate
The output result for the above data is: 2 (UserID 3 and 2).
How can I do this?
We can try using a left join approach here:
SELECT u.ID, u.LastPaymentDate
FROM Users u
LEFT JOIN ActivityLog a
ON u.ID = a.ID AND u.LastPaymentDate = a.ActivityDate
WHERE
a.ID IS NULL AND u.LastPaymentDate IS NOT NULL;
Demo
Use NOT EXISTS:
SELECT COUNT(*)
FROM Users u
WHERE
u.LastPaymentDate IS NOT NULL
AND NOT EXISTS (
SELECT 1
FROM ActivityLog a
WHERE u.ID = a.ID AND u.ActivityDate = a.ActivityDate
)
The good thing about this approach is that it will not count several times the same record in Users, even if it has several matching record in the ActivityLog.
I had a problem similar to this. My resolution was to create views to display the fields and the count as columns. Then I did a join between the views to display the net results:
CREATE ALGORITHM=UNDEFINED DEFINER=MySQL CURRENT_USER() SQL SECURITY DEFINER VIEW `subcr_count_x` AS SELECT `x_subscriptions`.`user_id` AS `user_id`, count(0) AS `cnt` FROM `x_subscriptions` WHERE (`x_subscriptions`.`user_id` > 0) GROUP BY `x_subscriptions`.`user_id` ;
CREATE ALGORITHM=UNDEFINED DEFINER=MySQL CURRENT_USER() SQL SECURITY DEFINER VIEW `subcr_count_y` AS SELECT `y_subscriptions`.`user_id` AS `user_id`, count(0) AS `cnt` FROM `y_subscriptions` WHERE (`y_subscriptions`.`user_id` > 0) GROUP BY `y_subscriptions`.`user_id`;
To select the records where there isn't a match, it does this.
SELECT * FROM
`subcr_count_x` x INNER JOIN
`subcr_count_y` y ON x.user_id = y.user_id
WHERE x.cnt != y.cnt

Can't access variable in inner scope?

I'm trying to get this piece of SQL query to work. The problem is that I can't access core_user.id in the stuff in its parameters. I don't really know why.
SELECT
core_user.id AS target, (
SELECT
COUNT(permission_id) AS permissions
FROM (
SELECT
DISTINCT permission_id
FROM (
SELECT
id,
permission_id
FROM
core_user_user_permissions
WHERE
core_user_user_permissions.user_id = core_user.id
UNION
SELECT
id,
permission_id
FROM
auth_group_permissions
WHERE
auth_group_permissions.group_id IN (
SELECT
group_id
FROM
core_user_groups
WHERE
core_user_groups.user_id = core_user.id)) AS `permissions`) AS `derived`) AS `perms`
FROM
`core_user`
WHERE
`core_user`.`is_active` = TRUE
GROUP BY
`core_user`.`id`
ORDER BY
`perms` ASC
If i try target.id with core_user AS target it does not work either: Unknown column 'target.id' in 'where clause'
Expected Results:
+----------------------------------+-------+
| id | perms |
+----------------------------------+-------+
| ab7ec54bf9124dffb807fb89f9ea8036 | 0 |
| b54d4d3f97134dfcbc36ac193c0c1250 | 81 |
| c69ffa4d162b49129ff6a316da3caaa3 | 64 |
| f8ac73eee80044359c246f3b173aa631 | 0 |
+----------------------------------+-------+
Any idea how to fix this?
It's not easy to understand what one wants, if there is only an (invalid) query and no description of what it should retrieve. So this is just a best guess:
SELECT cu.id target,
count(x.permission_id) perms
FROM core_user cu
LEFT JOIN (SELECT cuup.permission_id,
cuup.user_id
FROM core_user_user_permissions cuup
WHERE cuup.permission_id IS NOT NULL
UNION
SELECT agp.permission_id,
cug.user_id
FROM auth_group_permissions agp
INNER JOIN core_user_groups cug
ON cug.group_id = agp.group_id
WHERE agp.permission_id IS NOT NULL) x
ON x.user_id = cu.id
WHERE cu.is_active = TRUE
GROUP BY cu.id
ORDER BY perms ASC;
Instead of getting the count in a subselect it left joins the permission IDs for each user. Grouping by the user ID than gets the (distinct, because the union already removed any duplicates and id is also unique in core_user (I assume)) count of the non null permission IDs of a user, which is what I believe you want.
(You may remove the WHERE <alias>.permission_id IS NOT NULL in the inner query, if there are not null constraints on the columns. The idea here is, that nulls aren't counted in the end anyway, so we want to discard them as early as possible.)
(Untested, as neither schema nor sample data was provided. May contain typos.)
I would rewrite it as:
SELECT core_user.id AS target,
(SELECT COUNT(permission_id) AS permissions
FROM (SELECT permission_id
FROM core_user_user_permissions
WHERE core_user_user_permissions.user_id = `core_user`.id
UNION
SELECT permission_id
FROM auth_group_permissions
WHEREauth_group_permissions.group_id IN (
SELECT group_id
FROM core_user_groups
WHERE core_user_groups.user_id = `core_user`.id)
) AS `derived`
) AS `perms`
FROM `core_user`
WHERE `core_user`.`is_active` = TRUE
GROUP BY `core_user`.`id`
ORDER BY `perms`;
I've removed one level of nested subquery in SELECT list.
Well I hope I could get your expected result by excluding group premissons:
SELECT core_user.id AS target,
(
SELECT COUNT(DISTINCT(permission_id)) AS permissions
FROM
(
SELECT cuup.id as id, cuup.permission_id as permission_id
FROM core_user_user_permissions cuup
-- WHERE cuup.user_id = cu.id
--VV-- this should be a join
JOIN core_user cu ON cu.id = cuup.user_id
)
) AS `perms`
FROM `core_user`
WHERE `core_user`.`is_active` = TRUE
GROUP BY `core_user`.`id`
ORDER BY `perms` ASC

repeated rows in json_agg() in query with 2 lateral joins

I have a strange result when performing a lateral join on a query
I have the following table structure
task->id
comment -> id , taskId, comment
tasklink -> taskId, type, userid
with a single task record (id 10), 1 comment record ("row1", "a test comment") and 5 tasklink records (all with taskid 10)
I expected this query
select task.id,
json_agg(json_build_object('id',c.id, 'user',c.comment)) as comments,
json_agg(json_build_object('type',b.type, 'user',b.userid)) as users
FROM task
left join lateral (select c.* from comment c where task.id = c.taskid) c on true
left join lateral (select b.* from taskuserlink b where task.id = b.taskid) b on true
where task.id = 10
GROUP BY task.id ;
to return
id | comments | users
---------------------------------------------------------------------
10 "[{"id":"row1","user":"a test comment"}]" "[{"type":"updatedBy","user":1},"type":"closedBy","user":5},"type":"updatedBy","user":5},"type":"createdBy","user":5},{"type":"ownedBy","user":5}]"
instead, I got this
id | comments | users
10 "[{"id":"row1","user":"a test comment"},{"id":"row1","user":"a test comment"},{"id":"row1","user":"a test comment"},{"id":"row1","user":"a test comment"},{"id":"row1","user":"a test comment"}]" "[{"type":"updatedBy","user":1},{"type":"closedBy","user":5},{"type":"updatedBy","user":5},{"type":"createdBy","user":5},{"type":"ownedBy","user":5}]"
ie , for every link row, the comment row is duplicated
I am thinking that I am missing something really obvious, but as I have only just started using Postgres (and sql ) I'm a little stumped
I would appreciate some guidance on where I'm going wrong
Move the aggregates into subqueries:
select id, comments, users
from task t
left join lateral (
select json_agg(json_build_object('id',c.id, 'user',c.comment)) as comments
from comment c
where t.id = c.taskid
) c on true
left join lateral (
select json_agg(json_build_object('type',b.type, 'user',b.userid)) as users
from taskuserlink b
where t.id = b.taskid
) b on true
DbFiddle.

How can I get top users in specific tags?

I have a function like this:
CREATE DEFINER=`root`#`localhost` FUNCTION `user_top_tags`(`user_id` INT, `tags_num` TINYINT(1)) RETURNS varchar(50) CHARSET utf8mb4
NO SQL
BEGIN
DECLARE top_tags varchar(50);
SELECT substring_index(group_concat(x.name ORDER BY x.tag_score DESC SEPARATOR ','), ',', tags_num) INTO top_tags
FROM (
SELECT t.name, sum(r.score) AS tag_score
FROM reputations r
JOIN qanda_tags qt ON qt.qanda_id = r.question_id
JOIN tags t ON t.id = qt.tag_id
WHERE r.owner_id = user_id
GROUP BY t.name
) x;
RETURN top_tags;
END
And I call it like this:
SELECT u.name, user_top_tags(u.id, 3) FROM users u WHERE 1;
And it returns a list of users with their top three tags. Something like this:
+--------+-----------------+
| Jack | php,oop,mysql |
| Martin | css,js,html |
| Peter | jquery,js,react |
+--------+-----------------+
Now I want to get the users which has active in specific tags. Something like top users page in SO (which is for javascript tag, but I want to get list of users in multiple tags, like IN ('css','html')).
Now should I do a join on the query? Or should I modify the function? Does anybody have any idea how can I do that?
SELECT substring_index(group_concat(x.name ORDER BY x.tag_score DESC SEPARATOR ','), ',', 3) as top_tags,x.username
FROM (
SELECT u.name username,t.name, sum(r.score) AS tag_score
FROM reputations r
JOIN qanda_tags qt ON qt.qanda_id = r.question_id
JOIN tags t ON t.id = qt.tag_id
INNER JOIN users u ON u.id = r.owner_id
GROUP BY t.name,u.name
having u.active < 10
) x;
Instead of writing function,you can achieve same business logic using simple query. Then obviously go with that query only.
You can try above query.
Here I had consider that you have some column which specify that user is active from how many days.