sql query to get conflicts on time like an alarm - mysql

I’m trying to create an sql query to get a list of possible conflicts on my table but so far my sql doesn’t work.
The idea is to have like an alarm function
If date_start = date_end that means one shot
Example: date_start = 2018-11-07 10:37:00 and date_end = 2018-11-07 10:37:00
If date_start not null and date _end = null , one of the days column should be different from 0
Example: date_start = 2018-11-07 10:37:00 and date_end = NULL and Monday = 2
.
That means starting the start_date every Monday I’m gonna get the row at that time
My problem now is that I’m trying to get a list of conflicts if I have 2 rows that can be at the same time and date . Here is an example of my database:
I created an sql query above but It’s not working well, any one can help ?
SELECT
id
FROM
my_table
WHERE
date_start IN(
SELECT
date_start
FROM
my_table
WHERE
date_start = date_end
GROUP BY
date_start
HAVING
COUNT(*) > 1
)
UNION
SELECT
id
FROM
my_table
WHERE
DATE_FORMAT(date_start, '%H:%i:%s') IN(
SELECT
DATE_FORMAT(date_start, '%H:%i:%s')
FROM
my_table
WHERE
date_end IS NULL AND(
monday = 2 OR tuesday = 3 OR wednesday = 4 OR thursday = 5 OR friday = 6 OR saturday = 7 OR sunday = 1
)
GROUP BY
DATE_FORMAT(date_start, '%H:%i:%s')
HAVING
COUNT(*) > 1
)
UNION
SELECT
id
FROM
my_table
WHERE
DATE_FORMAT(date_start, '%H:%i:%s') IN(
SELECT
CASE WHEN
(DAYOFWEEK(date_start) = 2 AND (select monday from my_table where monday = 2) IS NOT NULL)
OR (DAYOFWEEK(date_start) = 3 AND (select tuesday from my_table where tuesday = 3) IS NOT NULL)
OR (DAYOFWEEK(date_start) = 4 AND (select wednesday from my_table where wednesday = 4) IS NOT NULL)
OR (DAYOFWEEK(date_start) = 5 AND (select thursday from my_table where thursday = 5) IS NOT NULL)
OR (DAYOFWEEK(date_start) = 6 AND (select friday from my_table where friday = 6) IS NOT NULL)
OR (DAYOFWEEK(date_start) = 7 AND (select saturday from my_table where saturday = 7) IS NOT NULL)
OR (DAYOFWEEK(date_start) = 1 AND (select sunday from my_table where sunday = 1) IS NOT NULL)
THEN DATE_FORMAT(date_start, '%H:%i:%s')
ELSE NULL
END
FROM
my_table
WHERE
(DAYOFWEEK(date_start) = 2
OR DAYOFWEEK(date_start) = 3
OR DAYOFWEEK(date_start) = 4
OR DAYOFWEEK(date_start) = 5
OR DAYOFWEEK(date_start) = 6
OR DAYOFWEEK(date_start) = 7
OR DAYOFWEEK(date_start) = 1
)
GROUP BY
DATE_FORMAT(date_start, '%H:%i:%s')
HAVING
COUNT(*) > 1
)

Perhaps something like this
SELECT date_start FROM alarms
WHERE date_end IS NULL AND
monday + tuesday + wednesday + thursday + friday + saturday + sunday > 0
GROUP BY TIME(date_start)
HAVING COUNT(monday)>0 OR COUNT(tuesday)>0 OR COUNT(wednesday)>0 OR
COUNT(thursday)>0 OR COUNT(friday)>0 OR COUNT(saturday)>0 OR COUNT(sunday)>0
UPDATE
If you want to catch conflicts between one-shot events and weekly events you can use a query like this
SELECT a1.date_start AS oneshow,a2.date_start AS weekly,DAYOFWEEK(a1.date_start) as dow
FROM alarms AS a1
LEFT JOIN alarms AS a2 ON
a1.date_start >= a2.date_start AND a2.date_end IS NULL
AND TIME(a1.date_start) = TIME(a2.date_start)
AND DAYOFWEEK(a1.date_start) IN (a2.monday, a2.tuesday, a2.wednesday, a2.thursday, a2.friday, a2.saturday, a2.sunday)
WHERE a1.date_end IS NOT NULL

