Mysql - Run a user query for all users - mysql

I have a user specific query which i need to run for all users.
I am struggling on how to replace the hard coded uuid with a reference or if it needs a different approach altogether?
select max(MaxDate), users.created_at
from (
select max(`moment`.`created_at`) as MaxDate
from `moment`
where `moment`.`user_uuid` = "7dd668af-241a-4176-a1da-f5689214b206"
union (
select max(`module`.`updated_at`) as MaxDate
from `module`
where `module`.`user_uuid` = "7dd668af-241a-4176-a1da-f5689214b206"
)
) as stuff, `users`
where `users`.`uuid` = "7dd668af-241a-4176-a1da-f5689214b206"
the end goal is to get the date a user was created and a date the same user last updated something and then get the avage time between them. But for all users not a single user.

Here is a general query which would report all users, sorted by user:
SELECT
u.user_uuid,
GREATEST(COALESCE(t1.max_created_at, t2.max_updated_at),
COALESCE(t2.max_updated_at, t1.max_created_at)) AS max_date
FROM users u
LEFT JOIN
(
SELECT user_uuid, MAX(created_at) AS max_created_at
FROM moment
GROUP BY user_uuid
) t1
ON u.user_uuid = t1.user_uuid
LEFT JOIN
(
SELECT user_uuid, MAX(updated_at) AS max_updated_at
FROM module
GROUP BY user_uuid
) t2
ON u.user_uuid = t2.user_uuid
ORDER BY
u.user_uuid;
If you want to restrict to a single user, you may still do so via a WHERE clause or via a WHERE IN clause for a group of users.
Note that there is a bit of a smell in your database design, because you have your user information strewn across multiple tables. My answer assumes that in general every user would appear in both tables, but maybe this is not the case.

Use group by
select `users`.`uuid`,max(MaxDate) as maxdate, min(users.created_at) as createddate
from (
select `moment`.`user_uuid`,max(`moment`.`created_at`) as MaxDate
from `moment`
group by `moment`.`user_uuid`
union
select `module`.`user_uuid`,max(`module`.`updated_at`) as MaxDate
from `module` group by `module`.`user_uuid`
) as stuff inner join `users` on `users`.`uuid`=stuff.user_uuid
group by `users`.`uuid`

Related

Creating a SQL view with personal best records

I have the following SQL Database structure:
Users are the registered users. Maps are like circuits or race tracks. When a user is driving a time a new time record will be created including the userId, mapId and the time needed to finish the racetrack.
I wish to create a view where all the users personal bests on all maps are listed.
I tried creating the view like this:
CREATE VIEW map_pb AS
SELECT MID, UID, TID
FROM times
WHERE score IN (SELECT MIN(score) FROM times)
ORDER BY registered
This does not lead to the wished result.
Thank you for your help!
I hope that you have 'times' table created as the above diagram and 'score' column in the table that you use to measure the best record.
(MIN(score) is the best record).
You can simply create a view to have the personal best records using sub-queries like this.
CREATE VIEW map_pb AS
SELECT a.MID, a.UID, a.TID
FROM times a
INNER JOIN (
SELECT TID, UID, MIN(score) score
FROM times
GROUP BY UID
) b ON a.UID = b.UID AND a.score= b.score
-- if you have 'registered' column in the 'times' table to order the result
ORDER BY registered
I hope this may work.
You probably need to use a query that will first return the minimum score for each user on each map. Something like this:
SELECT UID,
MID,
MIN(score) AS best_time
FROM times
GROUP BY UID, MID
Note: I used MIN(score) as this is what is shown in your example query, but perhaps it should be MIN(time) instead?
Then just use the subquery JOINed to your other tables to get the output:
SELECT *
FROM (
SELECT UID,
MID,
MIN(score) AS best_time
FROM times
GROUP BY UID, MID
) a
INNER JOIN users u ON u.UID = a.UID
INNER JOIN maps m ON m.MID = a.MID
Of course, replace SELECT * with the columns you actually want.
Note: code untested but does give an idea as to a solution.
Start with a subquery to determine each user's minimum score on each map
SELECT UID, TID, MIN(time) time
FROM times
GROUP BY UID, TID
Then join that subquery into a main query.
SELECT times.UID, times.TID,
mintimes.time
FROM times
JOIN (
) mintimes ON times.TID = mintimes.TID
AND times.UID = mintimes.UID
AND times.time = mintimes.time
JOIN maps ON times.MID = maps.MID
JOIN users ON times.UID = users.UID
This query pattern uses a GROUP BY function to find the outlying (MIN in this case) value for each combination. It then uses that subquery to find the detail record for each outlying value.

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.

Multiple Many to Many Relationships JOINed in MySQL

