Group by one field and display multiple fields (MySQL) - mysql

So i have these three tables :
Booking :
+----+---------+--------+---------------+-------------+
| id | idRoom | idUser | startDateTime | endDateTime |
+----+---------+--------+---------------+-------------+
| 4 | 3 | 1 | 07/06/2020 | 07/07/2020 |
| 5 | 3 | 2 | 07/06/2021 | 07/06/2021 |
+----+---------+--------+---------------+-------------+
Room :
+----+--------------+
| id | description |
+----+--------------+
| 3 | Room 1 |
+----+--------------+
User :
+----+----------+
| id | userName |
+----+----------+
| 1 | User 1 |
| 2 | User 2 |
+----+----------+
And want to select all the bookings (listed in table one) while displaying the User and the Room fields infos and group by the Room object.
I am using the JOIN clause along with the GROUP BY clause as follows :
select distinct r, b, u
from Booking b
join Room r on b.idRoom=r.id
join User u on b.idUser=u.id
where r.id=3
group by r, b, u
order by r
But it is not rendering the desired result.
Anyone suggests a working SQL query ?
EDIT (Desired Result ) :
+-------+--------+-----------+
| Rooms | Users | Bookings |
+-------+--------+-----------+
| 3 | 1 | 4 |
| | 2 | 5 |
+-------+--------+-----------+

As you wants to group by your query output by room, you can start your query from the room table. But not sure how you can get the Json formatted output. You can achieve the following output and rest part you should manage in the frontend.
Possible output-
Rooms Users Bookings
3 1 4
3 2 5
Query for the above output -
SELECT R.id AS Rooms,
B.idUser AS Users,
B.ID AS Bookings
FROM Room R
INNER JOIN Booking B ON R.Id = B.idRoom
For more details of a User, you can join the User table Now.

Related

how to perform an outer join in mysql

I have a table A that contains tree columns, id, users ids and vehicle id. And a table B that contains vehicleid, and vehicle name.
Table A
---------------------------
| Id | User_id |Vehicle_id|
---------------------------
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 4 |
| 4 | 2 | 2 |
| 5 | 2 | 3 |
| 6 | 4 | 5 |
---------------------------
Table B
-------------------
| Id |Vehicle_name|
-------------------
| 1 | Car |
| 2 | Bike |
| 3 | Plane |
| 4 | Boat |
| 5 | Rocket |
-------------------
Given a user id, I need to get all vehicle names, that doesn't match with table A. I've tried Outer joins, but I can't manage to do get the info that i need.
For example: Given user id 1, the query should return Car and Rocket.
thanks in advance
This is simple enough using not in or not exists:
select b.*
from b
where not exists (select 1
from a
where a.vehicle_id = b.id and a.user_id = #a_user_id
);
I also thought of using a cross join and was able to get the output in case you are more comfortable with join logic.
SELECT CJOIN.USER_ID, CJOIN.VEHICLE_ID, CJOIN.VEHICLE_NAME
FROM
(SELECT DISTINCT A.USER_ID, B.ID AS VEHICLE_ID, B.VEHICLE_NAME FROM TABLE_A A CROSS JOIN TABLE_B B) CJOIN
LEFT JOIN
TABLE_A D
ON CJOIN.USER_ID = D.USER_ID AND CJOIN.VEHICLE_ID = D.VEHICLE_ID
WHERE D.USER_ID IS NULL AND D.VEHICLE_ID IS NULL;
First, I got all possible combinations of USER_ID x VEHICLE_ID by a cross join and used this table in a left join to pull records for which there is no match.

MySQL select a single record from highest join value from multiple tables with multiple records