Related

Value for first available date if no record exisits

i have Items table:
item_id | date | item_price |
---------+-------------+--------------
1 | 2022-12-05 | 15 |
2 | 2022-02-14 | 12 |
1 | 2022-11-12 | 50 |
4 | 2022-01-21 | 13 |
1 | 2021-12-12 | 10 |
6 | 2021-12-27 | 83 |
The query which i use to select price one week ago from today's date:
SELECT
items.item_id AS id,
items.item_price AS weekAgoPrice,
items2.item_price AS monthAgoPrice,
FROM
items
LEFT JOIN
items items2 ON items2.item_id = items.item_id
AND items2.date = DATE_SUB(CURDATE(), INTERVAL 30 DAY)
WHERE
items.item_id = '1'
AND items.date = DATE_SUB(CURDATE(), INTERVAL 7 DAY);
How can i modify query that will return the price from the first available date if there is no entry for a particular date. Those, if for the specified item_id there is no price 7 days ago, then it should return the value of 6 days ago, if not 6 then 5. Additionally, if there is no price 1 month ago, it should return value of 29 days ago etc.
You may try with max window function as the following:
With last_prices As
(
Select *,
Max(Case
When date Between DATE_SUB(CURDATE(), INTERVAL 7 DAY) And CURDATE()
Then date
End) Over (Partition By item_id) As last_price_date_week,
Max(Case
When date Between DATE_SUB(CURDATE(), INTERVAL 1 MONTH) And CURDATE()
Then date
End) Over (Partition By item_id) As last_price_date_month
From items
)
Select item_id, date, item_price, 'last week price' As Price_Type
From last_prices
Where item_id = 1 And date = last_price_date_week
Union All
Select item_id, date, item_price, 'last month price' As Price_Type
From last_prices
Where item_id = 1 And date = last_price_date_month
See demo.
If you are open to using a stored procedure, you could make this task more dynamic, have a look at this procedure:
Create Procedure GetLatesPrice(id INT, period INT, interval_type Varchar(1))
Begin
Select item_id, date, item_price
From
(
Select *,
Case
When interval_type='d' Then
Max(
Case
When date Between DATE_SUB(CURDATE(), INTERVAL period Day) And CURDATE()
Then date
End
) Over (Partition By item_id)
When interval_type='m' Then
Max(
Case
When date Between DATE_SUB(CURDATE(), INTERVAL period Month) And CURDATE()
Then date
End
) Over (Partition By item_id)
When interval_type='d' Then
Max(
Case
When date Between DATE_SUB(CURDATE(), INTERVAL period Year) And CURDATE()
Then date
End
) Over (Partition By item_id)
End As last_price_date
From items
) T
Where date = last_price_date And item_id=id;
END
And to execute that procedure for example for item_id=1, 15 days ago:
Call GetLatesPrice(1, 15, 'd');
-- d for days, m for months, y for years
See demo.
If there is no entry for a particular date, you can use the COALESCE function
SELECT
items.item_id AS id,
COALESCE(
items.item_price,
(SELECT items.item_price FROM items WHERE items.item_id = '1' AND items.date = DATE_SUB(CURDATE(), INTERVAL 6 DAY)),
(SELECT items.item_price FROM items WHERE items.item_id = '1' AND items.date = DATE_SUB(CURDATE(), INTERVAL 5 DAY)),
...
) AS price
FROM items
WHERE items.item_id = '1'
SELECT
items.item_id AS id,
COALESCE(
items.item_price,
(SELECT items.item_price FROM items WHERE items.item_id = '1' AND items.date = DATE_SUB(CURDATE(), INTERVAL 6 DAY)),
(SELECT items.item_price FROM items WHERE items.item_id = '1' AND items.date = DATE_SUB(CURDATE(), INTERVAL 5 DAY)),
...
) AS price
FROM items
WHERE items.item_id = '1'
If all you need are scalar values then this would suffice:
SET #ItemID = 1;
SELECT (SELECT item_price FROM items WHERE item_id = #ItemID
AND date1 >= DATE_ADD(CURDATE(), INTERVAL -7 DAY) order by date1 LIMIT 1) as WeekAgoPrice,
etc.
Also see the LATERAL documentation.
Without sample output data it's hard to tell what you intend.

