Which bikes were live on December 8? - mysql

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;

Related

Join to table according to date

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

SQL Query to get the last set of distinct records with some conditions

I have the following table:
I'm trying to find a way to get the records for those customers that have expired, and then update the table accordingly (by update I mean add an a new record with entry 'SERVICE EXPIRED' with the customer_id of the relevant customer).
If you look at the bottom of the table, you will notice two records with the entry 'SERVICE EXPIRED' for already existing customers (customer_id 11 and 16).
I'm looking for a SQL Query that will:
Get the last set of distinct records by customer_id
Exclude records for the same customer_id from the resulting resultset that have the entry 'SERVICE EXPIRED' or status_id of 2 appearing later on in the table
If I use the following:
SELECT MAX(id) FROM mytable WHERE status_id != '2' AND expiry < '2012-12-26 19:00:00' GROUP BY customer_id
It will return ids 1, 11, 13, and 16. However, I don't want ids 11 and 16 because the expiry status has already been noted later on in the table (see the last two records of the table), and id 1 has been renewed as can be seen with an updated expiry date in id 3 later. All I want is id 13 because that is the only expired record that does not have a 'SERVICE EXPIRED' entry that appears later in the table.
I'm looking for a SQL Query that will enable me capture this requirement.
Thanks in advance
After some fiddling around I managed to come up with a solution:
SELECT MAX(id)
FROM mytable
WHERE status_id != '2'
AND expiry < '2012-12-26 19:00:00'
AND customer_id NOT IN (SELECT MAX(customer_id) FROM mytable WHERE status_id = '2' GROUP BY customer_id)
GROUP BY customer_id
Thanks #JupiterP5 for pointing me in the right direction.
Regards,
Your requirement is equivalent to finding "n" records after the last expiry on a record. The following query returns all records after the last expiry for a given customer:
select t.*
from t join
(select t.customer_id, MAX(id) as maxid
from t
where status_id = 2
) texp
on t.customer_id = texp.customer_id and
t.id > texp.maxid
By using variables cleverly, you can enumerate these to get the last "n". However, do you really need a fixed number? Why not all of them? Why not just one of them?
It's not efficient, but this should work.
SELECT MAX(id)
FROM mytable
WHERE status_id != '2'
AND expiry < '2012-12-26 19:00:00'
AND id NOT IN (SELECT id FROM mytable where status_id = 2)
GROUP BY customer_id
Edit: Missed the service renewed case. I'll update if I think of something.

SQL Query for columns with a unique value

I have a table which looks like this
courseid session_date title published
1 2012-07-01 Training Course A 0
1 2012-07-02 Training Course A 0
2 2012-07-04 Training Course B 1
2 2012-07-07 Training Course B 1
3 2012-07-05 Training Course C 1
3 2012-07-06 Training Course C 1
4 2012-07-07 Training Course D 1
4 2012-07-10 Training Course D 1
The table has two entries for each ID and Title because the session_date column shows the start date and the end date of the course.
I am trying to create a query that will pull the next five courses without showing any courses in the past.
I have gotten this far
SELECT session_date, title, courseid
FROM table
WHERE published = 1 AND session_date > DATE(NOW())
ORDER BY session_date ASC LIMIT 0,5
This pulls rows from the table for the next five session-dates but it includes both start dates and finish dates whereas I need the next five courses ordered by start date.
I need to create a query that will pull the earliest session_date for each courseid but ignore the row with the latest session_date for that same courseid but I am at a complete loss of how to do this.
Any help or advice would be most gratefully received.
If you group your results by course and select the MAX(session_date), you will get the latest of the dates associated with each course (i.e. the finish date):
SELECT courseid, MIN(session_date) AS start_date
FROM `table`
WHERE published = 1
GROUP BY courseid
HAVING start_date > CURRENT_DATE
ORDER BY start_date ASC
LIMIT 5
See it on sqlfiddle.
What you need to do is retrieve only the rows with the minimum session_date per courseid group and order by that resulting set:
SELECT
b.*
FROM
(
SELECT courseid, MIN(session_date) AS mindate
FROM tbl
GROUP BY courseid
) a
INNER JOIN
tbl b ON a.courseid = b.courseid AND a.mindate = b.session_date
WHERE
b.session_date > NOW() AND
b.published = 1
ORDER BY
b.session_date
LIMIT 5
But a much better design would be to only have one row per courseid and have two columns specifying start and end dates:
tbl
------------------
courseid [PK]
start_date
end_date
title
published
Then you can simply do:
SELECT *
FROM tbl
WHERE start_date > NOW() AND published = 1
ORDER BY start_date
LIMIT 5
Since values of all the columns in your SELECT clause are repeating, just use DISTINCT
SELECT distinct session_date, title, courseid
FROM table
WHERE published = 1 AND session_date > DATE(NOW())
ORDER BY session_date ASC LIMIT 0,5