I have the following tables:
members
This stores a list of members for our system.
---------------------
| member_id | name |
---------------------
| 1 | Bob |
---------------------
| 2 | Joe |
---------------------
| 3 | Tom |
---------------------
| 4 | Bill |
---------------------
| 5 | Will |
---------------------
categories
This stores the categories for our system. Categories are not visible to members by default. A member must have a valid licence to be able to access a category (see below).
----------------------
| cat_id | name |
----------------------
| 1 | Cat1 |
----------------------
| 2 | Cat2 |
----------------------
| 3 | Cat3 |
----------------------
licences
Stores the licences that a member has. One member can have many licences. Licences can have a life time and will expire. Once a licence expires, the member can no longer view the category.
------------------------------------------------------
| id | catid | subid | valid_from | valid_to |
------------------------------------------------------
| 1 | 1 | 1 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 2 | 1 | 2 | 1999-01-01 | 2001-01-02 |
------------------------------------------------------
| 3 | 1 | 3 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 4 | 1 | 4 | 1999-01-01 | 2000-01-01 |
------------------------------------------------------
| 5 | 1 | 5 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 6 | 2 | 1 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 7 | 2 | 2 | 1999-01-01 | 2001-01-02 |
------------------------------------------------------
| 8 | 2 | 3 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 9 | 2 | 4 | 1999-01-01 | 2000-01-01 |
------------------------------------------------------
| 10 | 2 | 5 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 11 | 3 | 1 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 12 | 3 | 2 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
preferences
The preferences table stores whether a member wishes to receive e-mails that are are sent relating to a category. The member can set a preference of '1' for 'wish to receive' or '0' for 'do not wish to receive'. A quirk is that if the member has no record (or a null value) we make an assumption that they wish to receive.
-----------------------------------
| id | catid | subid | pref |
-----------------------------------
| 1 | 1 | 1 | 0 |
-----------------------------------
| 2 | 2 | 1 | 1 |
-----------------------------------
| 3 | 3 | 1 | 1 |
-----------------------------------
| 4 | 1 | 2 | 0 |
-----------------------------------
| 5 | 1 | 3 | 1 |
-----------------------------------
| 6 | 2 | 3 | 0 |
-----------------------------------
recipients
When an email is sent out based on a category, the recipient is logged so we don't email them more than once.
-----------------------------
| id | emailid | subid |
-----------------------------
| 1 | 1 | 1 |
-----------------------------
| 2 | 1 | 2 |
-----------------------------
I'm trying to write a query the fetches all members, and their related licence for a range of category IDs, their preferences and also make sure that they don't have a record in the recipients table.
In pseudo query:
SELECT [all members, their licence info, and preference setting]
FROM [members table]
WHERE [member doesnt exist in the recipients table for a given emailID]
The issue is that I need to check against multiple categoryIDs, but return just one result and only if the preference is set to 1 (or null, or doesn't exist).
So with the example data, given we are searching for categoryIDs 1,2 and 3 (A member must have a licence for at least one of these categories) and checking against emailID of 1, the only result should be for member_id 3 (Tom) with preference ID of 6 (because it's set to 1) and licence ID of 3 (because it's valid and the preference ID of 6 corresponds to it and it is set to 1). The second result should be member_id 5 (Will) as he has a licence to catids 1 and 2, he hasn't received the email with ID of 1 and he has no specific preference set.
Reason being: Members 1 and 2 are in the recipient table for emailID 1, member 2's licence has also expired, member 4's licence has expired and member 5 has their preference set to 0.
The query I've written which isn't working quite right is:
SELECT
members.member_id,
members.name,
licence.catid as licencedToCat,
categories.cat_name as categoryName,
licence.valid_from as licenceStart,
licence.valid_to as licenceEnd,
preferences.pref
FROM (`members`)
JOIN `licence` ON `licence`.`subid`=`members`.`member_id`
JOIN `preferences` ON `preferences`.`subid`=`members`.`member_id`
JOIN `categories` ON `categories`.`cat_id`=`licence`.`catid`
WHERE `licence`.`catid` IN (1,2,3)
AND `start_date` <= '2014-12-16'
AND `end_date` >= '2014-12-16'
AND (pref='1' OR pref IS NULL)
AND `members`.`member_id` NOT IN (SELECT subid FROM `recipients` WHERE `recipients`.`emailid`='1')
GROUP BY `licence`.`subid`
The issue is that the query is returning results saying users have a preference set to 1 where they actually don't even have a record set for that category.
The desired output is any member(s) along with the licence they have for the category but only if their preference for that category is 1/null/doesn't exist AND only if they don't appear in the recipients table for a given emailID.
So, if a member has 2 licences
I appreciate this was a long read, so thanks if you're still here! Any ideas on how to tweak my query to solve this?
I think part of your problem here is that you're using all inner joins. Like you said, a user may not have a preference, so a row may not be returned in your query. That being said, it seems like you want to inner join most tables, as it appears you only want members who have licenses, but you want to see all licenses regardless of whether that user has a preference. So, I made preferences an outer joined table:
SELECT m.*, l.catid AS licenseCat, c.name AS categoryName,
l.valid_from AS licenseStart, l.valid_to AS licenseEnd, p.pref AS preference
FROM members m
JOIN licenses l ON l.subid = m.member_id
JOIN categories c ON c.cat_id = l.catid
LEFT JOIN preferences p ON p.catid = c.cat_id AND p.subid = l.subid;
Once I had done that, I wrote the subquery that pulled the member_id of all members who are in the recipients table with the specified email:
SELECT subid
FROM recipients
WHERE emailid = 1;
Now you can insert that into your original query, and add your other requirements:
SELECT m.*, l.catid AS licenseCat, c.name AS categoryName,
l.valid_from AS licenseStart, l.valid_to AS licenseEnd, IFNULL(p.pref, 0) AS preference
FROM members m
JOIN licenses l ON l.subid = m.member_id
JOIN categories c ON c.cat_id = l.catid
LEFT JOIN preferences p ON p.catid = c.cat_id AND p.subid = l.subid
WHERE c.cat_id IN (1, 2, 3) AND
l.valid_from <= '2014-12-06' AND l.valid_to >= '2014-12-06' AND
m.member_id NOT IN (SELECT subid FROM recipients WHERE emailid = 1)
AND (p.pref = 1 OR p.pref IS NULL);
You said in your question that this should return member_id 3 (which is Tom) but that does not match your results because member 5 has no preferences, so we should assume they want an email right? I'm also not sure how to group this for you. If a member has multiple subscriptions, which one do you want to keep?
I built an SQL Fiddle and tested what I have and it's really close. I hope this can at least push you in the right direction and I will edit the answer as needed.
EDIT
The following will give you what you want, but it is not always recommended. If you really don't care about the subscription dates (as long as it meets the criteria in the where clause) and you really don't care about the category for the user, just add GROUP BY m.member_id to get one row for each member.
So, the final query is like these, tested and working:
SELECT
m.member_id,
m.email,
l.catid as licencedToCat,
c.cat_name as categoryName,
l.valid_from as licenceStart,
l.valid_to as licenceEnd,
COALESCE(p.pref, 1) pref
FROM members m
JOIN licence l ON l.subid = m.member_id
JOIN categories c ON c.cat_id = l.catid
LEFT JOIN preferences p ON p.subid= m.member_id AND p.cat_id = l.cat_id
LEFT JOIN recipients r ON r.subid = m.member_id
WHERE l.catid IN (1,2,3)
AND start_date <= '2014-12-16' AND end_date >= '2014-12-16'
AND COALESCE(p.pref, 1) = 1
AND COALESCE(r.emailid, 0) = 0-- assuming with emailid = 0 it remains valid as recipient
GROUP BY m.member_id
However, for the purpose of the query it should only have DISTINCT m.* in the SELECT clause which would discard the GROUP BY

Match data against one column in mysql

Here is the sqlFiddle
I want to filter the users who have selected entities ,So if I want to filter user with entity say entity having ids "1" and "3" I hope to get the users which have both of these entities.
No of entities selected can vary in number .
Query I am using is
SELECT user_id from user_entities where entity_id IN(1,3)
but for obvious reason it is returing me result as
+----+-----------+---------+--------+
| ID | ENTITY_ID | USER_ID | STATUS |
+----+-----------+---------+--------+
| 1 | 1 | 3 | 1 |
| 2 | 3 | 3 | 1 |
| 7 | 1 | 2 | 1 |
| 29 | 3 | 1 | 1 |
+----+-----------+---------+--------+
So I will apply distinct to it it will give me user id with ids 1,2,3 but I only want user 3 as this is the only user having both entities .
What can be modified to get the exact results
You could join the table to itself specifying both IDs as part of the join condition:
SELECT e1.user_id
FROM user_entities e1
INNER JOIN user_entities e2
ON e1.user_id = e2.user_id AND
e1.entity_id = 1 AND
e2.entity_id = 3;

How can I get a history like query on MySQL?

I'd like a little help here.
I'm building a database in MySQL where I will have a bunch of different activities. Each activity is part of a list.
So, I have the following tables on my database.
List
id
name
Activity
id
name
idList (FK to List)
I also want to know when each activity is finished (you can finish the same activity many times). To accomplish that, I have another table:
History
date
idActivity (FK to activity)
When the user finishes an activity, I add the id of this activity and the current time the activity was finished, to the History table.
I want to get the entire list with the date it was finished. When an activity has not been finished, I want it to show the date as null.
But, getting the list just once is easy. A simple Left Outer Join will do the trick. My issue here is that I want to get the ENTIRE list everytime a date appears on the history table.
This is what I'm looking for:
List:
id | name
1 | list1
Activity:
id | name | idList
1 | Activity1 | 1
2 | Activity2 | 1
3 | Activity3 | 1
4 | Activity4 | 1
5 | Activity5 | 1
6 | Activity6 | 1
History:
date | idActivity
17/07/14 | 1
17/07/14 | 3
17/07/14 | 4
17/07/14 | 6
16/07/14 | 2
16/07/14 | 3
16/07/14 | 5
Expected Result:
idActivity | idList | activityName | date
1 | 1 | Activity1 | 17/07/14
2 | 1 | Activity2 | NULL
3 | 1 | Activity3 | 17/07/14
4 | 1 | Activity4 | 17/07/14
5 | 1 | Activity5 | NULL
6 | 1 | Activity6 | 17/07/14
1 | 1 | Activity1 | NULL
2 | 1 | Activity2 | 16/07/14
3 | 1 | Activity3 | 16/07/14
4 | 1 | Activity4 | NULL
5 | 1 | Activity5 | 16/07/14
6 | 1 | Activity6 | NULL
The "trick" is to use a CROSS JOIN (or semi-cross join) operation with a distinct list of dates from the history table, to produce the set of rows you want to return.
Then a LEFT JOIN (outer join) to the history table to find the matching history rows.
Something like this:
SELECT a.id AS idActivity
, a.idList AS idList
, a.name AS activityName
, h.date AS `date`
FROM activity a
CROSS
JOIN ( SELECT s.date
FROM history s
GROUP BY s.date
) r
LEFT
JOIN history h
ON h.idActivity = a.id
AND h.date = r.date
ORDER
BY r.date
, a.id
That query gets the six rows from activity, and two rows (distinct values of date) from history (inline view aliased as r). The CROSS JOIN operation matches each of the six rows with each of the two rows, to produce a Cartesian product of 12 rows.
To get the rows returned in the specified order, we order by date, and then by activity.id.

many-to-many and many-to-many intersections

Say I have a database that has people, grocery stores, and items you can buy in the store, like so:
Stores People Foods
----------------- ------------------ ------------------
| id | name | | id | name | | id | name |
----------------- ------------------ ------------------
| 1 | Giant | | 1 | Jon Skeet | | 1 | Tomatoes |
| 2 | Vons | | 2 | KLee1 | | 2 | Apples |
| 3 | Safeway | ------------------ | 3 | Potatoes |
----------------- ------------------
I have an additional table which keep track of which stores sell what:
Inventory
--------------------
| store_id| food_id|
--------------------
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
--------------------
And I have another table that has shopping lists on it
Lists
---------------------
| person_id| food_id|
---------------------
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 3 |
---------------------
My question is, given a person, or their id, what is the best way to figure out what stores they can go to so they will get everything on their list. Is there a pattern for these types of computations in MySQL?
My attempt (very ugly and messy) is something like:
-- Given that _pid is the person_id we want to get the list of stores for.
SELECT stores.name, store_id, num, COUNT(*) AS counter
FROM lists
INNER JOIN inventory
ON (lists.food_id=inventory.food_id)
INNER JOIN (SELECT COUNT(*) AS num
FROM lists WHERE person_id=_pid
GROUP BY person_id) AS T
INNER JOIN stores ON (stores.id=store_id)
WHERE person_id=_pid
GROUP BY store_id
HAVING counter >= num;
Thanks for your time!
Edit SQL Fiddle with Data
If I were to solved the problem, I'll join the four tables with their linking column (specifically the foreign keys) then a subquery on the HAVING clause to count the number of items on the list for each person. Give this a try,
SET #personID := 1;
SELECT c.name
FROM Inventory a
INNER JOIN Foods b
ON a.food_id = b.id
INNER JOIN Stores c
ON a.store_id = c.id
INNER JOIN Lists d
ON d.food_id = b.id
WHERE d.person_id = #personID
GROUP BY c.name
HAVING COUNT(DISTINCT d.food_id) =
(
SELECT COUNT(*)
FROM Lists
WHERE person_ID = #personID
)
SQLFiddle Demo
#JohnWoo: why DISTINCT?
Another one...
SET #pid=2;
SELECT store_id, name
FROM inventory
JOIN lists ON inventory.food_id=lists.food_id
JOIN stores ON store_id=stores.id
WHERE person_id=#pid
GROUP BY store_id
HAVING COUNT(*)=(
SELECT COUNT(*)
FROM lists
WHERE person_id=#pid
);