"Join" admins of different tables into a string - mysql

The real issue
Involved tables and their columns
accounts [id,name]
rooms [id,name,topic,owner]
room_admins [account_id,room_id]
Q: Get all rooms with their admin- and owner ids.
Where "all" of course has a condition to it (above: WHERE name LIKE ...)
Admins and owners should be returned in one column just called "admins". I tried to concatenate them above into one string.
What I tried
I came up with a solution, but it requires the use of an omnious external variable ":room_id" that changes on each outer SELECT and makes therefore no sense at all.
SELECT id,name,topic,
(SELECT GROUP_CONCAT(admins.account_id) AS owner
FROM
(SELECT account_id
FROM `room_admins`
WHERE room_id=:room_id
UNION
SELECT owner FROM `rooms` WHERE id=:room_id) admins) AS owner
FROM `rooms`
WHERE name LIKE "%htm%" OR topic LIKE "%htm%" LIMIT 20

Well, I haven't given this a deep thought... but I've just came up with this (sample data would have been useful to make tests... so this is just a blind answer).
select id, name, topic, group_concat(owner_admin) from (
select id, name, topic, owner owner_admin from rooms
union
select id, name, topic, account_id from rooms
left join room_admins on id = room_id
) s
where name like "%htm%" or topic like "%htm%"
group by id, name, topic
Basically I'm just generating a derived table with owner and admins mixed in one column. Then performing the grouping on that mixed column.

Most of the times, when you want to select and display dependent data, you want to use a JOIN. In this case, you want to join the rooms with their admins, so basically:
SELECT r.id, r.name, r.topic, a.id
FROM rooms r
LEFT JOIN admins a
ON r.id = a.room_id
WHERE :condition
Since you have one additional admin not in the admins table (the room owner), you have to (self) join a second time:
SELECT r.id, r.name, r.topic, a.id
FROM rooms r
LEFT JOIN admins a
ON r.id = a.room_id
LEFT JOIN rooms o
ON r.id = o.id
WHERE :condition
This doesn't give us any new information, but your question states that you want to return the list of admins in a single field. So, finally, putting it all together:
SELECT r.id, r.name, r.topic, GROUP_CONCAT(a.id)
FROM rooms r
LEFT JOIN
(
SELECT id, room_id FROM admins
UNION SELECT room.owner AS id, rooms.id AS room_id FROM rooms
) a
ON r.id = a.room_id
WHERE :condition
GROUP BY r.id
But to avoid this ugly sub-select-union clause, I'd advise you to put the room owner into your admin table too.

Related

mysql inner join interrogate 3 tables

I have a table in a MySQL DB, called ‘users’. The fields for users are : id, email, username, first_name, last_name. Another table in the same MySQL DB, called ‘premium_users’ , contains 2 fields : user_id, premium_service_id. A third table called ‘premium_services’ contains 2 fields : premium_service_id , premium_service_name.
I want to create an SQL query , to interrogate my db, so i can have a full list of what premium services has every premium user. How can i interrogate properly with inner join? I’ve try this :
select * from users inner join premium_users on users.id = premium_users.user_id inner join premium_services on premium_users.premium_service_id = premium_services.premium_service_id;
Since you say which service has every user, you'll need to use aggregation to determine this. Here's one way:
select user_id
from premium_users
group by user_id
having count(*) = (select count(*) from premium_services)
SQL Fiddle Demo
Depending on your data, you may need count(distinct premium_service_id) instead, but you should have constraints that don't allow duplicates in those table.
Rereading your question, I might have got this backwards. Looks like you want a list of premium services instead of users. Same concept applies:
select ps.premium_service_id
from premium_services ps
join premium_users pu on ps.premium_service_id = pu.premium_service_id
group by ps.premium_service_id
having count(distinct pu.user_id) = (select count(distinct user_Id) from premium_users)
More Fiddle

How to join on conditional tables

