Complex SQL query - mysql

I have need to write a query which is a little complicated for me to put together. The basic idea is to match a couple of fields from different tables and then edit another table based on the result.
There are three tables involved:
Schedules: sch_id, date, schedule, event_id
Link_Location_Schedules: id, loc_id, sch_id
Link_Location_Events: id, loc_id, event_id
Now what I need to try and do is:
find schedules that are set after todays date in "Schedules".
for these schedules get location ids from Link_Location_Events where event_ids equal the schedule event id.
for each of the matched schedules (sch_id) and returned locations (loc_id) check if the pair already exist in the Link_Location_Schedules, if not insert them.
Here are some SQL queries I have done for the above, I just need to combine them some how:
SELECT sch_id FROM 'Schedules' WHERE DATE_FORMAT(sports_schedule_insert_date_time, "%Y-%m-%d") >= '2012-11-14';
SELECT loc_id from Link_Location_Events, Schedules WHERE Link_Location_Events.event_id = Schedules.event_id;

sounds like a simple insert from select statement...
insert into Link_Location_Schedules
( loc_id,
sch_id )
select
PreQuery.loc_id,
PreQuery.sch_id
from
( select
s.sch_id,
lle.loc_id
from
Schedules s
join Link_Location_Events lle
on s.event_id = lle.event_id ) PreQuery
LEFT JOIN Link_Location_Schedules lls
on PreQuery.loc_id = lls.loc_id
and PreQuery.sch_id = lls.sch_id
where
lls.loc_id is null
The innermost prequery is to get all possible schedule / location IDs. From that, left-join to the existing location/schedules on those found. Then, the WHERE clause will return only those where NO MATCH WAS FOUND (thus the lls.loc_id is null). Take that result and insert directly into the schedule / location table.

Related

Comparing Datetime / Timestamp Values From Two Databases for Employee Scheduling (MySQL)

My goal is to compare a table of employee schedules from one database, to their actual clock in times in another.
Here is the query I'm starting with to simply find a singular clock-in time when searching the clock-in database:
SELECT *
FROM `users_log`
WHERE `user` = 'Employee'
AND `type` = 'Login'
ORDER BY ABS(`logintime` - UNIX_TIMESTAMP(`scheduled_logintime_from_2nd_database`)))
LIMIT 1
Not sure on the best way to do this (specifically because using the ORDER BY in a subquery is iffy). But ultimately I'd want some sort of parent query / join that would join the two databases on the Employee Name (user), and show both the scheduled logintime, and actual logintime from the two databases.
In the above example, for scheduled_logintime_from_2nd_database I'm just using a text string for testing, eventually that'd be the actual column from the other database. (note one is a timestamp, the other a datetime, hence the UNIX_TIMESTAMP function).
You're very close.
I suggest you proceed as follows:
First, do an appropriate JOIN operation to gather your two sources of data together. Something like this:
SELECT DATE(ul.logintime) day,
ul.name, ul.logintime,
UNIX_TIMESTAMP(sch.scheduled_logintime) sched_time
FROM users_log ul
JOIN schedule sch ON ul.name = sch.name
AND DATE(ul.logintime) = DATE(sch.scheduled_logintime)
This should get you a bunch of rows, one or more for each day and user, showing scheduled and actual times.
Then you can use that as a subquery, perhaps doing something like this:
SELECT name, day,
MAX(ABS(logintime - sched_time)),
MIN(ABS(logintime - sched_time))
FROM ( SELECT DATE(ul.logintime) day,
ul.name, ul.logintime,
UNIX_TIMESTAMP(sch.scheduled_logintime) sched_time
FROM users_log ul
JOIN schedule sch ON ul.name = sch.name
AND DATE(ul.logintime) = DATE(sch.scheduled_logintime)
)
GROUP BY name, day
That should give you each name's best and worst adherence to a schedule in each day.

Compare 1 row of returned data from multi-row nested SQL statement

