I have a SQL statement returning information from the database about available dates.
SELECT *
FROM venue
WHERE NAME NOT IN (SELECT NAME
FROM venue_booking
WHERE date_booked = '2016-01-17')
AND capacity > 150
The table has two columns, weekday price and weekend price.
I only need to show one of them in the final output, depending on the date.
E.G. if date is during the week, display the weekday price of that venue.
From the MySQL Reference here:
DAYOFWEEK(date)
Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 =
Saturday). These index values correspond to the ODBC standard.
You can use this function along with an IF or CASE to solve your problem.
SELECT `all_fields_other_than_price`,
CASE WHEN DAYOFWEEK('2016-01-17') IN (1,7) THEN v.weekend_price
ELSE v.weekday_price
END AS `VENUE_PRICE`
FROM venue v
WHERE name NOT IN(
SELECT name FROM venue_booking WHERE date_booked = '2016-01-17')
AND capacity > 150;
Do you mean something like this ?
SELECT *, IF(dayofweek(NOW()) = 7, v.weekday_price, v.weekend_price) as final_price FROM venue v
WHERE name NOT IN(SELECT name FROM venue_booking WHERE date_booked = '2016-01-17')
AND capacity > 150
NOTE
Change number 7 in IF(dayofweek(NOW()) = 7 to your desired day number.
Use this query:
Select CASE when DAYOFWEEK(booked_date)>1 and
DAYOFWEEK(booked_date)< 7 then weekday_price
ELSE weekend_price
END as price
From venue
WHERE NAME NOT IN (SELECT NAME FROM venue_booking WHERE date_booked = '2016-01-17') AND capacity > 150
Repeating the same date more than one time in a query can be dangerous (in terms of keeping the query consistent). In addition, I am not a fan of NOT IN, because it doesn't behave intuitively with NULL values.
So, you might consider:
SELECT v.*,
(CASE WHEN dayofweek(thedate) IN (1, 7) then weekend_price
ELSE weekday_price
END) as price
FROM (SELECT date('2016-01-17') as thedate) params CROSS JOIN
venue v LEFT JOIN
venue_booking vb
ON v.name = vb.name AND vb.date_booked = thedate
WHERE vb.name IS NULL AND
v.capacity > 150
Related
I am new to SQL (MySql) and I got stuck with this problem with one of my assignments and couldn't get answer from anyone or searching online about the subject. The question goes like:
Which bikes were live (is_live = TRUE) on December 8? (Display only bike_id).
Note: The Table does not have a row entry for December 8, and that is as intended
Name Type Description
id int LOG ID(PK)
bike_id int id of the bike
is_live boolean flag to indicate whether bike is live (TRUE/FALSE)
updated_on Date Date on which status of bike was updated
BIKE_LIVE_LOG table:
id bike_id is_live updated_on
1 1 TRUE 2018-12-01
2 2 TRUE 2018-12-01
3 3 TRUE 2018-12-01
4 3 FALSE 2018-12-02
5 2 FALSE 2018-12-05
6 3 TRUE 2018-12-10
I couldn't move forward with the question as I am not even getting the approach for it with my current knowledge.
I have used this query to generate the the last_update_date grouped by each bike_id.
select bll.bike_id, max(bll.updated_on) as last_update_date
from bike_live_log as bll
where bll.updated_on <= '2018-12-08'
group by bll.bike_id;
The output will be 1.
I'll try to help you get to the last step. You're really, really close!
You were correct in going down the road of finding the most recent updated_on date for each bike_id. It doesn't matter how many times a bike has been turned on or off; you really only care about the most recent status prior to the date you're interested in.
With your current query, you already know when each bike_id was last updated prior to December 8th.
Next, you can use that information to find out what the is_live value was for each of those bike_id values as of that last_update_date.
You can do that by using your existing query as a sub-query, or a CTE if you prefer, and join back to your main table again. Your JOIN criteria will be bike_id to bike_id and updated_on to last_update_date. By joining on the dates, you'll only return a single record for each bike_id, and that record will be the one you're interested in.
After you have your JOIN put together, you'll just need to add a WHERE clause to limit your result set to the rows where is_live = 'TRUE', which will return just bike_id of 1.
Your requirement can be expressed more data-centrically as find bikes whose last known status on or before Dec 8 was live.
This is one way (IMHO the most readable way) to express that in SQL:
select bike_id
from bike_live_log bll
where updated_on = (
select max(updated_on)
from bike_live_log
where bike_id = bll.bike_id
and updated_on <= '2018-12-08'
)
and is_live
The (corelated) subquery finds the date of the last update on or before Dec 8 for the current row of the outer query. If there's no such row, null will be returned, which won't match any rows from the outer query so, only bikes that have data on or before Dec 8 will be returned.
tl;dr
Here is the MySQL code you need:
SELECT bike_id FROM (
SELECT * FROM (
SELECT * FROM bikes
WHERE updated_on < '20181209'
ORDER BY updated_on DESC, id DESC
) AS sub
GROUP BY bike_id
) AS sub2
WHERE is_live = true
Why does this work?
We need to break down the question a little bit. I like to start from a position of "how can I get the information I need in a format that makes sense to me (as a human)?".
So the first thing I did was to get a list of all bikes with updated_on dates before the 9th Dec (i.e. were updated on the 8th Dec or before). I also ordered this by updated_on field so I (as a human) could easily see the "latest" record which will tell me the most recent status of each bike on or before the 8th Dec:
SELECT * FROM bikes
WHERE updated_on < '20181209'
ORDER BY updated_on DESC, id DESC
From this I can see there are 5 records of bike status changes before the 9th Dec. I can easily see that for each bike there are multiple "update" records, but I can now start from the top of the list and each bike id I encounter is the status on the 8th Dec.
Additionally I included an order_by for the record id. This is necessary because there could be multiple updates per day. The date itself won't tell us which of those updates was the latest ON THE SAME DAY, so we use the ID to determine that. (assuming it's chronologically incremental).
Now we have a list of all statuses and bike ids in the database before the 9th Dec we need to limit this to only one record per bike. This is easy, we can wrap the original query with a new query with a Group By directive on the bike_id.
SELECT * FROM (
SELECT * FROM bikes
WHERE updated_on < '20181209'
ORDER BY updated_on DESC, id DESC
) AS sub
GROUP BY bike_id
Group By in MySQL selects the first record it comes across for each group. We have already ordered the list by date and id in the original query, so the first record for each bike_id will be the latest status for that bike as of the 8th Dec.
Now, all that's left for us to do is to select the bike_id and filter out non-live bikes by using WHERE is_live = true on this latest query. That's how we end up with the query at the start of this answer:
SELECT bike_id FROM (
SELECT * FROM (
SELECT * FROM bikes
WHERE updated_on < '20181209'
ORDER BY updated_on DESC, id DESC
) AS sub
GROUP BY bike_id
) AS sub2
WHERE is_live = true
I hope this helps.
SELECT
bike_id,
MAX(CASE WHEN last_live_date <= '2018-12-08' AND ( last_not_live_date < last_live_date OR last_not_live_date IS NULL) THEN 1 ELSE 0 END) AS final_status
FROM
(
SELECT
bike_id,
MAX(CASE WHEN is_live = TRUE THEN updated_on END) AS last_live_date,
MAX(CASE WHEN is_live = FALSE THEN updated_on END) AS last_not_live_date
FROM `BIKE_LIVE_LOG`
WHERE updated_on <= '2018-12-08'
GROUP BY bike_id
) AS a
GROUP BY bike_id
HAVING final_status = 1;
with A as (select * from bike_live_log
where updated_on <= '2018-12-08'),
B as (select bike_id,max(updated_on) as updated from A
group by bike_id)
select A.bike_id from A inner join B
on A.updated_on = B.updated
where is_live = True;
I have two MySQL tables memberships and member_cards. Each membership & member card can have three states.
Active = start_date <= today <= end_date
Future = today < start_date
Expired = end_date < today
Memberships table
id--------membership_number--------start_date-------------end_date
1--------**123**--------------------------------09-20-2014-----------09-20-2015
2--------**123**--------------------------------09-20-2015-----------09-20-2016
3--------**123**--------------------------------09-20-2016-----------09-20-2017
4--------**123**--------------------------------09-20-2017-----------09-20-2018
5--------**456**--------------------------------09-20-2013-----------09-20-2014
6--------**456**--------------------------------09-20-2014-----------09-20-2015
Membership cards
id--------membership_id-------------start_date-------------end_date
1--------**1**--------------------------------09-20-2014-----------05-15-2015
2--------**1**--------------------------------09-20-2014-----------09-20-2015
3--------**2**--------------------------------09-20-2015-----------05-13-2016
4--------**2**--------------------------------09-20-2015-----------09-20-2016
5--------**3**--------------------------------09-20-2016-----------09-21-2016 (past)
6--------**3**--------------------------------09-20-2016-----------05-15-2017
7--------**3**--------------------------------09-20-2016-----------09-20-2017
8--------**4**--------------------------------09-20-2017-----------05-13-2017
9--------**4**--------------------------------09-20-2017-----------09-20-2018
10-------**5**--------------------------------09-20-2013-----------05-13-2014
11-------**5**--------------------------------09-20-2013-----------09-20-2014
12------**6**--------------------------------09-20-2014-----------05-13-2015
13-----**6**--------------------------------09-20-2014-----------09-20-2015
I want to retrieve
All the active + future memberships + (if there are no active or future memberships for a particular membership number, the last expired record)
The results:
id--------membership_number--------start_date-------------end_date
3--------**123**--------------------------------09-20-2016-----------09-20-2017
4--------**123**--------------------------------09-20-2017-----------09-20-2018
6--------**456**--------------------------------09-20-2014-----------09-20-2015
Active cards + (if the membership has expired, all the cards tied to that membership )
The results:
id--------membership_id-------------start_date-------------end_date
6--------**3**--------------------------------09-20-2016-----------05-15-2017
7--------**3**--------------------------------09-20-2016-----------09-20-2017
8--------**4**--------------------------------09-20-2017-----------05-13-2017
9--------**4**--------------------------------09-20-2017-----------09-20-2018
12------**6**--------------------------------09-20-2014-----------05-13-2015
13-----**6**--------------------------------09-20-2014-----------09-20-2015
Each table contains about 200k records. I am trying to do the second query (for the member_cards) using a single MySQL query using UNION. Are there any better approaches?
As has been said your question is a bit unclear, but I've written a query that returns your 2'nd target table based on the 1'st one. Inner query returns your 1'st target.
select
b.id#,
b.membership_id,
b.start_date,
b.end_date
from membership_cards b
full outer join (
select * from
(select a.*,
max(end_date) over (partition by membership_number) max_end_date,
case when
end_date>=sysdate --replace with today or whatever
then 1 --active
else 0 --inactive
end index_active
from memberships a)
where (end_date<=max_end_date and end_date>=sysdate) or
end_date = max_end_date) c
on c.id# = membership_id
where (c.index_active = 1 and b.end_date >= sysdate) or
c.index_active = 0
I have two tables, one is a list of firms, the other is a list of jobs the firms have advertised with deadlines for application and start dates.
Some of the firms will have advertised no jobs, some will only have jobs that are past their deadline dates, some will only have live jobs and others will have past and live applications.
What I want to be able to show as a result of a query is a list of all the firms, with the nearest deadline they have, sorted by that deadline. So the result might look something like this (if today was 2015-01-01).
Sorry, I misstated that. What I want to be able to do is find the next future deadline, and if there is no future deadline then show the last past deadline. So in the first table below the BillyCo deadline has passed, but the next BuffyCo deadline is shown. In the BillyCo case there are earlier deadlines, but in the BuffyCo case there are both earlier and later deadlines.
id name title date
== ==== ===== ====
1 BobCo null null
2 BillCo Designer 2014-12-01
3 BuffyCo Admin 2015-01-31
So, BobCo has no jobs listed at all, BillCo has a deadline that has passed and BuffyCo has a deadline in the future.
The problematic part is that BillCo may have a set of jobs like this:
id title date desired hit
== ===== ==== ===========
1 Coder 2013-12-01
2 Manager 2014-06-30
3 Designer 2012-12-01 <--
And BuffyCo might have:
id title date desired hit
== ===== ==== ===========
1 Magician 2013-10-01
2 Teaboy 2014-05-19
3 Admin 2015-01-31 <--
4 Writer 2015-02-28
So, I can do something like:
select * from (
select * from firms
left join jobs on firms.id = jobs.firmid
order by date desc)
as t1 group by firmid;
Or, limit the jobs joined or returned by a date criterion, but I don't seem to be able to get the records I want returned. ie the above query would return:
id name title date
== ==== ===== ====
1 BobCo null null
2 BillCo Designer 2014-12-01
3 BuffyCo Writer 2015-02-28
For BuffyCo it's returning the Writer job rather than the Admin job.
Is it impossible with an SQL query? Any advice appreciated, thanks in advance.
I think this may be what you need, you need:
1) calculate the delta for all of your jobs between the date and the current date finding the min delta for each firm.
2) join firms to jobs only on where firm id's match and where the calculated min delta for the firm matches the delta for the row in jobs.
SELECT f.id, f.name, j.title,j.date
FROM firms f LEFT JOIN
(SELECT firmid,MIN(abs(datediff(date, curdate())))) AS delta
FROM jobs
GROUP BY firmid) d
ON f.id = d.firmid
LEFT JOIN jobs j ON f.id = j.id AND d.delta = abs(datediff(j.date, curdate())))) ;
You want to make an outer join with something akin to the group-wise maximum of (next upcoming, last expired):
SELECT * FROM firms LEFT JOIN (
-- fetch the "groupwise" record
jobs NATURAL JOIN (
-- using the relevant date for each firm
SELECT firmid, MAX(closest_date) date
FROM (
-- next upcoming deadline
SELECT firmid, MIN(date) closest_date
FROM jobs
WHERE date >= CURRENT_DATE
GROUP BY firmid
UNION ALL
-- most recent expired deadline
SELECT firmid, MAX(date)
FROM jobs
WHERE date < CURRENT_DATE
GROUP BY firmid
) closest_dates
GROUP BY firmid
) selected_dates
) ON jobs.firmid = firms.id
This will actually give you all jobs that have the best deadline date for each firm. If you want to restrict the results to an indeterminate record from each such group, you can add GROUP BY firms.id to the very end.
The revision to your question makes it rather trickier, but it can still be done. Try this:
select
closest_job.*, firm.name
from
firms
left join (
select future_job.*
from
(
select firmid, min(date) as mindate
from jobs
where date >= curdate()
group by firmid
) future
inner join jobs future_job
on future_job.firmid = future.firmid and future_job.date = future.mindate
union all
select past_job.*
from
(
select firmid, max(date) as maxdate
from jobs
group by firmid
having max(date) < curdate()
) past
inner join jobs past_job
on past_job.firmid = past.firmid and past_job.date = past.maxdate
) closest_job
on firms.id = closest_job.firmid
I think this does what I need:
select * from (
select firms.name, t2.closest_date from firms
left join
(
select * from (
--get first date in the future
SELECT firmid, MIN(date) closest_date
FROM jobs
WHERE date >= CURRENT_DATE
GROUP BY firmid
UNION ALL
-- most recent expired deadline
SELECT firmid, MAX(date)
FROM jobs
WHERE date < CURRENT_DATE
GROUP BY firmid) as t1
-- order so latest date is first
order by closest_date desc) as t2
on firms.id = t2.firmid
-- group by eliminates all but latest date
group by firms.id) as t3
order by closest_date asc;
Thanks for all the help on this
Please consider the following two tables:
Holidays
HolidayID (PK)
Destination
Length
MaximumNumber
...
Bookings
BookingID (PK)
HolidayID (FK)
Name
...
Customers can book holidays (e.g. go to Hawaii). But, suppose that a given holiday has a maximum number of places. e.g. there are only 75 holidays to Hawaii this year (ignoring other years).
So if some customer wants to book a holiday to Hawaii. I need to count the records in Bookings table, and if that number is greater than 75 I have to tell the customer it's too late.
This I can do using 2 MySQL queries (1 to get MaximumNumber for the holiday, 2 to get the current total from Bookings) and PHP (for example) to compare the count value with the maximum number of Hawaii holidays.
But I want to know if there is a way to do this purely in SQL (MySQL in this case)? i.e. count the number of bookings for Hawaii and compare against Hawaii's MaximumNumber value.
EDIT:
My method:
$query1 = "SELECT MaximumNumber FROM Holidays WHERE HolidayID=$hawaiiID";
$query2 = "SELECT COUNT(BookingID) FROM Bookings WHERE HolidayID=$hawaiiID";
So if the first query gives 75 and the second query gives 75 I can compare these values in PHP. But I wondered if there was a way to do this somehow in SQL alone.
Maybe I am missing something, but why not use a subquery to determine the total bookings for each holidayid:
select *
from holidays h
left join
(
select count(*) TotalBooked, HolidayId
from bookings
group by holidayId
) b
on h.holidayId = b.holidayId
WHERE h.HolidayID=$hawaiiID;
See SQL Fiddle with Demo.
Then you could use a CASE expression to compare the TotalBooked to the MaxNumber similar to this:
select h.destination,
case
when b.totalbooked = h.maxNumber
then 'Not Available'
else 'You can still book' end Availability
from holidays h
left join
(
select count(*) TotalBooked, HolidayId
from bookings
group by holidayId
) b
on h.holidayId = b.holidayId
WHERE h.HolidayID=$hawaiiID;
See SQL Fiddle with Demo.
You will notice that I used a LEFT JOIN which will return all rows from the Holidays table even if there are not matching rows in the Bookings table.
Something like this will work. You can fill in the details:
select case
when
(select count(*)
from Bookings
where holidayID = $hawaiiid)
<= MaximumNumber then 'available' else 'sold out' end status
from holidays
etc
You might try something like this:
select case when b.freq < h.MaximumNumber
then 'Available'
else 'Not Available'
end as MyResult
from Holidays h
left join (
select HolidayID
, count(*) as freq
from Bookings
where HolidayID=$hawaiiID
group by HolidayID
) b
on h.HolidayID=b.HolidayID
I have a table that looks like this:
studentID | subjectID | attendanceStatus | classDate | classTime | lecturerID |
12345678 1234 1 2012-06-05 15:30:00
87654321
12345678 1234 0 2012-06-08 02:30:00
I want a query that reports if a student has been absent for 3 or more consecutive classes. based on studentID and a specific subject between 2 specific dates as well. Each class can have a different time. The schema for that table is:
PK(`studentID`, `classDate`, `classTime`, `subjectID, `lecturerID`)
Attendance Status: 1 = Present, 0 = Absent
Edit: Worded question so that it is more accurate and really describes what was my intention.
I wasn't able to create an SQL query for this. So instead, I tried a PHP solution:
Select all rows from table, ordered by student, subject and date
Create a running counter for absents, initialized to 0
Iterate over each record:
If student and/or subject is different from previous row
Reset the counter to 0 (present) or 1 (absent)
Else, that is when student and subject are same
Set the counter to 0 (present) or plus 1 (absent)
I then realized that this logic can easily be implemented using MySQL variables, so:
SET #studentID = 0;
SET #subjectID = 0;
SET #absentRun = 0;
SELECT *,
CASE
WHEN (#studentID = studentID) AND (#subjectID = subjectID) THEN #absentRun := IF(attendanceStatus = 1, 0, #absentRun + 1)
WHEN (#studentID := studentID) AND (#subjectID := subjectID) THEN #absentRun := IF(attendanceStatus = 1, 0, 1)
END AS absentRun
FROM table4
ORDER BY studentID, subjectID, classDate
You can probably nest this query inside another query that selects records where absentRun >= 3.
SQL Fiddle
This query works for intended result:
SELECT DISTINCT first_day.studentID
FROM student_visits first_day
LEFT JOIN student_visits second_day
ON first_day.studentID = second_day.studentID
AND DATE(second_day.classDate) - INTERVAL 1 DAY = date(first_day.classDate)
LEFT JOIN student_visits third_day
ON first_day.studentID = third_day.studentID
AND DATE(third_day.classDate) - INTERVAL 2 DAY = date(first_day.classDate)
WHERE first_day.attendanceStatus = 0 AND second_day.attendanceStatus = 0 AND third_day.attendanceStatus = 0
It's joining table 'student_visits' (let's name your original table so) to itself step by step on consecutive 3 dates for each student and finally checks the absence on these days. Distinct makes sure that result willn't contain duplicate results for more than 3 consecutive days of absence.
This query doesn't consider absence on specific subject - just consectuive absence for each student for 3 or more days. To consider subject simply add .subjectID in each ON clause:
ON first_day.subjectID = second_day.subjectID
P.S.: not sure that it's the fastest way (at least it's not the only).
Unfortunately, mysql does not support windows functions. This would be much easier with row_number() or better yet cumulative sums (as supported in Oracle).
I will describe the solution. Imagine that you have two additional columns in your table:
ClassSeqNum -- a sequence starting at 1 and incrementing by 1 for each class date.
AbsentSeqNum -- a sequence starting a 1 each time a student misses a class and then increments by 1 on each subsequent absence.
The key observation is that the difference between these two values is constant for consecutive absences. Because you are using mysql, you might consider adding these columns to the table. They are big challenging to add in the query, which is why this answer is so long.
Given the key observation, the answer to your question is provided by the following query:
select studentid, subjectid, absenceid, count(*) as cnt
from (select a.*, (ClassSeqNum - AbsentSeqNum) as absenceid
from Attendance a
) a
group by studentid, subjectid, absenceid
having count(*) > 2
(Okay, this gives every sequence of absences for a student for each subject, but I think you can figure out how to whittle this down just to a list of students.)
How do you assign the sequence numbers? In mysql, you need to do a self join. So, the following adds the ClassSeqNum:
select a.StudentId, a.SubjectId, count(*) as ClassSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= s1.classDate
group by a.StudentId, a.SubjectId
And the following adds the absence sequence number:
select a.StudentId, a.SubjectId, count(*) as AbsenceSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= a1.classDate
where AttendanceStatus = 0
group by a.StudentId, a.SubjectId
So the final query looks like:
with cs as (
select a.StudentId, a.SubjectId, count(*) as ClassSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= s1.classDate
group by a.StudentId, a.SubjectId
),
a as (
select a.StudentId, a.SubjectId, count(*) as AbsenceSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= s1.classDate
where AttendanceStatus = 0
group by a.StudentId, a.SubjectId
)
select studentid, subjectid, absenceid, count(*) as cnt
from (select cs.studentid, cs.subjectid,
(cs.ClassSeqNum - a.AbsentSeqNum) as absenceid
from cs join
a
on cs.studentid = a.studentid and cs.subjectid = as.subjectid
) a
group by studentid, subjectid, absenceid
having count(*) > 2