MySQL Query over multiple tables in the given example - mysql

I'd like to receive a table from my database but I am unable to form the query.
This is what I like to achieve: Think of a group of users who shall be invited to an event.
To find a date A list of dates are provided by the host.
So far we have these tables:
Users:
Id
Name
7
Sally
2
Bob
3
John
4
Lisa
5
Joe
6
Jane
Events
Id
Name
1
Birthdayparty
2
Barbecue
3
Dinner
Event Users:
Id
UserId
EventId
1
7
1
(Sally is invited to bp)
2
2
1
(Bob, too)
3
4
1
(and Lisa)
4
1
2
(Sally is invited. to Bbe)
5
5
2
(also Joe)
6
4
2
(and Lisa)
So far for the structure of main parts of the db.
Now lets put some possible dates for the birthday party
EventProposal:
Id
Event
Date
1
1
5. March
Birthday dates
2
1
6. March
3
1
8. March
4
1
10. March
5
3
4. April
Dinner
6
3
5. April
Now the last table holds which user selected which dates for an event.
EventProposalSelection:
Id
EventId
UserId
DateId
1
1
1
1
Sally selected 5. March for birthday
2
1
1
2
Sally selected 6. March for birthday
3
1
1
3
Sally selected 8. March for birthday
4
1
2
2
Bob selected 6. March for birthday
5
1
2
3
Bob selected 8. March for birthday
6
1
4
1
Lisa selected 5. March for birthday
7
1
4
2
Lisa selected 6. March for birthday
8
1
4
4
Lisa selected 10. March for birthday
What I like to know is if a user has picked a date for an event.
I want to see all dates of a specific event for a specific user
(where clause contains userId and eventId)
If I ask for Sally in combination of Birthday
(where userId = 1 and eventId = 1)
I'd like to receive
DateId
Checked
1
true
2
true
3
false
4
true
The tables are properly constrained and related to each other
How can I achieve this in MySQL?
EDIT:
select
e.Name EventName,
e.id,
dp.DateProposal DateOfEvent,
coalesce( u.Name, '' ) GuestName,
-- due to left-join, see if the date is chosen or NULL (not chosen)
case when dps.dateid IS NULL then false else true end DateWasPicked
from
-- start with events
Event e
-- Now, what dates were birthday parties POSSIBLE to attend
JOIN EventProposal dp
on e.id = dp.EventId
-- NOW, what dates by given users were selected
-- BUT, since not all dates may be selected, do a LEFT JOIN
LEFT JOIN EventProposalSelection dps
on dp.id = dps.dateid
-- and finally who selected the given date
-- and again LEFT-JOIN since a user may not pick all dates
LEFT JOIN User u
on dps.userid = u.id
-- prevent getting ALL users for an event
AND u.Id = 7 --Sally
where
-- but only birthday parties
e.Id = 1
-- Tricky here because you want ALL POSSIBLE event dates,
-- but ONLY those for Sally
AND ( dps.dateid IS NULL OR u.Id = 7 )
order by
-- and suggest you actually use datetime based column
-- as you can use functions to get the format of the date.
dp.DateProposal
Lead to
which seems to be fine, but when running for Bob (UserId = 2)
there is a date missing
And running for John (UserId = 3)