I have two tables... EVENT (Primary key is EID) (containing all the general event data) and SINGLE_EVENT (Primary Key is SEID) (containing the information about each individual event related to a particular event ID i.e. date and time of the individual event, location of the venue etc.
In summary I want to find the 'single event' which is happening soonest for each overall event(EID) -- this should return a single event for each unique EID in the SINGLE_EVENTS table
I then want to bind the overall EVENT information to the returned results.
The problem is that, with the current MySQL statement I have below, I need to select * for the nested query to have all the information it needs to process the query but I also DONT want to select all that information because I only need the SEID from that query result (and not the whole table)
here is my query (obviously executed without the comments):
<!-- non working outer query...
SELECT SINGLE_EVENT.SEID, EVENT.* FROM EVENT
INNER JOIN SINGLE_EVENT ON SINGLE_EVENT.EID=EVENT.EID
WHERE SINGLE_EVENT.SEID IN (
-->
<!-- working sub query...
select * from SINGLE_EVENT t
inner join (select eid, min(date) as MinDate from SINGLE_EVENT
group by eid) tm
on t.eid=tm.eid and t.date=tm.MinDate and t.date>=sysdate()
-->
)
I am new to SQL and dont know how best to find this information out from the tables. I feel that I'm very close to it working but I keep getting the message "Operand should contain 1 column(s)" because of the multi-column return value of the sub-query.
Any help is appreciated.
You can simply join first with your aggregates and then with your real table:
select e.*, se.*
from event e
join
(
select eid, min(date) as date
from single_event
where date >= sysdate()
group by eid
) first_events on first_events.eid = e.eid
join single_event se on se.eid = first_events.eid and se.date = first_events.date;
In your subquery, change:
select * from SINGLE_EVENT t
to
select SEID from SINGLE_EVENT t
You're effectively telling it to search the whole table for the SEID, which isn't allowed in a subquery.
If you specify which column the subquery is looking for matches, that should solve your problem.

How to store SQL Query result in table column

I'm aware of the INSERT INTO table_name QUERY; however, I'm unsure how to go about achieving the desired result in this case.
Here's a slightly contrived example to explain what I'm looking for, but I'm afraid I cannot put it more succiently.
I have two tables in a database designed for a hotel.
BOOKING and CUSTOMER_BOOKING
Where BOOKING contains PK_room_number, room_type, etc. and CUSTOMER_BOOKING contains FK_room_number, FK_cusomer_id
CUSTOMER_BOOKING is a linking table (many customers can make many bookings, and many bookings can consist of many customers).
Ultimately, in the application back-end I want to be able to list all rooms that have less than 3 customers associated with them. I could execute this a separate query and save the result in the server-side scripting.
However, a more elegant solution (from my point of view) is to store this within the BOOKING table itself. That is to add a column no_of_bookings that counts the number of times the current PK_room_number appears as the foreign key FK_room_number within the CUSTOMER_BOOKING table. And why do this instead? Because it would be impossible for me to write a single complicated query which will both include the information from all ROOMS, among other tables, and also count the occurrences of bookings, without excluding ROOMS that don't have any bookings. A very bad thing for a hotel website attempting to show free rooms!
So it would look like this
BOOKING: PK_room_number (104B) room_type (double) room_price (high), no_of_bookings (3)
BOOKING: PK_room_number (108C) room_type (single) room_price (low), no_of_bookings (1)
CUSTOMER_BOOKING: FK_room_number (104B) FK_customer_id (4312)
CUSTOMER_BOOKING: FK_room_number (104B) FK_customer_id (6372)
CUSTOMER_BOOKING: FK_room_number (104B) FK_customer_id (1112)
CUSTOMER_BOOKING: FK_room_number (108C) FK_customer_id (9181)
How would I go about creating this?
Because it would be impossible for me to write a single complicated
query which will both include the information from all ROOMS, among
other tables, and also count the occurrences of bookings, without
excluding ROOMS that don't have any bookings.
I wouldn't say it's impossible and unless you're running into performance issues, it's easier to implement than adding a new summary column:
select b.*, count(cb.room_number)
from bookings b
left join customer_booking cb on b.room_number = cb.room_number
group by b.room_number
Depending on your query may need to use a derived table containing the booking counts for each room instead instead
select b.*, coalesce(t1.number_of_bookings,0) number_of_bookings
from bookings b
left join (
select room_number, count(*) number_of_bookings
from customer_booking
group by room_number
) t1 on t1.room_number = b.room_number
You have to left join the derived table and select coalesce(t1.number_of_bookings,0) in case a room does not have any entries in the derived table (i.e. 0 bookings).
A summary column is a good idea when you're running into performance issues with counting the # of bookings each time. In that case I recommend creating insert and delete triggers on the customer_booking table that either increment or decrement the number_of_bookings column.
You could do it in a single straight select like this:
select DISTINCT
b1.room_pk,
c1.no_of_bookings
from cust_bookings b1,
(select room_pk, count(1) as no_of_bookings
from cust_bookings
group by room_pk) c1
where b1.room_pk = c1.room_pk
having c1.no_of_bookings < 3
Sorry i used my own table names to test it but you should figure it out easily enough. Also, the "having" line is only there to limit the rows returned to rooms with less than 3 bookings. If you remove that line you will get everything and could use the same sql to update a column on the bookings table if you still want to go that route.
Consider below solutions.
A simple aggregate query to count the customers per each booking:
SELECT b.PK_room_number, Count(c.FK_customer_id)
FROM Booking b
INNER JOIN Customer_Booking c ON b.PK_room_number = c.FK_room_number
GROUP BY b.PK_room_number
HAVING Count(c.FK_customer_id) < 3; # ADD 3 ROOM MAX FILTER
And if you intend to use a new column no_of_booking, here is an update query (using aggregate subquery) to run right after inserting new value from web frontend:
UPDATE Booking b
INNER JOIN
(SELECT b.PK_room_number, Count(c.FK_customer_id) As customercount
FROM Booking b
INNER JOIN Customer_Booking c ON b.PK_room_number = c.FK_room_number
GROUP BY b.PK_room_number) As r
ON b.PK_room_number = r.PK_room_number
SET b.no_of_booking = r.customercount;
the following generates a list showing all of the bookings and a flag of 0 or 1 if the the room has a customer for each of the rooms. it will display some rooms multiple times if there are multiple customers.
select BOOKING.*,
case CUSTOMER_BOOKING.FK_ROOM_NUMBER is null THEN 0 ELSE 1 END AS BOOKING_FLAG
from BOOKING LEFT OUTER JOIN CUSTOMER_BOOKING
ON BOOKING.PK_room_numer = CUSTOMER_BOOKING.FK_room_number
summing and grouping we arrive at:
select BOOKING.*,
SUM(case when CUSTOMER_BOOKING.FK_ROOM_NUMBER is null THEN 0 ELSE 1 END) AS BOOKING_COUNT
from BOOKING LEFT OUTER JOIN CUSTOMER_BOOKING
ON BOOKING.PK_room_number = CUSTOMER_BOOKING.FK_room_number
GROUP BY BOOKING.PK_room_number
there are at least two other solutions I can think of off the top of my head...

MySQL Statement with multi nested JOINs and Distinct Limited Ordering

I'm attempting to build a list of results based on three joins
I have created a table of leads, as my sales team takes action on the leads they attach event note records to the leads. 1 lead can have many notes. each note has a timestamp and also a date/time field where they can set a future date in order to schedule call backs and appointments.
I have no trouble building the list, with all my leads associated with their respective event notes, but what I want to do in this particular case is query a smaller list of leads that are associated with only the event note containing the "newest"/highest value in the date_time column.
I've been digging about especially here on stack for the last couple days attempting to get the desired result from my statements. I get either all of the lead records with all of their associated event note records or I get 1, no matter what I utilize ( GROUP BY date_time ASC LIMIT 1) or (ORDER BY date_time ASC LIMIT 1) I've even tried to build a view with only the highest scheduled record for each lead.id.
SELECT
rr_leads.id AS 'Lead',
rr_leads.first,
rr_leads.last,
rr_leads.company,
rr_leads.phone,
rr_leads.email,
rr_leads.city,
rr_leads.zip,
rr_leads.status,
z.noteid,
z.taskid,
z.scheduled,
z.event
FROM rr_leads
LEFT JOIN
(
SELECT
rr_lead_notes.lead_id,
rr_lead_notes.id AS 'noteid',
rr_lead_tasks.id AS 'taskid',
rr_lead_notes.date_time AS 'scheduled',
rr_lead_notes.task_note,
rr_lead_tasks.task_step AS 'event'
FROM rr_lead_notes
LEFT JOIN rr_lead_tasks
ON rr_lead_notes.task_note = rr_lead_tasks.task_step
AND rr_lead_notes.id IS NOT NULL
AND rr_lead_notes.task_note IS NOT NULL
GROUP BY rr_lead_notes.id DESC
) z
ON rr_leads.id = z.lead_id
WHERE rr_leads.id IS NOT NULL
AND z.noteid IS NOT NULL
ORDER BY rr_leads.id DESC
Here is the general idea of getting data associated with a most recent event. You can adjust for your particular situation.
select yourfields
from table1 join othertables etc
join
(select id, max(time_stamp) maxts
from table1
where whatever
group by id) temp on table1.id = temp.id
and table1.time_stamp = maxts
where whatever
Make sure the where clauses in your main query and subquery are the same.

Updating A Table Based On The Count() From Its Many-to-Many Table

I have a table of groups, of people, and a many-to-many table of group_id, person_id pairs. group has a count column which should store the current number of people in each group. I'd like to update this information in one SQL command if I can. I imagine some kind of subquery would let me accomplish this, but I'm not sure how.
This query successfully gives me a mapping of group_ids to their count
SELECT `group_id`, COUNT(`group_id`) FROM `group-person` GROUP BY `group_id`;
This query fails, but if it worked, this would be what I'm trying to do
UPDATE `group`,`group-person` WHERE `group`.`id` = `group-person`.`group_id`
SET `group`.`count` = COUNT(`group-person`.`group_id`)
GROUP BY `group-person`.`group_id`;
UPDATE `group`
SET `group`.`count` = (
SELECT COUNT(*)
FROM `group_person`
WHERE `group_person`.`group_id` = `group`.`id`
)