Select the next day occurance from the table

This is my db structure of the table game days. Every game ends at 8:00 pm . Here all the game id is same.So now i want to query by the game id like this way that if today is monday and it is before 8:00 pm so it will fetch only the game_days with value of day='Monday'.Once 8:00pm is over then it will show the row with the value of day='Wednesday' until wednesday 8:00pm and after 8:00 pm it will show the row with the value of day='Friday' until Friday 8:00pm and then again after Friday 8:00pm it will show Monday...
So what will be the query for this ?
I think you just want the date offset by 4 hours. One method is:
where date(updated_at + interval 4 hour) = curdate()
I am guessing that updated_at is the column you want to reference, but it can be any date/time column.
I prefer to express this as:
where updated_at >= curdate() - interval 4 hour and
updated_at < curdate() + interval 1 day - interval 4 hour
This version can make use of an index on the date/time column.
Please try this (updated based on comments, replace table_name with appropriate table name is your db):
SELECT *
FROM `table_name`
WHERE day = (
SELECT t3.day FROM `table_name` as t3
LEFT JOIN (
SELECT HOUR(NOW()),
CASE
WHEN HOUR(NOW()) <= 19 THEN DAYNAME(NOW() + INTERVAL t.p DAY)
ELSE DAYNAME(NOW() + INTERVAL (t.p + 1) DAY)
END AS day,
CASE
WHEN HOUR(NOW()) <= 19 THEN t.p
ELSE t.p + 1
END as p
FROM (
SELECT 0 as p
UNION SELECT 1 as p
UNION SELECT 2 as p
UNION SELECT 3 as p
UNION SELECT 4 as p
UNION SELECT 5 as p
UNION SELECT 6 as p
) t
) t2 on t3.day = t2.day
ORDER BY t2.p ASC LIMIT 1
)

SQL Query to find rows that didn't occur this month

