SQL : Left join with grouped joined rows - mysql

I have two tables:
users containing all my users with common attributes
metas holding all dynamic attributes in a one to many relation, (Fyi I cant merge theses attributes into the users table because they can be created / deleted on the fly).
users
id
name
email
1
John
j#d.com
2
Jane
a#d.com
metas
user_id
name
value
1
created_at
"2021-01-01"
1
logged_at
"2021-01-01"
2
created_at
"2021-01-01"
Problem
I want to build a SQL query resulting with something like below, so I can have some where and orders on user metadatas.
id
name
email
created_at
logged_at
1
John
j#d.com
2021-01-01
2021-01-01
2
Jane
a#d.com
2021-01-01
NULL
Progression
I managed to build a result but with only one meta (the created_at) but not logged_at, because I can't group many meta rows for the same user.
SELECT users.*, CAST(JSON_UNQUOTE(value) AS DATE) as created_at
FROM users
LEFT JOIN metas on users.id = metas.user_id and metas.name = 'created_at'
ORDER BY created_at desc
Constraints
It should be done in a single query and can't be done from the code side.
Any suggestions ?

You can join the same table twice.
SELECT users.*,
CAST(JSON_UNQUOTE(m1.value) AS DATE) as created_at,
CAST(JSON_UNQUOTE(m2.value) AS DATE) as logged_at
FROM users
LEFT JOIN metas m1 on users.id = m1.user_id and m1.name = 'created_at'
LEFT JOIN metas m2 on users.id = m2.user_id and m2.name = 'logged_at'
ORDER BY created_at desc

Using a pivot
select *
from
(
select u.id,u.name as user_name,u.email,m.name as meta_name,m.value
from #tUsers as u
left join #tMetas as m on u.id=m.user_id
) as q
pivot
(
max(q.value) for q.meta_name in ([Created_At],[Logged_At])
) as pvt
order by pvt.id

Related

Query only return ids where does't have a relationship with where

Doing a query on some data but I can't workout how to do this efficiently-- querying users and joining with a table of dates-
I want the users who have do NOT have a record in the second table with a matching date but they have multiple records so if I just did select * from users join dates on users.user_id=dates.user_id where dates.date != '8/3/2020';, shevy and rob would still be returned because row 4 still matches for shevy.
users dates
------- -------
id name id user_id date
1 shevy 1 2 8/1/2020
2 rob 2 1 8/3/2020
3 2 8/10/2020
4 1 8/17/2020
Personally, I would use NOT EXISTS...
SELECT U.ID
,U.NAME
FROM USERS U
WHERE NOT EXISTS (
SELECT 1
FROM DATES D
WHERE D.USER_ID = U.ID
AND D.DATE = '8/3/2020'
)
;
As an aside, this seems like a basic ANSI SQL question and is not really a Snowflake question.
I think you want:
select u.*
from users u left join
dates d
on u.user_id = d.user_id and d.date = '8/3/2020' -- should be 2020-08-03
where d.date is null;
This gets you users that do not have the specified date.

SQL : join if not in table or select only one row

I have four tables : one of users, one of profiles (users profiles), one of relations between profile and content and one of content. That content table could have no entry, single or multiple entry for a user profile.
I would like to get all the users who have no entry in the content table or if they have multiple entries, get only one.
Here is my current SQL :
SELECT
users.uid AS uid,
profile.id AS profile_id,
content.id AS content_id
FROM
users users_data
LEFT JOIN profile profile_data ON users_data.id = profile_data.uid
LEFT JOIN content_for_profile content_profile ON profile_data.id = content_profile.pid
LEFT JOIN content content_data ON content_for_profile.content_id = content_data.id
JOIN (
SELECT content_data_join.id, content_data_join.uid
FROM content content_data_join
GROUP BY content_data_join.uid
) content_data_join ON content_for_profile.content_id=content_data_join.id
GROUP BY uid, profile_id, content_id
ORDER BY profile.created DESC
I tried to add the simple JOIN to get only one result from the content table and it works. But with that, i don't get users with no entry in the content table.
I've been stuck for hours and try so many things ... Thanks in advance for your help !
EDIT :
user example
id
8
9
10
profile example :
id uid
2000 8
2001 9
2002 10
content_for_profile example :
id pid content_id
1 2000 100
2 2001 101
3 2001 102
content example :
id uid
100 8
101 9
102 9
expected result :
uid profile_id content_id
8 2000 100
9 2001 101
10 2002 NULL
for get the user without entry in content table you can use where condition for null
SELECT
users.uid AS uid,
profile.id AS profile_id,
content.id AS content_id
FROM
users users_data
LEFT JOIN profile profile_data ON users_data.id = profile_data.uid
LEFT JOIN content_for_profile content_profile ON profile_data.id = content_profile.pid
LEFT JOIN content content_data ON content_for_profile.content_id = content_data.id
where content_data.id is null
ORDER BY profile.created DESC
You should not use GROUP BY without aggreation function. this deprecated is most db and is not allowed in the most recent version of mysql
a
for get just one entry for content value the you can use an aggreation function eg min() ..max()
SELECT
users.uid AS uid,
profile.id AS profile_id,
min(content.id) AS content_id
FROM
users users_data
LEFT JOIN profile profile_data ON users_data.id = profile_data.uid
LEFT JOIN content_for_profile content_profile ON profile_data.id = content_profile.pid
LEFT JOIN content content_data ON content_for_profile.content_id = content_data.id
GROUP BY users.uid ,profile.id
ORDER BY profile.created DESC

Selecting a count of rows having a max value

