mysql get state on particular date - mysql

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.

Related

Which bikes were live on December 8?

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;

counting occurrences between dates of different date intervals

I have a query that give me a table like this:
Person | Date_IN | Date_OUT | Structure
During a year a person ENTER and EXIT many times, ENTER and EXIT could be also the same day.
I'd like to count, for a specific day of year, how many person were IN each structure.
The final goal is to have, for a given period (1st march --> 31st march), the sum of total person for each day for each structure.
I believe the following would work. It assumes that you have a table of dates (consists of one column which contains all the dates between 1950 and 2050) and you simply join it with the person check in/out table:
SELECT dates.date, Structure, COUNT(DISTINCT Person) Persons_on_That_Date
FROM dates
LEFT JOIN turndata ON dates.date BETWEEN Date_IN AND Date_OUT
WHERE dates.date BETWEEN '2018-03-01' AND '2018-03-31'
GROUP BY dates.date, Structure
ORDER BY Structure, dates.date
Demo Here
Note: the above assumes that the out date is inclusive (the person is counted as inside on that date). If out date is exclusive then the ON clause becomes:
... ON Date_IN <= dates.date AND dates.date < Date_OUT
Please use below query, data is grouped by structure for particular timeframe.
SELECT structure, COUNT(DISTINCT person) as no_of_person
FROM table_name
WHERE DATE(Date_IN) BETWEEN '2018-08-01' AND '2018-08-31'
GROUP BY structure
You say there can be no multiple date_in for the same day and person, because a person is in at least one day. So for a given date we only must look at the latest event per person until then to see whether the person is/was in that day.
These are the steps:
create a data set for the requiered days on-the-fly
join with the table and get the last date_in until that day per person
join with the table again to get the last records
aggregate per day and count persons present
This is:
select
data.day
sum(t.date_in is not null and (t.date_out is null or t.date_out = data.day)) as count_in
from
(
select days.day, t.person, max(t.date_in) as max_date_in
from (select date '2018-03-01' as day union all ...) days
left join t on t.date_in <= days.day
group by days.day, t.person
) data
left join t on t.person = data.person and t.date_in = data.max_date_in
group by data.day
order by data.day;

MySQL groupwise query