I am trying to find the number of sellers that made a sale last month but didn't make a sale this month.
I have a query that works but I don't think its efficient and I haven't figured out how to do this for all months.
SELECT count(distinct user_id) as users
FROM transactions
WHERE MONTH(date) = 12
AND YEAR(date) = 2015
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
AND transactions.user_id NOT IN
(
SELECT distinct user_id
FROM transactions
WHERE MONTH(date) = 1
AND YEAR(date) = 2016
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
)
The structure of the table is:
+---------+------------+-------------+--------+
| user_id | date | status | amount |
+---------+------------+-------------+--------+
| 1 | 2016-01-01 | 'COMPLETED' | 1.00 |
| 2 | 2015-12-01 | 'COMPLETED' | 1.00 |
| 3 | 2015-12-01 | 'COMPLETED' | 2.00 |
| 1 | 2015-12-01 | 'COMPLETED' | 3.00 |
+---------+------------+-------------+--------+
So in this case, users with ID 2 and 3, didn't make a sale this month.
Use conditional aggregation:
SELECT count(*) as users
FROM
(
SELECT user_id
FROM transactions
-- 1st of previous month
WHERE date BETWEEN SUBDATE(SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1), interval 1 month)
-- end of current month
AND LAST_DAY(CURRENT_DATE)
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
GROUP BY user_id
-- any row from previous month
HAVING MAX(CASE WHEN date < SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1)
THEN date
END) IS NOT NULL
-- no row in current month
AND MAX(CASE WHEN date >= SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1)
THEN date
END) IS NULL
) AS dt
SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1) = first day of current month
SUBDATE(first day of current month, interval 1 month) = first day of previous month
LAST_DAY(CURRENT_DATE) = end of current month
if you want to generify it, you can use curdate() to get current month, and DATE_SUB(curdate(), INTERVAL 1 MONTH) to get last month (you will need to do some if clause for January/December though):
SELECT count(distinct user_id) as users
FROM transactions
WHERE MONTH(date) = MONTH(DATE_SUB(curdate(), INTERVAL 1 MONTH))
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
AND transactions.user_id NOT IN
(
SELECT distinct user_id
FROM transactions
WHERE MONTH(date) = MONTH(curdate())
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
)
as far as efficiency goes, I don't see a problem with this one
The following should be pretty efficient. In order to make it even more so, you would need to provide the table definition and and the EXPLAIN.
SELECT COUNT(DISTINCT user_id) users
FROM transactions t
LEFT
JOIN transactions x
ON x.user_id = t.user_id
AND x.date BETWEEN '2016-01-01' AND '2016-01-31'
AND x.status = 'COMPLETED'
AND x.amount > 0
WHERE t.date BETWEEN '2015-12-01' AND '2015-12-31'
AND t.status = 'COMPLETED'
AND t.amount > 0
AND x.user_id IS NULL;
Just some input for thought:
You could create aggregated lists of user-IDs per month, representing all the unique buyers in that month. In your application, you would then simply have to subtract the two months in question in order to get all user-IDs that have only made a sale in one of the two months.
See below for query- and post-processing-examples.
In order to make your query efficient, I would recommend at least a 2-column index for table transactions on [status, amount]. However, in order to prevent the query from having to look up data in the actual table, you could even create a 4-column index [status, amount, date, user_id], which should further improve the performance of your query.
Postgres (v9.0+, tested)
SELECT (DATE_PART('year', t.date) || '-' || DATE_PART('month', t.date)) AS d,
STRING_AGG( DISTINCT t.user_id::TEXT, ',' ) AS buyers
FROM transactions t
WHERE t.status = 'COMPLETED'
AND t.amount > 0
GROUP BY DATE_PART('year', t.date),
DATE_PART('month', t.date)
ORDER BY DATE_PART('year', t.date),
DATE_PART('month', t.date)
;
MySQL (not tested)
SELECT (YEAR(t.date) || '-' || MONTH(t.date)) AS d,
GROUP_CONCAT( DISTINCT t.user_id ) AS buyers
FROM transactions t
WHERE t.status = 'COMPLETED'
AND t.amount > 0
GROUP BY YEAR(t.date), MONTH(t.date)
ORDER BY YEAR(t.date), MONTH(t.date)
;
Ruby (example for post-processing)
db_result = ActiveRecord::Base.connection_pool.with_connection { |con| con.execute( db_query ) }
unique_buyers = db_result.map{|e|[e['d'],e['buyers'].split(',')]}.to_h
buyers_dec15_but_not_jan16 = unique_buyers['2015-12'] - unique_buyers['2016-1']
buyers_nov15_but_not_dec16 = unique_buyers['2015-11']||[] - unique_buyers['2015-12']
...(and so on)...

Get total hours worked between 2 days mysql

