I have a rather complex-seeming query that will form the basis for an online classroom scheduling tool. My challenge is to develop a method to identify which classes a user is signed up for in the st_schedule table, then deduce from the overall table of classes, st_classes, which other classes are available that don't conflict with the user's current classes.
For example, if a user has an entry in st_schedule assigning them to a class from 8:00am to 9:00am, they would be ineligible for any class whose time fell between 8:00am and 9:00am. A class that ran 7:15am - 8:15am would make the user ineligible. I store the start times and end times of classes in the database separately for comparison purposes. It's important that this be as flexible as possible, so the concept of "blocking" times and assigning times to blocks is not a possibility.
Here are excerpts from the tables:
table st_classes (holds class information)
id
start_time
end_time
table st_schedule (holds schedule information)
id
user_id
class_id
I certainly could do this in a series of loops server-side, but I have to think that there's a MySQL method that can do this type of operation in one fell swoop.
You want to join the two tables together to represent the user's classes, and then find unregistered classes where the start time and end time do not fall between the start and end time of the user's classes.
Something like this. Completely off the cuff and untested:
SELECT
*
FROM
st_schedule s
INNER JOIN st_classes c ON c.id = s.class_id
INNER JOIN st_classes all_classes
ON all_classes.start_time NOT BETWEEN c.start_time AND c.end_time
AND all_classes.end_time NOT BETWEEN c.start_time AND c.end_time
WHERE
s.user_id = 1
Edit: Try #2
I only have a moment to look at this. I think I reversed the second join clauses. The all_classes alias represents the full list of classes, where the "c" alias represents the classes that the student is signed up for.
SELECT DISTINCT
all_classes.*
FROM
st_schedule s
INNER JOIN st_classes c ON c.id = s.class_id
INNER JOIN st_classes all_classes
ON c.start_time NOT BETWEEN all_classes.start_time AND all_classes.end_time
AND c.end_time NOT BETWEEN all_classes.start_time AND all_classes.end_time
WHERE
s.user_id = 1
This is using table variables in mssql but the sql selects should translate over to mysql
First the sample data
DECLARE #st_classes TABLE
(
ID INT NOT NULL,
Title VARCHAR(40) NOT NULL,
StartTime DATETIME NOT NULL,
EndTime DATETIME NOT NULL
)
DECLARE #st_schedule TABLE
(
ID INT NOT NULL,
UserID INT NOT NULL,
ClassID INT NOT NULL
)
INSERT INTO #st_classes (ID, Title, StartTime, EndTime)
SELECT 1,'Class1','08:00:00','09:30:00' UNION
SELECT 2,'Class2','09:30:00','11:30:00' UNION
SELECT 3,'Class3','11:30:00','16:00:00' UNION
SELECT 4,'Class4','16:00:00','17:30:00' UNION
SELECT 5,'Class5','09:00:00','11:45:00' UNION
SELECT 6,'Class6','07:00:00','18:00:00'
INSERT INTO #st_schedule(ID, UserID, ClassID)
SELECT 1,1,1 UNION
SELECT 2,1,2 UNION
SELECT 3,2,6
Next a bit of sql to confirm the tables join OK (selecting scheduled courses for user with an ID of 1) - Returns class 1 and 2
SELECT *
FROM #st_schedule AS S INNER JOIN
#st_classes AS C ON S.ClassID = C.ID
WHERE S.UserID = 1
Now we need to select all the ID of the courses where they overlap time wise with the users scheduled ones (including the scheduled ones) - Returns 1,2,5,6
SELECT AC.ID
FROM #st_classes AS AC
INNER JOIN ( SELECT C.StartTime,
C.EndTime
FROM #st_schedule AS S
INNER JOIN #st_classes AS C ON S.ClassID = C.ID
WHERE S.UserID = 1
) AS UC ON ( AC.StartTime < DATEADD(ss, -1, UC.EndTime)
AND DATEADD(ss, -1, UC.EndTime) > UC.StartTime
)
GROUP BY AC.ID
Now we need to select all courses where the Course ID is not in our list of overlapping course IDs. - Returns course 3 and 4
SELECT *
FROM #st_classes
WHERE ID NOT IN (
SELECT AC.ID
FROM #st_classes AS AC
INNER JOIN ( SELECT C.StartTime,
C.EndTime
FROM #st_schedule AS S
INNER JOIN #st_classes AS C ON S.ClassID = C.ID
WHERE S.UserID = 1
) AS UC ON ( AC.StartTime < DATEADD(ss, -1, UC.EndTime)
AND DATEADD(ss, -1, UC.EndTime) > UC.StartTime
)
GROUP BY AC.ID )
Change the user ID filter to 2 and you should not get any returned as the course assigned to that user overlaps all courses.
Related
I have three tables employee, promotion and punishment
Employee’s table structure something like this
Id int
Fullname varchar
...............
promotionDate date
Promotion’s table structure is like this
id int
emp_id int
directorateDate date
And punishment’s table structure is like this
id int
emp_id int
direcotorateDate date
Let’s say employee table has 200 records, each month a group of employees have promotion (after serving one year), I want to get the list of all employees in the current month that get promotion
I can easily get the list by this query
SELECT *
FROM employee
WHERE MONTH(promotionDate) = MONTH(CURRENT_DATE())
AND YEAR(promotionDate) = YEAR(CURRENT_DATE())
My question is
I want to count number of punishments and promotions each employee got in the current year from punishment and promotion table respectively
I did this query but it did not get right results
SELECT e.fullname , COUNT(punish.emp_id) as siza ,COUNT(pro.emp_id) as supas
FROM emp_employee as e
LEFT JOIN emp_punishment as punish on punish.emp_id=e.id
LEFT JOIN emp_promotion as pro on e.id=pro.emp_id
WHERE ((MONTH(e.promotionDate) = MONTH(CURRENT_DATE())
AND YEAR(e.promotionDate) = YEAR(CURRENT_DATE()))
AND ( YEAR(punish.directorate_date) = YEAR(CURRENT_DATE()) )
AND ( YEAR(pro.directorate_date) = YEAR(CURRENT_DATE()) )
GROUP BY e.fullname;
Any help please.
By joining directly the 3 tables you get duplicate rows.
Group by emp_id and aggregate separately each of the tables emp_punishment and emp_promotion and join the results to the table emp_employee.
select e.fullname, coalesce(pu.siza, 0) siza, coalesce(pr.supas, 0) supas
from emp_employee as e
left join (
select emp_id, count(*) siza
from emp_punishment
where year(directorate_date) = year(CURRENT_DATE)
group by emp_id
) pu on pu.emp_id = e.id
left join (
select emp_id, count(*) supas
from emp_promotion
where year(directorate_date) = year(CURRENT_DATE)
group by emp_id
) pr on pr.emp_id = e.id
I used only the condition:
where year(directorate_date) = year(CURRENT_DATE())
because in your question you say:
I want to count number of punishments and promotions each employee got in the current year from punishment and promotion
Removing MONTH() function, and moving each condition to their respective place, instead of within the WHERE clause should resolve the issue (Since, they're considered as if INNER JOINs with the current style ).
Only keep common column e.promotionDate within the WHERE clause :
SELECT e.fullname,
COUNT(punish.emp_id) as siza ,
COUNT(pro.emp_id) as supas
FROM emp_employee as e
LEFT JOIN emp_punishment as punish
ON punish.emp_id=e.id
AND YEAR(punish.directorate_date) = YEAR(CURRENT_DATE())
LEFT JOIN emp_promotion as pro
ON e.id=pro.emp_id
AND YEAR(pro.directorate_date) = YEAR(CURRENT_DATE()))
WHERE YEAR(e.promotionDate) = YEAR(CURRENT_DATE())
GROUP BY e.fullname;
I need help to formulate a query to calculate points based on user action.
There is a user table which stores user info and each user belongs to a specific category (User category). Each category has different levels (say level1, level2 etc..) and each level has a set of tasks(Task1,Task2...) to be completed. Each task can be performed multiple times. But points is calculated based on a maximum limit field.
Task 1 - 10 points ( Max:2 - Means user can perform this task N times but for point calculation it will be only counted twice )
Task 2 - 5 points ( Max:1 )
For example lets say User1 performs Task1 5 times, but for point calculation Task1 will only be counted 2 times , so total points is 20. This is similar for all tasks.
On completing each task the user gains N points and get upgraded to next level.
Please find below the table structure and query:
users
uid , uname , uc_id ( References User Category ) , ul_id (References user level and indicates current level of user )
user_category
uc_id ....
user_level
ul_id, uc_id (References User Category)
...
level_tasks
lt_id , ul_id (References User level) , lt_point, lt_max
user_tasks
ut_id, lt_id (References level task), uid (References user)
The below is the query i have tried so far :
SELECT uid, SUM(LT.lt_point/LT.lt_max) as TotalLevelPoints
FROM users AS U
INNER JOIN user_tasks AS UT ON UT.ut_user_id = U.id
INNER JOIN level_tasks AS LT ON LT.lt_id = UT.lt_id
INNER JOIN user_level AS UL ON UL.ul_id = LT.ul_id
INNER JOIN user_category AS UC ON UC.uc_id = UL.uc_id
WHERE UT.ut_status = 1 AND U.uid = 1 AND UL.ul_id = 1
GROUP BY U.uid
This gives all the sum without considering the max limit, how should i write so that sum is calculated on that column also.
Hope my question is clear enough.. Any help would be really appreciated.
I believe you will need to do this in 2 steps. First sum the points and compare to the maximums allowed for each task type. Once that is determined, sum those results for each user. e.g.
SELECT
u.uid
, SUM(user_task_points) AS user_task_points
FROM users AS u
INNER JOIN (
SELECT
ut.UT_user_id
, lt_id
, lt.LT_point
, lt.LT_max_times
, CASE WHEN SUM(lt.LT_point) > (lt.LT_point * lt.LT_max_times) THEN (lt.LT_point * lt.LT_max_times)
ELSE SUM(lt.LT_point)
END AS user_task_points
FROM user_tasks AS ut
INNER JOIN level_tasks AS lt ON ut.UT_rule_id = lt.LT_id
INNER JOIN user_level AS UL ON UL.ul_id = LT.ul_id
WHERE ut.UT_status = 1
AND ut.UT_user_id = 1
AND ul.ul_id = 1
GROUP BY
ut.UT_user_id
, lt_id
, lt.LT_point
, lt.LT_max_times
) AS p ON u.id = p.UT_user_id
GROUP BY
u.uid
;
There is some discrepancy between column names in schema and query you have provided. I have gone with schema.
Query is not tested. But this approach should work.
SELECT uid, SUM(LevelPoints) TotalLevelPoints
FROM ( SELECT U.uid, LT.lt_id, SUM((CASE WHEN COUNT(*) > lt_max THEN lt_max ELSE COUNT(*) END)*lt_point) LevelPoints
FROM users U
INNER JOIN user_tasks UT
ON U.uid = UT.uid
INNER JOIN level_tasks LT
ON UT.lt_id = LT.lt_id
GROUP BY U.uid, LT.lt_id
)
GROUP BY uid
Table A [PATIENT] has columns [PATID], [FIRSTVISITDATE]
Table B [APPT] has columns [APPTID], [PATID], [CREATEDATE]
Table C [NOTE] has columns [NOTEID], [NOTETEXT]
Table D [PROCS] has column [PROCID], [PATID]
Table E [CHARGE] has columns [CHARGEID], [AMOUNT]
I need to sum CHARGE(AMOUNT) by PATID for all PATIENTS where the NOTE.NOTETEXT contains 'text' and one of the APPT for a PATIENT containing the 'text' has an APPT.CREATEDATE = to the PATIENT.FIRSTVISITDATE
Simply put I need to SUM the charges for PATIENTS if they have an appointment with 'text' in their notes and the appointment with that 'text' was their first visit to the office
Other key points:
CHARGE.CHARGEID = PROC.PROCID
NOTE.NOTEID = APPT.APPTID
With my limited knowledge of SQL I was able to sum for all patients regardless if the 'text' was included in their first appointments notes and for that I used:
select (SUM(AMOUNT)) as 'Cash Payments' from CHARGE where CHARGEID in
(select PROCID from PROC where PATID in
(select PATID from APPT where APPTID in
(select NOTEID from NOTE where NOTETEXT like '%text%')))
You can use the GROUP BY clause to group the AMOUNT by patient. You can filter your patients to just the ones with the text in the notes and FIRSTVISITDATE = CREATEDATE using an inner query that joins the tables on those conditions.
I have not tested the following query, but it should do what you're asking.
SELECT pa.PATIENT, SUM(c.AMOUNT) AS 'Cash Payments'
FROM PATIENT pa
INNER JOIN PROCS pr
ON pa.PATID = pr.PATID
INNER JOIN CHARGE c
ON pr.PROCID = c.CHARGEID
WHERE pa.PATIENT IN (
SELECT pa.PATIENT
FROM PATIENT pa
INNER JOIN APPT a
ON pa.PATID = a.PATID
AND pa.FIRSTVISITDATE = a.CREATEDATE
INNER JOIN NOTE n
ON a.APPTID = n.NOTEID
WHERE n.NOTETEXT LIKE '%text%'
)
GROUP BY pa.PATIENT;
MySQL Table Diagram:
My query this far:
SELECT tblcourses.CourseStandard,
tblcourses.CourseID,
tblcourses.CourseRef,
tblcourses.CourseStandard,
tblcourses.CourseName,
tblcourses.CourseDuration,
tblcourses.NQFLevel,
tblcourses.CoursePrice,
tblcoursestartdates.StartDate
FROM etcgroup.tblcoursestartdates tblcoursestartdates
INNER JOIN etcgroup.tblcourses tblcourses
ON (tblcoursestartdates.CourseID = tblcourses.CourseID)
WHERE tblcoursestartdates.StartDate >= Now()
If you look at the diagram you will see I have a 3rd table. The query above works fine. It display all the data as it should.
I want to show all the courses and their respective dates excluding those that the student is already booked for. Keep in mind that there can be 20 start dates for 1 course. This is why I am only choosing dates >= Now().
I want to make sure that a student does not get double booked. Yes I can do it afterwards. Beep student already booked BUT if I can have it now show the course dates that the student already booked then great. Any suggestions?
This is pretty straightforward. Presumably you know the StudentID you'd like to see. Do a left join to the bookings table and select the mismatches.
SELECT tblcourses.CourseStandard,
tblcourses.CourseID,
tblcourses.CourseRef,
tblcourses.CourseStandard,
tblcourses.CourseName,
tblcourses.CourseDuration,
tblcourses.NQFLevel,
tblcourses.CoursePrice,
tblcoursestartdates.StartDate
FROM etcgroup.tblcoursestartdates tblcoursestartdates
INNER JOIN etcgroup.tblcourses tblcourses
ON tblcoursestartdates.CourseID = tblcourses.CourseID
AND tblcoursestartdates.StartDate >= Now()
LEFT JOIN tblbookings
ON tblbookings.CourseId = tblcourses.CourseId
AND tblbookings.StudentId = <<<the student ID in question >>>
WHERE tblbookings.BookingID IS NULL
The trick here is the LEFT JOIN ... IS NULL pattern. It eliminates the rows where the ON condition of the LEFT JOIN hit, leaving only the ones where it missed.
Do a left join to tblBookings on courseID where the bookingID is null (there are no matches). You'll have to provide the studentID as a parameter to the query.
SELECT DISTINCT c.CourseStandard,
c.CourseID,
c.CourseRef,
c.CourseStandard,
c.CourseName,
c.CourseDuration,
c.NQFLevel,
c.CoursePrice,
d.StartDate
FROM etcgroup.tblcoursestartdates d
INNER JOIN etcgroup.tblcourses c ON d.CourseID = c.CourseID
LEFT JOIN etcgroup.tblBookings b on c.CourseID = b.CourseID and b.StudentID = #StudentID
WHERE d.StartDate >= Now() and b.bookingID is null
Use NOT EXISTS or NOT IN to find the courses a student has already booked:
SELECT
c.CourseStandard,
c.CourseID,
c.CourseRef,
c.CourseStandard,
c.CourseName,
c.CourseDuration,
c.NQFLevel,
c.CoursePrice,
csd.StartDate
FROM etcgroup.tblcourses c
INNER JOIN etcgroup.tblcoursestartdates csd ON csd.CourseID = tblcourses.CourseID
WHERE csd.StartDate >= Now()
AND c.CourseID NOT IN
(
SELECT CourseID
FROM tblbookings
WHERE StudentID = 12345
);
Have tables: person,person_ip
Both tables have pid column as a primary key, in table person there is column state_id, in table person_ip there is column ip.
Want to discover if specified IP address is assigned to person with state_id is not equal to 2. But always got result 1, even if state_id is 0, 1 or 2. Always got 0 only if ip address is not listed at all. What am I doing wrong?
SELECT EXISTS (
SELECT person_ip.PID
FROM person_ip,person
WHERE person.PID=person_ip.PID
AND person.state_id NOT IN (2)
AND person_ip.ip='10.11.12.13'
)
this seems like a simple join.. unless i'm missing something
select person.*
from person
inner join person_ip
on person.pid = person_ip.pid
where person.state_id <> 2
and person_ip.ip_address = '10.0.0.1'
If you want to exclude the ip_address if it has been assigned to any user with state = 2, even if it has also been assigned to a user without state = 2, then try:
select max(i)
from (
select *
from (
select 1 as i
from dual
where not exists (
select 1
from person p
inner join person_ip pi
on p.pid = pi.pid
where p.state_id = 2
and pi.ip_address = '10.0.0.1'
)
) q
union
select 0
) qq
(dual is a system table that can be used as a sort of stub table)
here's a fiddle showing both versions
update after some actual sleep
Okay, so the above query is a little.. out there. Back in the real world, this one is probably more appropriate:
select count(case when p1.state_id = 2 then 1 end)
from person p1
inner join person_ip pi1
on p1.pid = pi1.pid
where pi1.ip_address = '10.0.0.1'
group by pi1.ip_address;
This will return 1 or more if your ip_address has been used by someone with a state_id of 2, and 0 if it has never been used by someone with a state_id of 2.
It will return nothing if the ip has never been used.
this fiddle has all three of the above queries.
SELECT IF(COUNT(*)>0,1,0)
FROM person
INNER JOIN person_ip
ON person.pid = person_ip.pid
AND person_ip.ip_address = '10.0.0.1'
WHERE person.state_id <> 2