creating logical expression in mysql to extract data from a table - mysql

I have a data set that looks like this:
my mission is to Write a logical condition in MySQL based on the check-in and check-out dates to retrieve all stays that
include (the night of) March 23rd. I tried this :
check_in<="23/03/2019" and check_out>"23/03/2019"
but I found out that stayed with check-in = ‘2019-03-21’ and check-out =
‘2019-03-25’ wasn't retrieved even though it includes the 23r. I wonder what is the problem and how could I fix it? thank you :)

You need to join it with itself to be able to search on both terms. Assuming your table is called stays:
SELECT a.stay_id,a.date check_in,b.date check_out FROM stays a JOIN stays b ON a.stay_id=b.stay_id AND a.event_type='check-in' AND b.event_type='check-out' WHERE a.date<="2019-03-23" AND b.date>"2019-03-23";

Use a subquery that groups by stay_id and checks your requirement in the HAVING clause:
select *
from tablename
where stay_id in (
select stay_id from tablename
group by stay_id
having '2019-03-23' between
max(case when event_type = 'check-in' then date end) and max(case when event_type = 'check-out' then date end)
)
or since a check-out date is (I assume) later than or equal to a check-in date:
select *
from tablename
where stay_id in (
select stay_id from tablename
group by stay_id
having '2019-03-23' between min(date) and max(date)
)
Or if you want 1 row for each stay_id:
select stay_id, min(date) check_in_date, max(date) check_out_date
from tablename
group by stay_id
having '2019-03-23' between min(date) and max(date)

Assume everyone checked out:
select stay_id
from tbl
where event_type = 'check-in' and date <= '2019-03-23'
and stay_id in
(select stay_id
from tbl
where event_type = 'check-out' and date > '2019-03-23')
I can edit later to create a logic to include people not checked out yet

Related

How do I select data going back a period of time for each group?

I want for each group id to get the latest week's worth of data. Not from a specific date, but counting backwards from the MAX(startTime) of each individual group.
However, the following does not seem to work. I assume it's because startTime in each group (a single value) is BETWEENed by itself? Otherwise, how do I keep it in my filter?
SELECT
id
, startTime
FROM MyTable
GROUP BY id, startTime
HAVING startTime BETWEEN MAX(startTime) - INTERVAL 1 WEEK AND MAX(startTime)
What's the right query?
Also, in my case it has to work with MySQL 5.7.
SELECT m.id, m.startTime
FROM (
SELECT id, MAX(startTime) AS startTime
FROM MyTable GROUP BY id
) AS x
JOIN MyTable AS m ON m.id = x.id
AND m.startTime BETWEEN x.startTime - INTERVAL 1 WEEK AND x.startTime;

Combine Time in and Time out in one row mysql

Currently, I have this table: date_time_records where all of the time in and out of employees are being saved.
As you can see all of the data of time in and out are being stored for each row.
and these data can be identified if it is time in or out using state field
Time in = C/In
Time Out = C/Out
Expected Output
Now I'm trying to do a query something like this
Where you can see the same employee record but different day
and you can notice that the time in and out is being arranged.
Here's my code
SELECT
a.id,
a.ACNo,
a.name,
a.email,
(SELECT MAX(datetime) FROM date_time_records WHERE id = a.id AND state = "C/In") as time_in,
(SELECT MIN(datetime) FROM date_time_records WHERE id = a.id AND state = "C/Out") as time_out,
FROM `date_time_records` as a GROUP BY datetime ORDER BY `created_at` ASC
Please disregard the created_at
I'm using datetime since the system is capable to do a bulk upload of time in and out. of past data.
You could do something like this:
SELECT ACNo, Name,
SUBSTRING_INDEX(GROUP_CONCAT(CASE WHEN state='C/In' THEN DATETIME END ORDER BY DATETIME ASC),',',1) AS time_in,
SUBSTRING_INDEX(GROUP_CONCAT(CASE WHEN state='C/Out' THEN DATETIME END ORDER BY DATETIME ASC),',',-1) AS time_out,
DATE(DATETIME) AS recDate
FROM date_time_records
GROUP BY ACNo, Name,recDate
ORDER BY ACNo;
Using GROUP_CONCAT then SUBSTRING_INDEX to get the first & last value.
Fiddle here: https://www.db-fiddle.com/f/bfkoKK13kcE8NYVzo71Zi3/3
Use conditional aggregation:
SELECT dtr.id, dtr.ACNo, dtr.name, dtr.email,
MAX(CASE WHEN dtr.stat = 'C/IN' THEN dtr.datetime END) as time_in,
MIN(CASE WHEN dtr.stat = 'C/OUT' THEN dtr.datetime END) as time_out
FROM date_time_records dtr
GROUP BY dtr.id, dtr.ACNo, dtr.name, dtr.email
ORDER BY MIN(created_at) ASC
Could you just log a user's time in/time out separately from the date? That'd make the min/max query for time in/time out on a daily basis a lot simpler.