Working example: http://sqlfiddle.com/#!9/80995/20
I have three tables, a user table, a user_group table, and a link table.
The link table contains the dates that users were added to user groups. I need a query that returns the count of users currently in each group. The most recent date determines the group that the user is currently in.
SELECT
user_groups.name,
COUNT(l.name) AS ct,
GROUP_CONCAT(l.`name` separator ", ") AS members
FROM user_groups
LEFT JOIN
(SELECT MAX(added), group_id, name FROM link LEFT JOIN users ON users.id = link.user_id GROUP BY user_id) l
ON l.group_id = user_groups.id
GROUP BY user_groups.id
My question is if the query I have written could be optimized, or written better.
Thanks!
Ben
You actual query is not giving you the answer you want; at least, as far as I understand your question. John actually joined group 2 on 2017-01-05, yet it appears on group 1 (that he joined on 2017-01-01) on your results. Note also you're missing one Group 4.
Using standard SQL, I think the next query is what you're looking for. The comments in the query should clarify what each part is doing:
SELECT
user_groups.name AS group_name,
COUNT(u.name) AS member_count,
group_concat(u.name separator ', ') AS members
FROM
user_groups
LEFT JOIN
(
SELECT * FROM
(-- For each user, find most recent date s/he got into a group
SELECT
user_id AS the_user_id, MAX(added) AS last_added
FROM
link
GROUP BY
the_user_id
) AS u_a
-- Join back to the link table, so that the `group_id` can be retrieved
JOIN link l2 ON l2.user_id = u_a.the_user_id AND l2.added = u_a.last_added
) AS most_recent_group ON most_recent_group.group_id = user_groups.id
-- And get the users...
LEFT JOIN users u ON u.id = most_recent_group.the_user_id
GROUP BY
user_groups.id, user_groups.name
ORDER BY
user_groups.name ;
This can be written in a more compact way in MySQL (abusing the fact that, in older versions of MySQL, it doesn't follow the SQL standard for the GROUP BY restrictions).
That's what you'll get:
group_name | member_count | members
:--------- | -----------: | :-------------
Group 1 | 2 | Mikie, Dominic
Group 2 | 2 | John, Paddy
Group 3 | 0 | null
Group 4 | 1 | Nellie
dbfiddle here
Note that this query can be simplified if you use a database with window functions (such as MariaDB 10.2). Then, you can use:
SELECT
user_groups.name AS group_name,
COUNT(u.name) AS member_count,
group_concat(u.name separator ', ') AS members
FROM
user_groups
LEFT JOIN
(
SELECT
user_id AS the_user_id,
last_value(group_id) OVER (PARTITION BY user_id ORDER BY added) AS group_id
FROM
link
GROUP BY
user_id
) AS most_recent_group ON most_recent_group.group_id = user_groups.id
-- And get the users...
LEFT JOIN users u ON u.id = most_recent_group.the_user_id
GROUP BY
user_groups.id, user_groups.name
ORDER BY
user_groups.name ;
dbfiddle here

select one column from one table and other columns from another table

I have the tables :
1. Users : user_id, user_name
2. Transaction : user_id, transaction_number, transaction_date
I want to get the user_id, transaction_number, transaction_date for a particular transaction_date. The first table may contain repeated user_id for different dates and may not contain all user_id for a particular date. The result must contain all user_ids and its transaction_number and transaction dates are null if the user_id has no any transaction entries in the transaction table.
For an example :
user_id transaction_number transaction_date
1 10 12/04/2013
2 0 12/04/2013
3 0 12/04/2013
It shows there are only 3 users and only user_id 1 have transaction in 12/04/2013.
You can replace transaction_number with 0 if it is null depending on the type of database you are using.
Select u.User_id, transactions.transaction_number, '12/04/2013' as transaction_date
from Users u
left join ( Select user_id, Sum(transaction_number)
from Transaction
where transaction_date = '12/04/2013'
Group By user_id
) transactions
on transactions.User_id = u.user_id
select * from Transaction right outer join Users on Transaction.user_id = Users.user_id and Transaction.transaction_date = 'Apr 12 2013 12:00AM'
List all users with transactions if any.Use left join.
select users.user_id,users.user_name,transaction.transaction_number, transaction.date from users left join transaction on users.user_id = transaction.user_id

Mysql join query

I have table users and another table premium_users in which I hold the userid and the date when he bought premium membership.
How can I use mysql join , so that in a single query I can select all the columns from the table users and also know for each premium user the date he joined on.
USERS:
ID USERNAME
1 JOHN
2 BILL
3 JOE
4 KENNY
PREMIUM USERS:
ID USERID DATE
1 2 20/05/2010
2 4 21/06/2011
And the final table (the one that will be returned my the query) should look like this:
ID USERNAME DATE
1 JOHN
2 BILL 20/05/2010
3 JOE
4 KENNY 21/06/2011
Is it ok for some rows to have the DATE value empty?
How can I check if that value is empty? $row['date']=='' ?
EDIT:
This was only an example, but the users table has much more columns, how can I select all from users and only date from premium_users without writing all the columns?
select u.*, pu.DATE
from USERS u LEFT OUTER JOIN PREMIUM_USERS pu on
u.ID = pu.USERID
You can check if a row is empty with:
if (!$row['DATE'])
{
...
}
select USERS.ID, USERS.USERNAME, PREMIUM_USERS.DATE
from USERS
join PREMIUM_USERS on USERS.ID = PREMIUM_USERS.ID
order by USERS.ID
This is mssql syntax, but it should be pretty similar...
select *
from users u
left join premiumUsers p
on u.id = p.id
order by u.id asc
SELECT A.*, B.DATE
FROM USERS A
LEFT JOIN PREMIUIM_USERS B on A.ID=B.USERID
EDITED
It might be easier to have it all in one table. You can have nullable fields for isPremium(t/f) and premiumDate. you actually dont even need the isPremium field. just premiumDate if its null they are not premium and if it has value they are premium user and you have the date they joined.