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

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

Related

SQL : Left join with grouped joined rows

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

How to count make an SQL based on 3 tables

I have three tables and Im trying to count the number of likes per user on all his/her post.
USER TABLE
id name
1 John
2 Joe
POSTS TABLE
id user_id post_title
1 1 Some Title
2 1 Another Title
3 2 Yeah Title
LIKES TABLE
id post_id
1 1
2 1
3 1
4 2
5 3
My expected output is
ID LIKES
1 4
2 1
Im kinda stuck with the code below. I don't know how to add and count the likes table.
SELECT *
FROM user
INNER JOIN posts
ON user.id = posts.user_id;
You need to extend the join to the LIKES table and then use GROUP BY to group by the user ID and COUNT() all of the records for that user...
SELECT user.id, COUNT(likes.id)
FROM user
INNER JOIN posts ON user.id = posts.user_id
INNER JOIN likes ON posts.id = likes.post_id
GROUP BY user.id
If you want to list people who don't have posts or likes, then you should use outer joins (so change INNER JOIN to LEFT JOIN) so that these users show up.
For your desired result, you don't need the user table. You can simply do:
SELECT p.user_id, COUNT(*)
FROM posts p JOIN
likes l
ON l.post_id = p.id
GROUP BY p.user_id;
The only information you are taking from users is the id, which is already in posts. This assumes that all the user_id values in posts are valid, but that seems like a very reasonable assumption.

How to get different results from one mysql table?

I have a user table in the database where all users of the system are stored.
The table has a user_id and a business_name and a first_name.
Some users are merchants and get a business name,
some users are consumers and get a first name.
In a second table I have transactions with a user_id and a merchant_id (which are defining the transaction) and an amount. Both ids reference to user table.
Table users:
user_id bus_name first_name role_id
1 Thomas 10
2 comp1 7
3 Peter 10
4 comp2 7
(role_id is defining with 10=consumer, 7=merchant)
Table transactions:
trans_id amount user_id merchant_id
1 12 1 2
2 23 3 2
3 34 3 4
4 19 1 4
Now I want to have a query with a result as one table:
This table should contain the transaction with amount, user_id, first_name, merchant_id and bus_name.
I want to get this result:
trans_id amount user_id first_name merchant_id bus_name
1 12 1 Thomas 2 comp1
2 23 3 Peter 2 comp1
3 34 3 Peter 4 comp2
4 19 1 Thomas 4 comp2
I have the problem that either I get only the first_name and empty bus_name or I get only the bus_name but empty first_name.
I am using a left join:
...
left join `users`
on(
(`transactions`.`user_id` = `users`.`user_id`)
)
...
But for this I would get for user_id=1 the first_name=Thomas and the bus_name='' would be empty because I only reference to one line in table and not also to different user with user_id=2.
But I want to say something like:
for trans_id=1
get first_name FROM users WHERE transactions.user_id = users.user_id
AND
get bus_name FROM users WHERE transactions.merchant_id = users.user_id
Thanks for your help, I tried so many things but it does not work.
You have to join the user table twice:
SELECT t.*, u.first_name, m.bus_name
FROM transactions t
JOIN users as u
ON t.user_id = u.user_id
JOIN users as m
ON t.merchant_id = m.merchant_id
you could use a duoble join in users table
select a.trans_id, a.amount , a.user_id, b.first_name, a.merchant_id, c. bus_name
from transactions a
inner join users b on a.user_id = b.user_id and b.role_id = 10
inner join users c on a.merchant_id = c.user_id and c.role_id = 7
To join the user table twice worked fine. With "left join users as consumer" I create a kind of a virtual users table called "consumer", this one is joined. Of course in select I had to adjust table name as well. Same for second "virtual" table od users, called "merchant".
select
`transactions`.`trans_id` AS `trans_id`,
`transactions`.`merchant_id` AS `merchant_id`,
`merchant`.`bus_name` AS `bus_name`,
`transactions`.`user_id` AS `user_id`,
`consumer`.`first_name` AS `first_name`,
`cards`.`card_id` AS `card_id`,
`cards`.`serial_no` AS `serial_no`
from (
`transactions`
left join `cards`
on(
(`cards`.`card_id` = `transactions`.`card_id`)
)
left join `users` as consumer
on(
(`consumer`.`user_id` = `transactions`.`user_id`)
)
left join `users` as merchant
on(
(`merchant`.`user_id` = `transactions`.`merchant_id`)
)
)

Join on multiple table