Ok, so lets take this one step at a time. You are interested in a SPECIFIC event, and all POSSIBLE dates FOR said event. Then, based on a specific user, if they had (or not) picked any of the possible dates. And by the user ID, get the name too.
Sample data. Your version of data had the sample selections with Sally's ID of 1, not 7. So this is a sample set I ran with using 7 as the basis for Sally
create table users ( id integer, name varchar(10))
insert into users ( id, name ) values
( 7, 'Sally' ),
( 2, 'Bob' ),
( 3, 'John' ),
( 4, 'Lisa' ),
( 5, 'Joe' ),
( 6, 'Jane' )
create table Events ( id int, name varchar(15))
insert into Events (id, name ) values
( 1, 'Birthdayparty' ),
( 2, 'BBQ' ),
( 3, 'Dinner' )
create table EventUsers ( id int, userid int, eventid int )
insert into EventUsers ( id, userid, eventid ) values
( 1, 7, 1 ),
( 2, 2, 1 ),
( 3, 4, 1 ),
( 4, 1, 2 ),
( 5, 5, 2 ),
( 6, 4, 2 )
create table EventProposal (id int, event int, date datetime )
insert into EventProposal ( id, event, date ) values
( 1, 1, '2022-03-05' ),
( 2, 1, '2022-03-06' ),
( 3, 1, '2022-03-08' ),
( 4, 1, '2022-03-10' ),
( 5, 3, '2022-04-04' ),
( 6, 3, '2022-04-05' )
create table EventProposalSelection ( id int, eventid int, userid int, DateID int )
insert into EventProposalSelection ( id, eventid, userid, dateid ) values
( 1, 1, 7, 1 ),
( 2, 1, 7, 2 ),
( 3, 1, 7, 3 ),
( 4, 1, 2, 2 ),
( 5, 1, 2, 3 ),
( 6, 1, 4, 1 ),
( 7, 1, 4, 2 ),
( 8, 1, 4, 4 )
select
AllEventDates.id,
AllEventDates.EventName,
AllEventDates.DateOfEvent,
u.id UserID,
coalesce( u.Name, '' ) GuestName,
-- due to left-join, see if the date is chosen or NULL (not chosen)
case when eps.dateid IS NULL
then 'false' else 'true' end DateWasPicked
from
Users u
-- this query get all events and all possible dates regardless
-- of who may have supplied a selection to attend
JOIN
( select
e.id,
e.Name EventName,
ep.id EventProposalID,
ep.date DateOfEvent
from
Events e
JOIN EventProposal ep
on e.id = ep.Event
where
-- but only birthday parties
e.Name = 'Birthdayparty' ) AllEventDates
on 1=1
-- NOW, left join for a given one person
LEFT JOIN EventProposalSelection eps
on eps.userid = u.id
AND AllEventDates.EventProposalID = eps.dateid
-- and finally who selected the given date
-- and again LEFT-JOIN since a user may not pick all dates
where
u.id = 7
order by
-- and suggest you actually use datetime based column
-- as you can use functions to get the format of the date.
AllEventDates.DateOfEvent

Related

Join to another table only matching specific records

I have a table of ports:
drop table if exists ports;
create table ports(id int, name char(20));
insert into ports (id, name ) values
(1, 'Port hedland'),
(2, 'Kwinana');
And a table of tariffs connected to those ports:
drop table if exists tariffs;
create table tariffs(id int, portId int, price decimal(12,2), expiry bigint(11));
insert into tariffs (id, portId, price, expiry ) values
(1, 2, 11.00, 1648408400),
(2, 2, 12.00, 1648508400),
(3, 2, 13.00, 1648594800),
(4, 2, 14.00, 1651273200),
(5, 2, 15.00, 2250000000 );
insert into tariffs (id, portId, price, expiry ) values
(1, 1, 21.00, 1648408400),
(2, 1, 22.00, 1648508400),
(3, 1, 23.00, 1648594800),
(4, 1, 24.00, 1651273200),
(5, 1, 25.00, 2250000000 );
Each tariff has an expiry.
I can easily make a query to figure out the right tariff for as specific date for each port. For example at timestamp 1648594700 the right tariff is:
SELECT * FROM tariffs
WHERE 1648594700 < expiry AND portId = 2
ORDER BY expiry
LIMIT 1
Result:
id portId price expiry
3 2 13.00 1648594800
However, in my application I want to be able to pull in the right tariff starting from the ports record.
For one record, I can do this:
SELECT * FROM ports
LEFT JOIN tariffs on tariffs.portId = ports.id
WHERE 1648594700 < tariffs.expiry AND ports.id = 2
LIMIT 1
Result:
id name id portId price expiry
2 Kwinana 3 2 13.00 1648594800
This feels a little 'dirty', especially because I am doing a lookup on a record, and then forcing only one result using LIMIT. But, OK.
What I cannot do, and can't work out how to do, is a query that will return a list of ports, and each port having a price field that matches the constraint above (that is, the record with the highest expiry compared to 1648594700 for each port).
This obviously won't work:
SELECT * FROM ports
left join tariffs on tariffs.portId = ports.id
where 1648594700 < tariffs.expiry
Since the result of the query, testing with timestamp 1648594700, would be:
id name id portId price expiry
2 Kwinana 3 2 13.00 1648594800
2 Kwinana 4 2 14.00 1651273200
2 Kwinana 5 2 15.00 2250000000
1 Port he 3 1 23.00 1648594800
1 Port he 4 1 24.00 1651273200
1 Port he 5 1 25.00 2250000000
Instead, the result for all ports (before further filtering) should be:
id name id portId price expiry
2 Kwinana 3 2 13.00 1648594800
1 Port he 3 1 23.00 1648594800
Is there a clean, non-hacky way to have such a result?
As an added constraint, is this possible for this to be done in ONE query, without temp tables etc.?
You can select the lowest expiry, do your join and only take the rows having this minimum expiry:
SELECT p.id, p.name, t.id, t.portId, t.price, t.expiry
FROM ports p
LEFT JOIN tariffs t ON p.id = t.portId
WHERE expiry = (SELECT MIN(expiry) FROM tariffs WHERE 1648594700 < expiry)
ORDER BY p.id;
This will get your desired result, please see here: db<>fiddle
On MySQL 8+, ROW_NUMBER should work here:
WITH cte AS (
SELECT p.id, p.name, t.price, t.expiry,
ROW_NUMBER() OVER (PARTITION BY p.id ORDER BY t.expiry) rn
FROM ports p
LEFT JOIN tariffs t ON t.portId = p.id
WHERE t.expiry > 1648594700
)
SELECT id, name, price, expiry
FROM cte
WHERE rn = 1
ORDER BY id;
This logic would return one record for each port having the nearest expiry.