I'm attempting to calculate the CURRENT location of a person based on a schedule items table (schedules).
The basic premise is, you can schedule a person to be in an office for a certain period of time (let's say start_date=2015-10-01, end_date=2015-12-31). That is a schedule item. It has a 1toM relationship with a location - that's no problem, I have that part sorted.
However, whilst they're scheduled to be in that office, they may also be scheduled to attend an offsite/client office. So there will be another schedule entry for, say, start_date=2015-12-03, end_date=2015-12=04.
Here's the table structure.
Person table
----------------------------------------------
|person_id |person_name |person_email |
----------------------------------------------
|1 |John |john#example.org |
|2 |Jane |jane#example.org |
----------------------------------------------
Schedule table
--------------------------------------------------------------
|schedule_id |person_id |location_id |start_date |end_date |
--------------------------------------------------------------
|1 |1 |1 |2015-10-01 |2015-12-31 |
|2 |2 |2 |2015-10-15 |2016-01-15 |
|3 |1 |5 |2015-12-03 |2015-12-10 |
|4 |2 |7 |2015-12-04 |2015-12-12 |
--------------------------------------------------------------
When I'm querying a single record, I'm easily able to calculate where the person currently is. It's not so complex.
SELECT * FROM schedules
WHERE person_id = 1 AND start_date <= CURDATE() AND end_date >= CURDATE
ORDER BY end_date ASC, start_date DESC
LIMIT 0,1
However, when I need to generate a list of all people with their current schedule item, I'm running into issues. I had initially thought of just using a GROUP BY statement in the query, but that will only ever return the earliest schedule item that matches the query.
The problem therein, is that there are MULTIPLE schedule items that will match the query (this is part of the domain logic). However, I will always select the SHORTEST current stint as their CURRENT location.
I've used a groupwise query in the past to calculate the status of a person's employment based on the most recent status entry. However, because the schedule item has some slightly more complex logic in and around it (it has future scheduled items in it) I'm really just talking myself in circles as to the best approach.
A method using a sub query with substring_index. This gets all the schedule ids ordered by the length of time between the end and start dates, then uses SUBSTRING_INDEX to just get the first one. Then joins this against schedules to get the rest of the details.
SELECT *
FROM schedules
INNER JOIN
(
SELECT person_id, SUBSTRING_INDEX(GROUP_CONCAT(schedule_id ORDER BY DATEDIFF(end_date, start_date)), ',', 1) AS best_schedule_id
FROM schedules
WHERE person_id = 1
AND start_date <= CURDATE() AND end_date >= CURDATE
GROUP BY person_id
) sub0
ON schedules.schedule_id = sub0.best_schedule_id
AND schedules.person_id = sub0.person_id
Note, I have also returned the person id from the sub query. Not strictly necessary as the query is at the moment, but put it in place so if you start to want to bring back multiple people it will need little change.
You want to select all records with a starting date before and an end date after the current date. You can get one person multiple times. From that person you want to select the occurrence with the earliest end date. That means you have to order those record by end date and number them within the person group. Try this:
select * from (
select a.scheduled_id
, a.person_id
, a.location_id
, a.start_date
, a.end_date
, row_number() over (partition by a.person_id order by a.end_date) as rn
from schedules a
where getdate() between a.start_date and a.end_date
) tab
where rn=1
I added this afterwards because I realized that the row_number function is not available in MySQL. So this is the MySQL version. A bit more complicated but it should work:
select * from (
select #row_num := if(#prev_value=a.person_id,#row_num+1,1) as rn
, a.scheduled_id
, a.person_id
, a.location_id
, a.start_date
, a.end_date
, #prev_value := a.person_id as asgmnt
from schedules a,
(select #row_num:=1) x,
(select #prev_value:=0) y
where a.start_date<=curdate() and a.end_date>=curdate()
order by a.person_id, a.end_date
) tab
where rn=1
I took some motivation from what you gave me and simply decided to do another subquery on the result set, prior to doing a GROUP BY on the output.
SELECT s.schedule_id, s.person_id, s.location_id FROM (
SELECT * FROM schedules
WHERE person_id = 1 AND start_date <= CURDATE() AND end_date >= CURDATE
ORDER BY end_date ASC, start_date DESC
) AS s GROUP BY s.person_id
This appears to have given me the result set that I was after, unless anybody can think of a reason this would fail?

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

How to get data from mysql for given date range group by weekly,monthly

I have a table which has data in this format:
id name cnt1 created_on
'1','uac','116','2014-09-06 17:16:29'
'2','uac','116','2014-09-06 17:17:06'
'3','uac','90','2014-09-06 21:53:34'
'4','uac','100','2014-08-06 21:53:34'
'5','uac','1','2014-07-06 21:53:34'
'6','uac','2','2014-07-26 21:53:34'
'7','uac','3','2014-09-01 21:53:34'
'8','uac','4','2014-09-02 21:53:34'
'9','uac','5','2014-09-03 21:53:34'
'10','uac','6','2014-09-04 21:53:34'
'11','uac','7','2014-09-05 21:53:34'
'12','uac','8','2014-09-07 21:53:34'
'13','uac','9','2014-09-08 21:53:34'
I want data for given date range should be grouped on 1. Weekly 2. Monthly
Also I want that for a week or month I should get data for last dat of week or month. Eg
if I am getting data for monthly I should get following output:
'6','uac','1','2014-07-26 21:53:34'
'4','uac','100','2014-08-06 21:53:34'
'13','uac','116','2014-09-08 21:53:34'
I tried this query
SELECT id,name,cnt1,created_on
FROM qa_dashboard.project_qa_coverage_detail
GROUP BY year(created_on), month(created_on);
but this is giving me following output
'5','uac','1','2014-07-06 21:53:34'
'4','uac','100','2014-08-06 21:53:34'
'1','uac','116','2014-09-06 17:16:29'
Please help
You don't actually want a group by query. You want to get the last row for each set. Here is a method using not exists:
SELECT cd.*
FROM qa_dashboard.project_qa_coverage_detail cd
WHERE NOT EXISTS (SELECT 1
FROM qa_dashboard.project_qa_coverage_detail cd2
WHERE year(cd2.created_on) = year(cd.created_on) and
month(cd2.created_on) = month(cd.created_on) and
cd2.created_on > cd.created_on
) ;
This is saying, in essence: "Get me all rows from the table where there is no other row with the same year and month and a more recent created_on date." That is a fancy way of saying "Get me the last row for each month."
EDIT;
If you want the values from the first and last date of the month, then use a join method instead:
select cd.*, cdsum.minco as first_created_on
from qa_dashboard.project_qa_coverage_detail cd join
(select year(cd2.created_on) as yr, month(cd2.created_on) as mon,
min(cd2.created_on) as minco, max(cd2.created_on) as maxco
from qa_dashboard.project_qa_coverage_detail cd2
group by year(cd2.created_on), month(cd2.created_on)
) cdsum
on cd.created_on = cd2.maxco;
Pretty sure this will get you your expected output:
Last for month:
select t.*
from tbl t
join (select max(created_on) as last_for_month
from tbl
group by year(created_on), month(created_on)) v
on t.created_on = v.last_for_month
Except where you say you expect:
'6','uac','1','2014-07-26 21:53:34'
I think what you really want is:
'6','uac','2','2014-07-26 21:53:34'
(based on the sample data you provided)
Fiddle:
http://sqlfiddle.com/#!9/faaa3/4/0
Last for week:
select t.*
from tbl t
join (select max(created_on) as last_for_week
from tbl
group by year(created_on), week(created_on)) v
on t.created_on = v.last_for_week
Based on your comment, if you want the last value and the last id for the last of the month, but the value of cnt1 for the first of the month, use the following (change month() to week() if you want the same but for week):
select v.id, v2.first_created_on, v.cnt1
from (select t.id, t.created_on, t.cnt1
from tbl t
join (select max(created_on) as last_created_on
from tbl
group by year(created_on), month(created_on)) v
on t.created_on = v.last_created_on) v
join (select min(created_on) as first_created_on
from tbl
group by year(created_on), month(created_on)) v2
on year(v.created_on) = year(v2.first_created_on)
and month(v.created_on) = month(v2.first_created_on)
Fiddle:
http://sqlfiddle.com/#!9/faaa3/5/0
Output:
| ID | FIRST_CREATED_ON | CNT1 |
|----|----------------------------------|------|
| 4 | August, 06 2014 21:53:34+0000 | 100 |
| 6 | July, 06 2014 21:53:34+0000 | 2 |
| 13 | September, 01 2014 21:53:34+0000 | 9 |
That looks like the expected output, one row per year and month. You've specified created_on as an expression in the SELECT list, so you are getting the value from the created_on column from one row in the each group.
If you want just year and month in the output, you'd need to use a different expression in the SELECT list. For example,
DATE_FORMAT(created_on,'%Y-%m') AS yyyy_mm
You could use the same expression the GROUP BY clause, rather that two separate expressions.
As another option, you could use the YEAR(created_on), MONTH(created_on) in the SELECT list.
To get rows "grouped" by week, you could use WEEK(created_on) in place of MONTH(created_on).
To return the "last" row for each group, the normal pattern is to use an inline view and a JOIN operation. For example:
SELECT t.id
, t.project
, t.total_tc
, t.created_on
FROM qa_dashboard.project_qa_coverage_detail t
JOIN ( SELECT MAX(r.created_on)
FROM qa_dashboard.project_qa_coverage_detail r
GROUP BY DATE_FORMAT(r.created_on,'%Y-%m')
) s
ON s.created_on = t.created
Note that if there are two (or more) rows with the same created_on value, there's a potential for this query to return more than one row for the group.