I have 5 tables like below
user
id name
11 rajesh
12 kumar
group
id name
21 sales
22 dev
domain
id name
51 network
52 database
user_group
user_id group_id
11 21
user_domain
user_id domain_id
12 51
I need to retrive all users who are member of atleast one group or domain. Here is the query that I am using to join two tables.
select user.name from user join user_domain on user.id=user_domain.user_id
I am able to join tables user and group. But along with that I need to join user and domain also. When I try like below I am getting zero results.
select user.name from user
left join user_group on user.id=user_group.user_id
join user_domain on user.id=user_domain.user_id
If I change to left join like below I am getting all users.
select user.name from user
left join user_group on user.id=group.user_id
left join user_domain on user.id=user_domain.user_id
Please advise
Isn't this enough?
SELECT `id`
FROM `user` JOIN `user_group` ON `user`.`id` = `user_group`.`user_id`
UNION
SELECT `id`
FROM `user` JOIN `user_domain` ON `user`.`id` = `user_domain`.`user_id`;

Mysql query in drupal database - groupwise maximum with duplicate data

I'm working on a mysql query in a Drupal database that pulls together users and two different cck content types. I know people ask for help with groupwise maximum queries all the time... I've done my best but I need help.
This is what I have so far:
# the artists
SELECT
users.uid,
users.name AS username,
n1.title AS artist_name
FROM users
LEFT JOIN users_roles ur
ON users.uid=ur.uid
INNER JOIN role r
ON ur.rid=r.rid
AND r.name='artist'
LEFT JOIN node n1
ON n1.uid = users.uid
AND n1.type = 'submission'
WHERE users.status = 1
ORDER BY users.name;
This gives me data that looks like:
uid username artist_name
1 foo Joe the Plumber
2 bar Jane Doe
3 baz The Tooth Fairy
Also, I've got this query:
# artwork
SELECT
n.nid,
n.uid,
a.field_order_value
FROM node n
LEFT JOIN content_type_artwork a
ON n.nid = a.nid
WHERE n.type = 'artwork'
ORDER BY n.uid, a.field_order_value;
Which gives me data like this:
nid uid field_order_value
1 1 1
2 1 3
3 1 2
4 2 NULL
5 3 1
6 3 1
Additional relevant info:
nid is the primary key for an Artwork
every Artist has one or more Artworks
valid data for field_order_value is NULL, 1, 2, 3, or 4
field_order_value is not necessarily unique per Artist - an Artist could have 4 Artworks all with field_order_value = 1.
What I want is the row with the minimum field_order_value from my second query joined with the artist information from the first query. In cases where the field_order_value is not valuable information (either because the Artist has used duplicate values among their Artworks or left that field NULL), I would like the row with the minimum nid from the second query.
The Solution
Using divide and conquer as a strategy and mysql views as a technique, and referencing this article about groupwise maximum queries, I solved my problem.
Create the View
# artists and artworks all in one table
CREATE VIEW artists_artwork AS
SELECT
users.uid,
users.name AS artist,
COALESCE(n1.title, 'Not Yet Entered') AS artist_name,
n2.nid,
a.field_image_fid,
COALESCE(a.field_order_value, 1) AS field_order_value
FROM users
LEFT JOIN users_roles ur
ON users.uid=ur.uid
INNER JOIN role r
ON ur.rid=r.rid
AND r.name='artist'
LEFT JOIN node n1
ON n1.uid = users.uid
AND n1.type = 'submission'
LEFT JOIN node n2
ON n2.uid = users.uid
AND n2.type = 'artwork'
LEFT JOIN content_type_artwork a ON n2.nid = a.nid
WHERE users.status = 1;
Query the View
SELECT
a2.uid,
a2.artist,
a2.artist_name,
a2.nid,
a2.field_image_fid,
a2.field_order_value
FROM (
SELECT
uid,
MIN(field_order_value) AS field_order_value
FROM artists_artwork
GROUP BY uid
) a1
JOIN artists_artwork a2
ON a2.nid = (
SELECT
nid
FROM artists_artwork a
WHERE a.uid = a1.uid
AND a.field_order_value = a1.field_order_value
ORDER BY
uid ASC, field_order_value ASC, nid ASC
LIMIT 1
)
ORDER BY artist;
A simple solution to this can be to create views in your database that can then be joined together. This is especially useful if you often want to see the intermediate data in the same way in some other place. While it is possible to mash together the one huge query, I just take the divide and conquer approach sometimes.