Using an SQL LEFT JOIN with the MAX() and MIN() functions - mysql

Let's assume I have the following two tables:
CREATE TABLE users (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
name CHAR(30) NOT NULL,
PRIMARY KEY (id)
) ENGINE=MyISAM;
CREATE TABLE logins (
user_id NOT NULL,
day DATE NOT NULL,
PRIMARY KEY (`user_id, `day`)
) ENGINE=MyISAM;
What I'm trying to do here is get a query for all users with the first day they logged in and the last day they logged in. The query I was executing to achieve this looks like the following:
SELECT u.id AS id, u.name AS name, MIN(l.day) AS first_login,
MAX(l.day) AS last_login
FROM users u
LEFT JOIN logins l ON u.id = l.user_id
The problem is that because of the use of MIN() and MAX(), I'm only receiving one row back in the entire result. I'm sure it's my use of those functions that's causing this. I should have one row per user, even if they do not have any login entries. This is the reason for me using a LEFT JOIN vs an INNER JOIN.

in order to use aggregate functions (min, max, ...) you need grouping. Try something like this:
SELECT u.id AS id, u.name AS name, MIN(l.day) AS first_login, MAX(l.day) AS last_login
FROM users u
LEFT JOIN logins l ON u.id = l.user_id
GROUP BY u.id

Any sensible database except MySQL would have given you an error on mixing row-terms and aggregate terms, making the error clearer. MySQL, unfortunately allows this behavior, making it harder to notice that you forgot the group by clause needed to generate a row per user:
SELECT u.id AS id,
u.name AS name,
MIN(l.day) AS first_login,
MAX(l.day) AS last_login
FROM users u
LEFT JOIN logins l ON u.id = l.user_id
GROUP BY u.id, u.name -- missing in the original query

Grouping is a waste of resources.
Use nested select statement instead.
eg.
SELECT
u.id AS id,
u.name AS name,
(
SELECT MAX(logins.day) FROM logins WHERE logins.user_id=u.id
) AS last_login
FROM users u;

MIN and MAX are aggregate functions.
You should use GROUP BY with some field from u, like id.

Related

How can I get customer data based on the number of users they have?

I want to get customer data from all the businesses with more than 1 user.
For this I think I need a subquery to count more than 1 user and then the outer query to give me their emails.
I have tried subqueries in the WHERE and HAVING clause
SELECT u.mail
FROM users u
WHERE count IN (
SELECT count (u.id_business)
FROM businesses b
INNER JOIN users u ON b.id = u.id_business
GROUP BY b.id, u.id_business
HAVING COUNT (u.id_business) >= 2
)
I believe that you do not need a subquery, everything can be achieved in a joined aggregate query with a HAVING clause, like :
SELECT u.mail
FROM users u
INNER JOIN businesses b on b.id = u.id_business
GROUP BY u.id, u.email
HAVING COUNT (*) >= 2
NB : in case several users may have the same email, I have added the primary key of users to the GROUP BY clause (I assumed that the pk is called id) : you may remove this if email is a unique field in users.

Mysql - Run a user query for all users

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`

what is the query equivalence in postgresql

what is the equivalence query on postgresql?
Build database and tables
CREATE DATABASE IF NOT EXISTS lemonade;
use lemonade;
CREATE TABLE users (
id int PRIMARY KEY NOT NULL auto_increment,
name varchar(50) NOT NULL,
email varchar(50) NOT NULL
);
CREATE TABLE memories (
id int PRIMARY KEY NOT NULL auto_increment,
content varchar(50) NOT NULL,
userID int,
FOREIGN KEY (userID) REFERENCES users(id)
);
INSERT INTO users (name, email) VALUES ("Ruan", "ruan#gmail.com");
INSERT INTO users (name, email) VALUES ("Pereira", "pereira#gmail.com");
INSERT INTO memories (content, userID) VALUES ("memoria 1", 1);
INSERT INTO memories (content, userID) VALUES ("memoria 2", 1);
INSERT INTO memories (content, userID) VALUES ("memoria 3", 2);
INSERT INTO memories (content, userID) VALUES ("memoria 4", 2);
query on mysql:
select ANY_VALUE(m.id), ANY_VALUE(m.content), m.userID, ANY_VALUE(u.id), ANY_VALUE(u.name), ANY_VALUE(u.email) from memories m inner join users u on m.userID = u.id group by userID;
result:
image with result of my query
query on postgresql:
?
result: expect result equal of up image
The ANY_VALUE function in MySQL is used when a query is doing a GROUP BY aggregation, but columns are being selected which are not mentioned in the GROUP BY clause nor appear inside aggregate functions. In the context of your query, this means that the only columns which can be selected are the userID or another column inside an aggregate function like MAX or SUM. Technically you can also select other columns from the users table as well, assuming they are functionally dependent on the userId. As the name implies, ANY_VALUE is telling MySQL to return any value for that column from each group of records.
As far as I know/expect, the value you get from ANY_VALUE is not guaranteed to be deterministic, and so would be logically equivalent to selecting a random value for that column from each group of records. Assuming you do not care which values you get back, in Postgres you could arbitrarily just select the earliest memory for each user:
SELECT
memory_id, content, id, name, email
FROM
(
SELECT m.id AS memory_id, m.content, u.id, u.name, u.email,
ROW_NUMBER() OVER (PARTITION BY u.id ORDER BY m.id) rn
FROM memories m
INNER JOIN users u
ON m.userID = u.id
) t
WHERE rn = 1;
I think that in general you should avoid using ANY_VALUE in MySQL unless you absolutely have no other choice. A better long term solution would be to clean up the MySQL query and make it ANSI compliant. Then it would be straightforward how to port it to another database.
I think you need DISTINCT ON ( expression ...) clause.
New code is
select distinct on(u.id)
m.id, m.content, m.userID, u.id, u.name, u.email
from memories m
inner join users u on m.userID = u.id;
Note: DISTINCT ON is Non-Standard SQL Clause.
Form more information see
I just had this problem and simply solved it by using a min function. It works also on string columns. In your case it would give:
SELECT
m.userID,
min(m.id) as id,
min(m.content) as content,
min(u.id) as uid,
min(u.name) as name,
min(u.email) as email
FROM memories m
INNER JOIN users u on m.userID = u.id
GROUP BY userID;
If you were ready to use any_value, then it means that you don't care which value to return. Take the minimum one is one of those random values.

select a column corresponding to max value in two joined tables

I have two tables, say Users and Interviews. One user can have multiple interview records.
Users
-----
UserID
FirstName
LastName
Interviews
----------
InterviewID
UserID
DateOfInterview
I want to get only the latest interview records. Here's my query
select u.UserID, firstname, lastname, max(DateOfInterview) as latestDOI
from users u
left join interviews i
on u.UserID = i.UserID
GROUP BY u.UserID, firstname, lastname
ORDER BY max(DateOfInterview) DESC
How do I update the query to return the InterviewID as well (i.e. the one which corresponds to max(DateOfInterview))?
Instead of using an aggregate function in your select list, you can use an aggregate subquery in your WHERE clause:
select u.UserID, firstname, lastname, i.InterviewId, DateOfInterview as latestDOI
from users u
left join interviews i
on u.UserID = i.UserID
where i.UserId is null or i.DateOfInterview = (
select max(DateOfInterview)
from interviews i2
where i2.UserId = u.UserId
)
That does suppose that max(DateOfInterview) will be unique per user, but the question has no well-defined answer otherwise. Note that the main query is no longer an aggregate query, so the constraints of such queries do not apply.
There are other ways to approach the problem, and it is worthwhile to look into them because a correlated subquery such as I present can be a performance concern. For example, you could use an inline view to generate a table of the per-user latest interview dates, and use joins to that view to connect users with the ID of their latest interview:
select u.*, im.latestDOI, i2.InterviewId
from
users u
left join (
select UserID, max(DateOfInterview) as latestDOI
from interviews i
group by UserID
) im
on u.UserId = im.UserId
left join interviews i2
on im.UserId = i2.UserId and im.latestDOI = i2.DateOfInterview
There are other alternatives, too, some standard and others DB-specific.
Rewrite to use an OUTER APPLY when grabbing your interview, that way you can use order by rather than MAX
select u.UserID, firstname, lastname, LatestInterviewDetails.ID, LatestInterviewDetails.DateOfInterview as latestDOI
from users u
OUTER APPLY (SELECT TOP 1 Id, DateOfInterview
FROM interviews
WHERE interviews.UserID = u.UserId
ORDER BY interviews.DateOfInterview DESC
) as LatestInterviewDetails
Note: This is providing you are using Microsoft SQL Server

join 2 mysql tables and get the first and last date

I have 2 mysql tables, one with the users details and the second with all the pages that the users saw (1:N)
TABLE "users"
id int(10) auto_increment primay
ip varchar(15)
lang char(2)
...
TABLE "pages"
id int(10) auto_increment primay
uid int(10) index
datetime datetime
url varchar(255)
I know is possibile to join the 2 tables, but i'm a little confused how to get the first and last datetime, and the first url from the "pages" table...
SELECT * FROM users, pages WHERE users.id = pages.uid
I think with GROUP BY / MIN(pages.datetime), MAX(pages.datetime) but I have no idea where to use it, and how I can get the first pages.url
As you mentioned you need to use Group by with MIN & MAX aggregate function to find the first and last datetime per user.
Also don't use comma separated join syntax which is quite old and not much readable use proper INNER JOIN syntax
SELECT U.ID,
MIN(pages.datetime) as First_date,
MAX(pages.datetime) as Last_date
FROM users U
INNER JOIN pages P
ON U.id = P.uid
Group by U.ID
If you want to see the other information like first visited url,etc.. Then you can join above result to the main table to get the related information.
select A.uid,A.url First_URL,C.url as Last_url,First_date,Last_date
from pages A
INNER JOIN
(
SELECT U.ID,
MIN(pages.datetime) as First_date,
MAX(pages.datetime) as Last_date
FROM users U
INNER JOIN pages P
ON U.id = P.uid
Group by U.ID
) B
ON A.ID =B.ID
and A.datetime = B.First_date
INNER JOIN pages C
on C.ID =B.ID
and C.datetime = B.Last_date