I have a MySQL table where employee login and logout timings are recorded. Here in the in-out column 1-represents login and 0-represents logout.
[id] [User_id] [Date_time] [in_out]
1 1 2011-01-20 18:01:03 1
2 1 2011-01-20 19:30:43 0
3 1 2011-01-20 20:46:23 1
4 1 2011-01-21 00:42:45 0
Is it possible to retrieve total hours worked in a day (between 2 days) by a user using single query?
The same Question it's a copy of Get total hours worked in a day mysql and solution:
SELECT `User_id`, time(sum(`Date_time`*(1-2*`in_out`)))
FROM `whatever_table` GROUP BY `User_id`;
But the solution needs to be different when the employee start working in a day and go out on the next day.
You can achieve this using given stored procedure. Consider your table name EventLog.
DELIMITER $$
CREATE PROCEDURE `GET_TOTAL_LOGIN_TIME`(
IN startDate DATETIME,
IN endDate DATETIME,
IN userId INT(11)
)
BEGIN
select
(sum(
case when(e2.Date_time <= startDate) then 0 else
case when(e1.Date_time >= endDate) then 0 else
case when(e1.Date_time >= startDate && e2.Date_time <= endDate) then
TIME_TO_SEC(TIMEDIFF(e2.Date_time, e1.Date_time))/60 else
case when(e1.Date_time <= startDate && e2.Date_time <= endDate) then
TIME_TO_SEC(TIMEDIFF(e2.Date_time, startDate))/60 else
case when(e1.Date_time >= startDate && e2.Date_time >= endDate) then
TIME_TO_SEC(TIMEDIFF(endDate,e1.Date_time))/60
end end end end end
)) as loginTimeInMin
from
((EventLog e1
left join EventLog e2 ON (((e1.User_id = e2.User_id) and (e2.in_out = 0) and (e1.Date_time < e2.Date_time))))
left join EventLog e3 ON (((e1.User_id = e3.User_id) and (e1.Date_time < e3.Date_time) and (e3.Date_time < e2.Date_time))))
where
((e1.in_out = 1) and isnull(e3.Date_time)) and e2.Date_time is not null
AND e1.User_id = userId
AND userRole.userRoleId = roleId
AND userRole.userLoginId = userId
group by e1.User_id;
END;
You can get the number of seconds worked like this:
SELECT `User_id`, sum(unix_timestamp(`Date_time`)*(1-2*`in_out`))
FROM `whatever_table`
GROUP BY `User_id`;
Then you can convert the seconds to whatever you want.

Select users who fits in timetable mysql

I need to select all users who "fits" in theirs working timetables.
Table in MySQL
Timetable:
user_id PRIMARY
day_number(0-sunday 6-saturday) PRIMARY (one user - one day)
start start worktime
end end worktime
Sample user:
user_id = 1
day_number = 1
start = 10:00
end = 18:00
user_id = 1
day_number = 2
start = 12:00
end = 18:00
user_id = 1
day_number = 3
start = 14:00
end = 16:00
Now I want select every user who works from 1(Monday) to 3(Wednesday) from 14:00 to 16:00.
Sample user (with id 1) should be included.
Any Ideas?
SELECT user_id
FROM my_table
NATURAL JOIN (SELECT 1 day_number UNION ALL SELECT 2 UNION ALL SELECT 3) days
JOIN (SELECT MAKETIME(14,0,0) start, MAKETIME(16,0,0) end) times
ON my_table.start <= times.start
AND my_table.end >= times.end
GROUP BY user_id
HAVING COUNT(DISTINCT my_table.day_number) = 3 -- number of days in range
See it on sqlfiddle.
Try this query
SELECT *
FROM `Timetable`
WHERE day_number between
1 and 3
AND TIME_TO_SEC( start ) <= TIME_TO_SEC( '14:00' )
AND TIME_TO_SEC( end ) >= TIME_TO_SEC( '16:00' )
Or
SELECT *
FROM `Timetable`
WHERE day_number in (1,2,3 )
AND TIME_TO_SEC( start ) <= TIME_TO_SEC( '14:00' )
AND TIME_TO_SEC( end ) >= TIME_TO_SEC( '16:00' )
Try the code below:
Select * from Timetable
Where day_number>=1 and day_number <=3 And
hour(start) >= 12 and hour(end)<= 16