How to create SQL based on complex rule?

I have 3 columns (id, date, amount) and trying to calculate 4th column (calculated_column).
How to create SQL query to do following:
The way that needs to be calculated is to look at ID (e.g. 1) and see all same IDs for that month (e.g. for first occurrence - 1-Sep it should be calculated as 5 and for second occurrence - it would be 5+6=11 -> all amounts from beginning of that month including that amount).
Then for the next month (Oct) - it will find first occurrence of id=1 and store 3 in calculated_column and for the second occurrence of id=1 in Oct it will do sum from beginning of that month for the same id (3+2=5)
Assuming I've understood correctly, I would suggest a correlated subquery such as:
select t.*,
(
select sum(u.amount) from table1 u
where
u.id = t.id and
date_format(u.date, '%Y-%m') = date_format(t.date, '%Y-%m') and u.date <= t.date
) as calculated_column
from table1 t
(Change the table name table1 to suit your data)
In Oracle and MySQL 8+, you can use window functions. The corresponding date arithmetic varies, but here is the idea:
select t.*,
(case when date = max(date) over (partition by to_char(date, 'YYYY-MM') and
id = 1
then sum(amount) over (partition by to_char(date, 'YYYY-MM')
end) as calculated_column
from t;
The outer case is simply to put the value on the appropriate row of the result set. The code would be simpler if all rows in the month had the same value.
Here is a solution for oracle. Since you did not gave the table name I named it my_table, change it to the real name
select
t1.id,
t1.date,
t1.amount,
decode(t1.id, 1, sum(nvl(t2.amount, 0)), null) calculated_column
from my_table1 t1
left join my_table t2
on trunc(t2.date, 'month') = trunc(t1.date, 'month')
and t1.id = 1
group by t1.id, t1.date, t1.amount
If your version supports window function (e.g. MySQL 8 upwards)
# MySQL 8+
select
t.*
, sum(amount) over (partition by id, date_format(date, '%Y-%m-01') order by date) as calculated_column
from t
;
-- Oracle
select
t.*
, sum(amount) over (partition by id, trunc(date, 'MM') order by date) as calculated_column
from t
;

How to Group a table and get results for a row based on the previous rows' data

I have a lookup table that relates dates and people associated with those dates:
id, user_id,date
1,1,2014-11-01
2,2,2014-11-01
3,1,2014-11-02
4,3,2014-11-02
5,1,2014-11-03
I can group these by date(day):
SELECT DATE_FORMAT(
MIN(date),
'%Y/%m/%d 00:00:00 GMT-0'
) AS date,
COUNT(*) as count
FROM user_x_date
GROUP BY ROUND(UNIX_TIMESTAMP(created_at) / 43200)
But, how can get the number of unique users, that have now shown up previously? For instance this would be a valid result:
unique, non-unique, date
2,0,2014-11-01
1,1,2014-11-02
0,1,2014-11-03
Is this possibly without having to rely on a scripting language to keep track of this data?
I think this query will do what you want, at least it seems to work for your limited sample data.
The idea is to use a correlated sub-query to check if the user_id has occurred on a date before the date of the current row and then do some basic arithmetic to determine number of unique/non-unique users for each date.
Please give it a try.
select
sum(u) - sum(n) as "unique",
sum(n) as "non-unique",
date
from (
select
date,
count(user_id) u,
case when exists (
select 1
from Table1 i
where i.user_id = o.user_id
and i.date < o.date
) then 1 else 0
end n
from Table1 o
group by date, user_id
) q
group by date
order by date;
Sample SQL Fiddle
I didn't include the id column in the sample fiddle as it's not needed (or used) to produce the result and won't change anything.
This is the relevant question: "But, how can get the number of unique users, that have now shown up previously?"
Calculate the first time a person shows up, and then use that for the aggregation:
SELECT date, count(*) as FirstVisit
FROM (SELECT user_id, MIN(date) as date
FROM user_x_date
GROUP BY user_id
) x
GROUP BY date;
I would then use this as a subquery for another aggregation:
SELECT v.date, v.NumVisits, COALESCE(fv.FirstVisit, 0) as NumFirstVisit
FROM (SELECT date, count(*) as NumVisits
FROM user_x_date
GROUP BY date
) v LEFT JOIN
(SELECT date, count(*) as FirstVisit
FROM (SELECT user_id, MIN(date) as date
FROM user_x_date
GROUP BY user_id
) x
GROUP BY date
) fv
ON v.date = fv.date;

MySQL - How to select rows with the min(timestamp) per hour of a given date

I have a table of production readings and need to get a result set containing a row for the min(timestamp) for EACH hour.
The column layout is quite simple:
ID,TIMESTAMP,SOURCE_ID,SOURCE_VALUE
The data sample would look like:
123,'2013-03-01 06:05:24',PMPROD,12345678.99
124,'2013-03-01 06:15:17',PMPROD,88888888.99
125,'2013-03-01 06:25:24',PMPROD,33333333.33
126,'2013-03-01 06:38:14',PMPROD,44444444.44
127,'2013-03-01 07:12:04',PMPROD,55555555.55
128,'2013-03-01 10:38:14',PMPROD,44444444.44
129,'2013-03-01 10:56:14',PMPROD,22222222.22
130,'2013-03-01 15:28:02',PMPROD,66666666.66
Records are added to this table throughout the day and the source_value is already calculated, so no sum is needed.
I can't figure out how to get a row for the min(timestamp) for each hour of the current_date.
select *
from source_readings
use index(ID_And_Time)
where source_id = 'PMPROD'
and date(timestamp)=CURRENT_DATE
and timestamp =
( select min(timestamp)
from source_readings use index(ID_And_Time)
where source_id = 'PMPROD'
)
The above code, of course, gives me one record. I need one record for the min(hour(timestamp)) of the current_date.
My result set should contain the rows for IDs: 123,127,128,130. I've played with it for hours. Who can be my hero? :)
Try below:
SELECT * FROM source_readings
JOIN
(
SELECT ID, DATE_FORMAT(timestamp, '%Y-%m-%d %H') as current_hour,MIN(timestamp)
FROM source_readings
WHERE source_id = 'PMPROD'
GROUP BY current_hour
) As reading_min
ON source_readings.ID = reading_min.ID
SELECT a.*
FROM Table1 a
INNER JOIN
(
SELECT DATE(TIMESTAMP) date,
HOUR(TIMESTAMP) hour,
MIN(TIMESTAMP) min_date
FROM Table1
GROUP BY DATE(TIMESTAMP), HOUR(TIMESTAMP)
) b ON DATE(a.TIMESTAMP) = b.date AND
HOUR(a.TIMESTAMP) = b.hour AND
a.timestamp = b.min_date
SQLFiddle Demo
With window function:
WITH ranked (
SELECT *, ROW_NUMBER() OVER(PARTITION BY HOUR(timestamp) ORDER BY timestamp) rn
FROM source_readings -- original table
WHERE date(timestamp)=CURRENT_DATE AND source_id = 'PMPROD' -- your custom filter
)
SELECT * -- this will contain `rn` column. you can select only necessary columns
FROM ranked
WHERE rn=1
I haven't tested it, but the basic idea is:
1) ROW_NUMBER() OVER(PARTITION BY HOUR(timestamp) ORDER BY timestamp)
This will give each row a number, starting from 1 for each hour, increasing by timestamp. The result might look like:
|rest of columns |rn
123,'2013-03-01 06:05:24',PMPROD,12345678.99,1
124,'2013-03-01 06:15:17',PMPROD,88888888.99,2
125,'2013-03-01 06:25:24',PMPROD,33333333.33,3
126,'2013-03-01 06:38:14',PMPROD,44444444.44,4
127,'2013-03-01 07:12:04',PMPROD,55555555.55,1
128,'2013-03-01 10:38:14',PMPROD,44444444.44,1
129,'2013-03-01 10:56:14',PMPROD,22222222.22,2
130,'2013-03-01 15:28:02',PMPROD,66666666.66,1
2) Then on the main query we select only rows with rn=1, in other words, rows that has lowest timestamp in each hourly partition (1st row after sorted by timestamp in each hour).