mysql get state on particular date

How could I get row from table with dates nearest to some date? If if insert log on February 27th, state will remain the same for some time and no other record will be added. How can I tell which state was on March 8th for instance?
I have two tables: State and History
State: History:
id|name id| date |id_state
1 |works 1 |2010-08-06 |1
2 |broken 2 |2011-05-10 |1
3 |active 3 |2009-27-01 |2
If I draw timeline of records when when were put in database...
2009-08-06 2010-08-06
---|--------------------------------------------|---------------->
'active' 'broken'
So it was active this entire time. I need all rows from History when state was active and date was March 8th 2010. Thank you
Simple query. Considering your mentioned date of 8th March 2010.
select h.id, h.date,h.id_state from history h
left outer join State s on s.id = h.id_state
where
h.date = '2010-03-08' and s.id = 3
You can rephrase the where clause as below according to your need.
where h.date = '2010-03-08' and s.name = 'active'
This may work
SELECT state.id, history.id, name, date
FROM state
JOIN history ON state.id = history.id_state
WHERE date = '2010-08-06'
Simple joining of 2 tables...
Edit:
To retrieve the last closest date to the given date, use this...
SELECT state.id, history.id, name, date
FROM state
JOIN history ON state.id = history.id_state
WHERE date <= '2012-04-10'
ORDER by date DESC
LIMIT 1
You get exactly ONE, but the closest date...
Edit2:
To retrieve the last closest date to the given date, that IS ACTIVE...
SELECT state.id, history.id, name, date
FROM state
JOIN history ON state.id = history.id_state
WHERE date <= '2012-04-10' AND name = 'active'
ORDER by date DESC
LIMIT 1
You get exactly ONE, but the closest date...
To get the last state before a given date (which will give you the state at the given date), use this query:
select * from (
select *
from log_table
where `date` < $1
and name = 'active'
order by `date` desc) x
limit 1
You can add to the where clause as you like to find the most recent row with some particular condition.

boolean if record is first 10 post of the month

i have a very tough mysql statement question.
i have a table with the following structure.
post_id, date_time, category_id
i want to sort this table by date_time and also to have a true or false Boolean that indicates if this record is the first 10 record of the month of each category.
for example first 10 record of Oct 2011 for category a will be true, first 10 record of the Oct 2011 for category b is also true, first 10 record of Nov 2011 for category c will be true, first 10 record of the Nov 2011 for category b is also true.
Please guide me along. thanks!
You just apply query for select date_time from tablename oder by date_time desc
OR
You can do like get all the data_time field column data in array.
and apply substring logic to get the only date number.
After you can sort that according to value
and append again remaing part ..
like
10-11-2011 15:30:46
take String date=substring(0,2)
so you got 10
and String remainingDatepart=(3,length)
after you just sort according to date.
and after you can append reminingDatapart string in sorted date..
This is logic you can apply them.
Something like this should od the trick, I've only tested it in my head.
SELECT
s.*
, CASE WHEN s.rank < 10
THEN TRUE
ELSE FALSE
END AS top10
FROM (
SELECT
t1.post_id, t1.date_time, t1.category_id
,CONCAT(year(t1.date_time), month(t1.date_time)) as yearmonth
,#prevyearmonth:= CONCAT(year(t1.date_time), month(t1.date_time)) as yearmonth
,CASE WHEN (#prevyearmonth = CONCAT(year(t1.date_time), month(t1.date_time)))
THEN #rank:= #rank + 1
ELSE #rank:= 1
END as rank
FROM (SELECT * FROM table1 ORDER BY date_time DESC) t1
CROSS JOIN (SELECT #rank:= 0, #prevyearmonth:= null)
ORDER BY date_time DESC
) s
/* HAVING top10 = TRUE */
ORDER BY s.date_time, s.top10