For a project I'm working on, I am trying to query a time clock but when I LEFT JOIN multiple many to many (or in a single users' record sense, 1 to many) it creates duplicate entries, so when it's grouped by, the aggregate totals are incorrect.
Given the below mock schema:
And a query:
SELECT
UserTbl.UserID,
CONCAT_WS(", ", UserTbl.LastName, UserTbl.FirstName) AS UserName,
SUM(TIMESTAMPDIFF(MINUTE, TimeClockTbl.StartDateTime, TimeClockTbl.EndDateTime)) AS ClockedInMinutes,
FROM
Users AS UserTbl
LEFT JOIN
TimeClock AS TimeClockTbl
ON UserTbl.UserID = TimeClockTbl.UserID
LEFT JOIN
UserRoles AS UserRoleTbl
ON UserTbl.UserID = UserRoleTbl.UserID
WHERE
UserRoleTbl.RoleID IN (1,2,3)
GROUP BY
UserTbl.UserID
ORDER BY
UserTbl.LastName ASC,
UserTbl.FirstName ASC;
If the user only has 1 role, assigned, it works fine, but if there is a second or third role assigned, it seems to multiply the final result. I considered using a GROUP_CONCAT for the roles and filtering after, but that doesn't seem to be efficient. I also considered subqueries to calculate the clocked in hours for a given user, but I felt that would have the same result. It's also important to note that this is scaled to have a TimeClock table with multiple entries, and a Scheduled table with multiple entries as well.
How can I do this with a decent amount of efficiency?
Simple decision:
SELECT UserTbl.UserID,
CONCAT_WS(", ", UserTbl.LastName, UserTbl.FirstName) AS UserName,
SUM(TIMESTAMPDIFF(MINUTE, TimeClockTbl.StartDateTime, TimeClockTbl.EndDateTime)) AS ClockedInMinutes,
FROM Users AS UserTbl
LEFT JOIN TimeClock AS TimeClockTbl ON UserTbl.UserID = TimeClockTbl.UserID
WHERE UserTbl.UserID IN( SELECT UserID FROM UserRoles WHERE RoleID IN (1,2,3) )
GROUP BY UserTbl.UserID
ORDER BY UserTbl.LastName ASC, UserTbl.FirstName ASC;
Concept for similar situations - consistent join:
SELECT A.*,
SUM(TIMESTAMPDIFF(MINUTE, TimeClockTbl.StartDateTime, TimeClockTbl.EndDateTime)) AS ClockedInMinutes,
MAX(A.RolesTitle) AS RolesTitle
FROM (
SELECT UserTbl.UserID,
CONCAT_WS(", ", UserTbl.LastName, UserTbl.FirstName) AS UserName,
FirstName, LastName,
GROUP_CONCAT(Roles.Title) as RolesTitle
FROM Users AS UserTbl
JOIN UserRoles AS UserRoleTbl ON UserTbl.UserID = UserRoleTbl.UserID
JOIN Roles ON Roles.RoleID=UserRoleTbl.RoleID
WHERE UserRoleTbl.RoleID IN (1,2,3)
GROUP BY UserTbl.UserID
) A
LEFT JOIN TimeClock AS TimeClockTbl ON A.UserID = TimeClockTbl.UserID
GROUP BY A.UserID
ORDER BY A.LastName ASC, A.FirstName ASC;

SQL query to fetch the last users who created content orderd by date

I have a oneToMany relationship between User entity and Post entity. Each user can create as muck posts as he want. Tables look like:
User(id, name)
Post(id, post_owner_id, content, date) // post_owner_id is the foreign key
My goal is to fetch the last users who created posts. I started by:
SELECT *
FROM `post`
ORDER BY post_date DESC
LIMIT 0 , 30
and it is giving me correct result. But when I add GROUP BY post_user_id, it shrinks the result to only one row per user but with unordered date. What am I missing?
I believe that I need a JOIN to get the final goal but I need first to find a solution for this first part. Your help is much appreciated.
I need a table of users with the date of their last post.
SELECT post_owner_id, max(date) as maxdt
FROM `post`
group by post_owner_id
order by maxdt desc
limit 0,30
select id, maxdt, name
from (
SELECT post_owner_id, max(date) as maxdt
FROM `post`
group by post_owner_id) t join `User` u on u.id = t.post_owner_id
You don't even need a sub query.
SELECT u.id, u.name, max(p.`date`) as maxdt
FROM `post` p
JOIN `User` u on u on u.id = p.post_owner_id
group by u.id, u.name

get the average time for time from subscription until payment

I have two tables. The first is subscribers. Subscribers are also appointed to a category. The second table is payments that the subscribers made. I want to know what the average time is between the time of subscription and the FIRST payment of a subscriber (the can make multiple).
Here is a piece of SQL, but it doesn't do what I want just yet - although I have the feeling I'm close ;)
SELECT category,
AVG(TIMESTAMPDIFF(HOUR, subs.timestamp, MIN(payments.timestamp)))
FROM subs
JOIN payments ON (payments.user_id = subs.user_id)
GROUP BY category
Now I get "Invalid use of group function" - because of the MIN function, so that ain't right. What do I have to do now? Thanks in advance!
SELECT category,
AVG(TIMESTAMPDIFF(HOUR, subs.timestamp, p.timestamp))
FROM subs
JOIN ( SELECT user_id
, min(timestamp) timestamp
FROM payments
GROUP BY user_id
) p
ON p.user_id = subs.user_id
GROUP BY category
If you needed to update another table with the results of this query, you could do something like this (not tested, so there may be syntax errors but hopefully you get the idea). I assume that another_table has category and avg_hrs_spent columns.
UPDATE another_table
SET avg_hrs_spent =
(
SELECT a.avg_hrs_spent FROM
(
(SELECT category,
AVG(TIMESTAMPDIFF(HOUR, subs.timestamp, p.timestamp)) avg_hrs_spent
FROM subs
JOIN ( SELECT user_id
, min(timestamp) timestamp
FROM payments
GROUP BY user_id
) p
ON p.user_id = subs.user_id
GROUP BY category) a
)
WHERE a.category = another_table.category
)