I have to maintain a database that has 3 different tables for their users (for different roles). Unfortunately, merging them is not allowed.
There is another table that logs their activity, and this table has two columns for identifying a specific user, one is userId, and the other is roleId. Now, when I want to list user activities for all users, then depending on the value in roleId, I would have to join each row with a different table.
If there was only one table with users, the query would be something like:
SELECT * FROM activity JOIN buyers ON activity.userId = buyers.id
So how would I expand on this query now, if I had 3 tables with users: buyers, sellers, and administrators; knowing that the roleId column in the activity table identifies either a buyer, a seller, or an administrator?
SELECT *, 'Buyer' as accountType FROM activity JOIN buyers b ON activity.userId = b.id
UNION
SELECT *, 'Seller' FROM activity JOIN sellers s ON activity.userId = s.id
UNION
SELECT *, 'Admin' FROM activity JOIN administrators a ON activity.userId = a.id
may consider writing the above as a view that way I could build on it as needed, given I can't change any existing table design.
may have to spell out specific table columns as well if buyer, seller, and admin have different columns. Given you've not provided the table structures, I'm assuming they are identical for this example purpose.
Just guessing here as to how roleId relates to the other tables.
SELECT * FROM activity JOIN buyers ON activity.userId = buyers.id
WHERE activity.roleId = 1
UNION SELECT * FROM activity JOIN sellers ON activity.userId = sellers.id
WHERE activity.roleId = 2
UNION SELECT * FROM activity JOIN administrators ON activity.userId = administrators.id
WHERE activity.roleId = 3

Distinct select statement using two tables

I've looked around the forum for a while now, but I can't find a solution for the following.
I've got two tables, users and bookings.
users:
unique id, firstname and lastname
bookings:
uniquide id, userId, arrival and departure
For every booking a user makes, the bookings table will contain the unique users id in the field userId.
I now want to get the firstname and lastname from table users, and all bookings which are made depending on the userId.
select users.*, bookings.* FROM users, bookings WHERE bookings.userId =:id GROUP BY bookings.id
The statement above returns all bookings according to the userId, but it also contains the user itself multiple times, i just need the user data once.
select u.id, u.firstname, u.lastname, b.id as bookingid, b.arrival, b.departure from users u JOIN bookings b ON u.id = b.userid
doing GROUP BY on booking.id makes no sense in this case, as booking.id is the lowest level element. Now, you could group by users.id, but then you can no longer return all the individual bookings - but you can return a lot of helpful things like COUNT(bookings.id) AS NbrBookings or MIN(arrival) or MAX(departure)
but you can't have aggregate data and inidividual data at the same time.
users.*, bookings.*
instead of this specify the relevant column names

Selecting non-matching fields in mySQL

I have three tables as follows:
Contact, Custom_Field, Custom_Field_Value.
Each contact can have one Custom_Field_Value record for each Custom_Field. So there is a 1:many relationship between Contact and Custom_Field_Value but it isn't quite that simple.
Everything works fine - except for one edge case where I need to select Contacts that have a particular Custom_Field not set (i.e. no corresponding Custom_Field_Value record exists linking to the Contact and the Custom_Field). This is surprisingly difficult. I can't just use the normal "left join and look for NULL" approach because they may have a different custom field - but not the one I am looking for. I need to say "Where Custom_Field_ID=10" but I can't because the thing I'm looking for does not exist.
My line of thinking was heading in this direction, but I'm just tying myself in knots now:
Select ID, First_Name, Last_Name, CF_ID From
(
(Select Contact.ID, First_Name, Last_Name, Custom_Field_Value.ID as CFV_ID, Custom_Field_Value.CustomFieldID as CF_ID, TextValue
From Contact Inner Join Custom_Field_Value on Contact.ID = Custom_Field_Value.ContactID
Where Custom_Field_Value.CustomFieldID=23 Order By Contact.ID)
UNION
(Select Contact.ID, First_Name, Last_Name, Custom_Field_Value.ID as CFV_ID, Custom_Field_Value.CustomFieldID as CF_ID, TextValue
From Contact LEFT Join Custom_Field_Value on Contact.ID = Custom_Field_Value.ContactID
Order by Contact.ID)
) as A
Group BY `ID`, CF_ID ASC
I don't want to create blank records for every possibility because there could be millions of records and every time someone adds a custom field, the database would have to insert millions of corresponding blank records.
It would be really great if we could do this:
Select ID From thingy
EXCLUDE
Select * From thingy Where x = true
This is a nasty one, but I know there'll be someone out there who will love it:)
Okay, I think I have a better understanding now. I was trying to pull it off without a subquery, but I'm not sure if I can.
Can you try
Select Contact.ID, First_Name, Last_Name, Custom_Field_Value.ID as CFV_ID, Custom_Field_Value.CustomFieldID as CF_ID, TextValue
From Contact LEFT Join Custom_Field_Value on Contact.ID = Custom_Field_Value.ContactID
WHERE NOT EXISTS(SELECT * FROM Custom_Field_Value cfv2 WHERE cfv2.ContactID = Contact.ID AND cfv2.CustomFieldID=23)
Order by Contact.ID
The NOT EXISTS subquery should only return rows where the contact has no value for that field.
This is the crazy SQL I ended up with - created dynamically by users. Just publishing it in case its any use to anyone. (Any tips on optimisation are very welcome!):
The problem is that not only do I have to select missing dynamic records, I also have to join together Left Join queries into one result.
SELECT * FROM (
(SELECT * FROM Contact
WHERE (...some dynamic stuff...)
)
UNION All
(SELECT Contact.* FROM Contact Inner Join Contact_Campaign_Link ON Contact.ID=Contact_Campaign_Link.Contact_ID
WHERE ((Campaign_ID=31))
)
UNION All
(SELECT * FROM Contact
WHERE (CustomerID=3)
AND (NOT EXISTS
(SELECT * FROM Custom_Field_Value cfv2
WHERE (cfv2.ContactID = Contact.ID)
AND (cfv2.CustomFieldID =27) )) ORDER BY Contact.ID)
) As tbl
GROUP BY tbl.ID HAVING COUNT(*)=3 Order by ID