Custom query with group by and then count

I am using events.I would like to know how to calculate sum in event or using single query
http://sqlfiddle.com/#!9/ad6d1c/1
DDL for question:
CREATE TABLE `table1` (
`id` int(11) NOT NULL,
`group_id` int(11) NOT NULL DEFAULT '0',
`in_use` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0->in_use,1->not_in_use',
`auto_assign` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0->Yes,1->No'
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `table1`
ADD PRIMARY KEY (`id`);
ALTER TABLE `table1`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
INSERT INTO `table1` (`id`, `group_id`, `in_use`, `auto_assign`) VALUES
(1, 3, 1, 0),(2, 2, 0,1),(3, 1, 1, 1),(4, 3, 1, 0),(5, 3, 0, 0),(6, 3, 0, 1),
(7, 3, 1, 0),(8, 3, 0, 1),(9, 3, 0, 1),(10, 3, 0, 1),(11, 3, 0, 1),(12, 3, 1, 1),
(13, 3, 1, 0),(14, 3, 0, 0),(15, 3, 0, 0),(16, 3, 0, 0),(17, 3, 0, 0),(18, 3, 1, 1),
(19, 3, 0, 0),(20, 3, 0, 0)
Expected Output :
| count | in_use | auto_assign | sum | check_count |
|-------|--------|-------------|------|------------ |
| 7 | 0 | 0 | 11 | 5 |
| 5 | 0 | 1 | 07 | 3 |
| 4 | 1 | 0 | 11 | 5 |
| 2 | 1 | 1 | 07 | 3 |
Here we can see that auto_assign=0 have total 11 count(7+4) and
auto_assign=1 have 7 count(5+2) this count should be stored into new column sum.
check_count column is percentage value of sum column.Percentage will be predefined.
Lets take 50%, So count 11(sum column value) ->50% = 5.5 = ROUND(5.5) == 5(In integer). Same way count 7(sum column value)->50% = 3.5 =ROUND(3.5)=3(Integer)
Here 5 > 4(auto_assign=0 and in_use=1 ).So have to insert record into another table(table2). if not then not.
Same way, If 3 >2 then also need to insert record into another table(table2).if not then not.
Note : This logic I would like to implement in event
This is bit complicated, but please suggest me how to do this in event.
Detail clarification :
here percentage_Value is 5 for auto_assign =0.But auto_assign=0 and in_use=1 have count is 4 which less than 5 ,then have to insert record into table 2.
suppose,if we get count is 6 for auto_assign=0 and in_use=1 ,Then no need to insert record into table2.
Same way,
here percentage_Value is 3 for auto_assign =1.But auto_assign=1 and in_use=1 have count is 2 which less than 3 ,then have to insert record into table 2.
suppose,if we get count is 4 for auto_assign=1 and in_use=1 ,Then no need to insert record into table2.
Insert query into table2:
Insert into table2(cli_group_id,auto_assign,percentage_value,result_value) values(3,0,5,4)
DEMO Fiddle
Break the problem down: we need a count of the records by auto_Assigns; so we generate a derived table (B) with that value and join back to your base table on auto_Assign. This then gives us the column we need for some and we use the truncate function and a division model to get the check_count
SELECT count(*), in_use, A.Auto_Assign, B.SumC, truncate(B.SumC/2,0) as check_Count
FROM table1 A
INNER JOIN (Select Auto_Assign, count(*) sumC
from table1
where Group_ID = 3
Group by Auto_Assign) B
on A.Auto_Assign = B.Auto_Assign
WHERE GROUP_ID = 3
Group by in_use, A.Auto_Assign
we can eliminate the double where clause by joining on it:
SELECT count(*), in_use, A.Auto_Assign, B.SumC, truncate(B.SumC/2,0) as check_Count
FROM table1 A
INNER JOIN (Select Auto_Assign, count(*) sumC, Group_ID
from table1
where Group_ID = 3
Group by Auto_Assign, Group_ID) B
on A.Auto_Assign = B.Auto_Assign
and A.Group_ID = B.Group_ID
Group by in_use, A.Auto_Assign
I'd need clarification on the rest of the question: I'm not sure what 5 > 4 your'e looking at and I see no 3 other than the check count but that's not "the same way" so I'm not sure what you're after.
Here 5 > 4(auto_assign=0 and in_use=1 ).So have to insert record into another table(table2). if not then not.
Same way, If 3 >2 then also need to insert record into another table(table2).if not then not.
Note : This logic I would like to implement in event
This is bit complicated, but please suggest me how to do this in event.
So to create the event: DOCS
Which results in:
CREATE EVENT myevent
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 6 Minutes
DO
INSERT INTO table2
SELECT count(*) as mCount
, in_use
, A.Auto_Assign
, B.SumC, truncate(B.SumC/2,0) as check_Count
FROM table1 A
INNER JOIN (SELECT Auto_Assign, count(*) sumC, Group_ID
FROM table1
WHERE Group_ID = 3
GROUP BY Auto_Assign, Group_ID) B
ON A.Auto_Assign = B.Auto_Assign
AND A.Group_ID = B.Group_ID
GROUP BY in_use, A.Auto_Assign

Selecting row data as columns

I have a contacts table where contact_type_id means:
1- email2 - phone3 - skype
The example below shows 3 different users with different types of contacts.
First user has phone number and Skype. The second one has only email. The third one has all 3 types: email, phone number & Skype.
user_id contact_type_id value
1 2 353234
1 3 skypeLogin
2 1 example#mail.com
3 1 example2#mail.com
3 2 123345
3 3 skypeLogin2
Issue needed to be clarified
How can I select this data as the following table
user_id email phone skype
1 null 353234 skypeLogin
2 example#mail.com null null
3 example2#mail.com 123345 skypeLogin2
Pivot is what you're looking for. If you have an unknown number of contact_type_id's then google Dynamic Pivot - there are tons of examples on SO.
This is for SQL Server:
CREATE TABLE #Contacts
(
user_id INTEGER
,contact_type_id INTEGER
,value NVARCHAR(20)
);
INSERT INTO #Contacts
( user_id, contact_type_id, value )
VALUES
( 1, 2, '353234' ),
( 1, 3, 'skypeLogin' ),
( 2, 1, 'example#mail.com' ),
( 3, 1, 'example2#mail.com' ),
( 3, 2, '123345' ),
( 3, 3, 'skypeLogin2' );
SELECT
pvt.user_id
,pvt.[1] email
,pvt.[2] phone
,pvt.[3] skype
FROM
#Contacts
PIVOT( MAX(value) FOR contact_type_id IN ( [1], [2], [3] ) ) pvt;
user_id email phone skype
----------- -------------------- -------------------- --------------------
1 NULL 353234 skypeLogin
2 example#mail.com NULL NULL
3 example2#mail.com 123345 skypeLogin2
(3 row(s) affected)
If MySQL then use CASE WHEN along with GROUP BY
SELECT
user_id,
MAX(CASE WHEN contact_type_id = 1 THEN `value` END) AS email,
MAX(CASE WHEN contact_type_id = 2 THEN `value` END) AS phone,
MAX(CASE WHEN contact_type_id = 3 THEN `value` END) AS skype
FROM your_table
GROUP BY user_id
WORKING DEMO
This works on sql server and mysql. Note value should be escaped in sql server.
USE sandbox;
/*
create table users(user_id int,contact_type_id int, value varchar(20));
insert into users values
(1, 2, '353234'),
(1, 3, 'skypeLogin'),
(2, 1, 'example#mail.com'),
(3, 1, 'example2#mail.com'),
(3, 2, '123345'),
(3, 3, 'skypeLogin2');
*/
select user_id,
max(case when contact_type_id = 1 then value else '' end) as email,
max(case when contact_type_id = 2 then value else '' end) as tel,
max(case when contact_type_id = 3 then value else '' end) as skpe
from users
group by user_id;
One is Pivot table, other can be below logic:
Create 3 Tables :
1. With user_id and Value for only email i.e. where contact_type_id=1
2. With user_id and Value for only phone i.e. where contact_type_id=2
3. With user_id and Value for only skype i.e. where contact_type_id=3
4. Outer join the three tables.
Below code does the same:
Select A.user_id,A.value as email, B.value as Phone, C.Value as Skype
from contacts A
outer join contacts B
On A.User_id=B.User_ID and A.value=1 and B.value=2
outer join contacts C
On A.User_id=C.User_ID and C.Value=3

Selecting distinct records that has last creation date in similar records

I have a table: messages with this structure:
`ID` // auto increment
`SenderUserID` // foreign key: `ID` from `User` table
`ReceiverUserID` // foreign key: `ID` from `User` table
`Message`
`DateCreated` // timestamp
I need to get distinct ReceiverUserIDs from table by this conditions:
Sender user should have conversations with receiver users.
Selected distinct ReceiverUserID should give us last DateCreated message.
So in the results, I'll have user last conversations ordered by creation date DESC.
When I distinct in select query, mysql return a first record, but I need maximum DateCreated for selected `ReceiverUserID to get last message in top of results.
SELECT DISTINCT `m`.`ReceiverUserID` AS `ID` FROM `messages` `m`
WHERE (`m`.`SenderUserID` = :UserID OR `m`.`ReceiverUserID` = :UserID)
ORDER BY `m`.`DateCreated` DESC
//:UserID means LogedInUserID
It is good to know It will be exactly like facebook messages board.
ID, SenderUserID, ReceiverUserID, Message, DateCreated
1, 12, 11, 'Hi admin', 2012-10-24 11:07:00
2, 11, 12, 'Hi guy', 2012-10-24 11:08:00
3, 11, 13, 'Hello dear customer', 2012-10-24 11:12:00
4, 11, 13, 'Are you there?', 2012-10-24 11:13:00
5, 13, 11, 'Hiiii', 2012-10-24 11:14:00 // last conversation betwen 11 & 13
Current logedin user id: 11
SELECT `m`.`ReceiverUserID` AS `ID`, MAX(`m`.`DateCreated`) as `lastmsg`
FROM `messages` `m` WHERE (`m`.`SenderUserID` = 11 OR `m`.`ReceiverUserID` = 11)
GROUP BY `m`.`ReceiverUserID`
We have problem in this section:
It is not return our last conversation between 11 and 13 when receiver user answered the sender message, It will be not seen in results
Results:
ID, lastmsg
12, 2012-10-24 11:08:00
13, 2012-10-24 11:13:00 // Should be: 13, 2012-10-24 11:14:00
If you're asking for a way to be able to keep all of your current functionality and work flows, yet keep the data in a single table I think you're pretty close.
Instead of having the conversationId be a key to a different table, I would instead have it point to the ID of the message that began the conversation. This would create a parent-child relationship between messages that began a conversation and all those that followed after it. To be able to see all conversations, you would just select all messages where the conversationId is null. Below is a representation of a 2 message conversation:
+----+---------+------+------------------+----------------+--------+----------+
| id | message | read | time | conversationId | toUser | fromUser |
+----+---------+------+------------------+----------------+--------+----------+
| 1 | test 1 | 0 | (some timestamp) | null | 3 | 4 |
| 2 | test 2 | 0 | (some timestamp) | 1 | 4 | 3 |
+----+---------+------+------------------+----------------+--------+----------+
The conversation was initiated by user 3. All messages in the conversation can be filter by conversationId. One limitation of this design is that only 2 users can be apart of the conversation.
Update
You could get the last message given a conversation id this way:
SELECT id, message
FROM userMessages
WHERE conversationId = {conversationId}
ORDER BY time DESC
LIMIT 1
As I mentioned you have to do the same with SenderUserID...
SELECT `ID`, MAX(`lastmsg`) FROM
( SELECT `m`.`ReceiverUserID` AS `ID`, MAX(`m`.`DateCreated`) as `lastmsg` FROM `messages` `m`
WHERE `m`.`SenderUserID` = :UserID
GROUP BY `m`.`ReceiverUserID`
UNION
SELECT `m`.`SenderUserID` AS `ID`, MAX(`m`.`DateCreated`) as `lastmsg` FROM `messages` `m`
WHERE `m`.`ReceiverUserID` = :UserID
GROUP BY `m`.`SenderUserID`
) as table2 GROUP BY `ID` ORDER BY `lastmsg`
Just use a MAX?
SELECT ReceiverUserID, MAX(DateCreated) AS LatestDateCreated
FROM messages
WHERE (SenderUserID = :UserID OR ReceiverUserID = :UserID)
GROUP BY ReceiverUserID
EDIT
Give this a try:-
SELECT IF( SenderUserID =11, ReceiverUserID, SenderUserID ) AS ID, MAX( DateCreated ) AS LatestDateCreated
FROM messages
WHERE (SenderUserID =11
OR ReceiverUserID =11
)
GROUP BY IF( SenderUserID =11, ReceiverUserID, SenderUserID )

How to get ID for INSERT if name already used, else generate new one

I'm having a bit of trouble with an INSERT query.
I have a table I'm inserting a value into that's like this:
TABLE cars
ID Brand Model B_ID
---------------------------
1 Ford Escort 1
2 Ford Focus 1
3 Nissan Micra 2
4 Renault Megane 3
5 Ford Mustang 1
ID is unique and B_ID is the same ID for every same brand.
When inserting a new entry I want to be able to check if a brand is already in there and use that same B_ID otherwise I want to increment the highest B_ID and insert that.
I've got this far:
INSERT INTO 'cars' ('brand', 'model', 'B_ID')
VALUES (
'Nissan'
'Note'
'SELECT B_ID FROM cars WHERE brand = 'Nissan'
)
How can I get the highest B_ID and increment it by one if there is no match with my subquery because it's a new brand?
I'm using MySQL.
INSERT INTO `cars` (`brand`, `model`, `B_ID`)
select 'Nissan', 'Note', coalesce(Max(B_ID),0)+1 FROM cars WHERE brand = 'Nissan'
Until you normalize your tables:
INSERT INTO cars
(brand, model, B_ID)
SELECT 'Nissan'
, 'Note'
, COALESCE( ( SELECT B_ID
FROM cars
WHERE brand = 'Nissan'
LIMIT 1
)
, ( SELECT MAX(B_ID)
FROM cars
) + 1
, 1 --- this is for the case when the table is empty
)
Also notice that if you have multiple concurrent INSERT, you may end with rows that have different brand but same B_ID.