MySQL query optimization: Multiple SELECT IN to LEFT JOIN

I usually go with the join approach but in this case I am a bit confused. I am not even sure that it is possible at all. I wonder if the following query can be converted to a left join query instead of the multiple select in used:
select
users.id, users.first_name, users.last_name, users.description, users.email
from users
where id in (
select assigned.id_user from assigned where id_project in (
select assigned.id_project from assigned where id_user = 1
)
)
or id in (
select projects.id_user from projects where projects.id in (
select assigned.id_project from assigned where id_user = 1
)
)
This query returns the correct result set. However, I guess the repetition of the query that selects assigned.id_project is a waste.
You could start with the project assignments of user 1 a1. Then find all assignments of other people to those projects a2, and the user in the project table p. The users you are looking for are then in either a2 or p. I added distinct to remove users who can be reached in both ways.
select distinct u.*
from assigned a1
left join
assigned a2
on a1.id_project = a2.id_project
left join
project p
on a1.id_project = p.id
join user u
on u.id = a2.id_user
or u.id = p.id_user
where a1.id_user = 1
Since both subqueries have a condition where assigned.id_user = 1, I start with that query. Let's call that assignment(s) the 'leading assignment'.
Then join the rest, using left joins for the 'optional' tables.
Use an inner join on user that matches either users of assignments linked to the leading assignment or users of projects linked to the leading project.
I use distinct, because I assumen you'd want each user once, event if they have an assignment and a project (or multiple projects).
select distinct
u.id, u.first_name, u.last_name, u.description, u.email
from
assigned a
left join assigned ap on ap.id_project = a.id_project
left join projects p on p.id = a.id_project
inner join users u on u.id = ap.id_user or u.id = p.id_user
where
a.id_user = 1
Here's an alternative way to get rid of the repetition:
SELECT
users.id,
users.first_name,
users.last_name,
users.description,
users.email
FROM users
WHERE id IN (
SELECT up.id_user
FROM (
SELECT id_user, id_project FROM assigned
UNION ALL
SELECT id_user, id FROM projects
) up
INNER JOIN assigned a
ON a.id_project = up.id_project
WHERE a.id_user = 1
)
;
That is, the assigned table's pairs of id_user, id_project are UNIONed with those of projects. The resulting set is then joined with the user_id = 1 projects to obtain the list of all users who share the projects with the ID 1 user. And now it only remains to retrieve the details for those users, which in this case is done in the same way as in your query, i.e. using an IN clause.
I'm sorry to say that I don't have MySQL to thoroughly test the performance of this query and so cannot be quite sure if it is in any way better or worse than your original query or than the one suggested both by #GolezTrol and by #Andomar. Generally I tend to agree with #GolezTrol's comment that a query with simple (semi- or whatever-) joins and repetitive parts might turn out more efficient than an equivalent sophisticated query that doesn't have repetitions. In the end, however, it is testing that must reveal the final answer for you.