MySQL date difference check within a trigger - mysql

Within a trigger before I insert some data into a table, I would want it to check, whether the difference of dates of events I am entering is greater than or equal to 1 day. There can only be one event in one club taking place each day.
Sample story
If there already is 2014-01-01 19:00:00 date in database and I'm trying to insert another record with 2014-01-01 date (hour does not matter), it should not allow it.
Partial code from the trigger
DECLARE k INT DEFAULT 0;
/* This is where I get the error, ABS is to make it always positive to go through
checking, so that it wont matter whether the NEW date is before or after */
SELECT ABS(DATEDIFF(DATE_FORMAT(`performance_date`, '\'%Y-%m-%d %H:%i:%s\''),
DATE_FORMAT(NEW.`performance_date`, '\'%Y-%m-%d %H:%i:%s\''))) INTO k;
/* Below code is out of scope for this question */
IF k = 0 THEN
SIGNAL SQLSTATE '58005'
SET MESSAGE_TEXT = 'Wrong! Only 1 performance in 1 club is allowed per day! Change your date, or club!';
END IF;
Error Code: 1054. Unknown column 'performance_date' in 'field list'
I've tried something as simple as:
...DATEDIFF(`performance_date`, NEW.`performance_date`)

You can use a SELECT ... INTO var_list query to COUNT how many entries are already in the database that match your times:
I'm assuming that you mean one entry per day, and I'm assuming that the performance_date column is of DATETIME or TIMESTAMP type.
DECLARE k INT DEFAULT 0;
/* Count number of performances occurring on the same date as the
performance being inserted */
SELECT COUNT(*)
FROM tbl
WHERE performance_date
BETWEEN DATE(NEW.`performance_date`)
AND DATE(DATE_ADD(NEW.`performance_date`, INTERVAL 1 DAY))
INTO k;
/* If k is not 0, error as there is already a performance */
IF k != 0 THEN
SIGNAL SQLSTATE '58005'
SET MESSAGE_TEXT = 'Wrong! Only 1 performance in 1 club is allowed per day! Change your date, or club!';
END IF;
For clarity, if you have a performance with performance_date as 2014-01-01 19:00:00, and you insert a new performance with date 2014-01-01 08:30:00 (for example) then the above code will run this query, which will return a COUNT of 1, which will then cause the trigger to give that error:
SELECT COUNT(*)
FROM tbl
WHERE performance_date
BETWEEN DATE("2014-01-01 08:30:00") AND DATE(DATE_ADD("2014-01-01 08:30:00", INTERVAL 1 DAY))
# The line above will become:
# BETWEEN "2014-01-01" AND "2014-01-02"
INTO k

Related

sql Trigger ensuring 30 days between appointments not working

Trying to create a trigger ensuring that there is at least 30 days between appointments but this doesn't seem to work:
delimiter //
create trigger time_between_doses
before insert on appointment
for each row
begin
if datediff((select appointment_time from appointment where new.patientSSN = patientSSN),
new.appointment_time) < 30
THEN SIGNAL SQLSTATE '45000'
SET MYSQL_ERRNO = 9996,
MESSAGE_TEXT = 'Not 30 days between appointments for this patient';
end if;
end //
delimiter ;
The query that you want to check would use exists. It is not 100% clear what the "30 days" means. Presumably you want to know if this returns any rows:
select a.*
from appointment a
where new.patientSSN = a.patientSSN and
new.appointment_time < a.appointment_time + interval 30 day;
One thing that is unclear is whether the 30 day limit goes into the past as well as the future. And whether it applies to only to appointments in the past or also to appointments in the future.
Your version is an error waiting to happen because you are using a subquery where a single value is expected. If the subquery returns multiple rows, then the code will generate an error. The above logic should be used with exists.

how to shift back the date by a certain number of days v2

I have the following problem: I have a table in which in the first column there are dates, in the second information whether a day is a working day or not (1 or 0) and in the third, the number of working days by which the value from the first column should be shift back. Anyone have maybe think how to get this fourth column?
The table looks something like this:
date
workday
days back
10.01.2021
1
1
10.01.2021
1
2
10.01.2021
1
3
10.01.2021
1
4
11.01.2021
0
1
11.01.2021
0
2
11.01.2021
0
3
11.01.2021
0
4
12.01.2021
1
1
12.01.2021
1
2
12.01.2021
1
3
12.01.2021
1
4
or otherwise .... the third column is the number of working days needed to make a given product. The first column is the date when the product have to be ready.
I need in column nr 4 the date (the working day) in which to start production. For example for line 9 it will be 10.012021 This view is supposed to be a combination of product realization dates and production start dates for different production lifecycle values.
You either need every working day in your table or you need a separate table with a every day and whether it is a working day or not.
I'm not familiar with mysql, the below is my attempt to translate something that works in MS SQL Server, I'm hoping it will work in mysql.
Below I've assumed your only table is called Table1 and it has every working day in and is the table from your question.
First you need to create a function
CREATE FUNCTION [OffsetDate]
(
#StartDate DATE NULL,
#OffsetDays INT = 0
)
RETURNS DATE
AS
BEGIN
DECLARE #Result DATE
SET #Result = #StartDate
BEGIN
SELECT #Result = distinctDates.Date
FROM (SELECT DISTINCT Table1.Date
FROM Table1
WHERE Table1.Date <= #StartDate
AND Table1.WorkDay = 1) AS distinctDates
ORDER BY distinctDates.Date DESC
LIMIT 1 OFFSET #OffsetDays
END
RETURN #Result
END
Then use this in a query to get your 4th column:
SELECT Table1.Date, Table1.WorkDay, Table1.[Days Back], OffsetDate(Table1.Date, Table1.[Days Back]) AS StartDate
FROM Table1

Mysql date range query slow

I have 2 mysql tables spot_times - 10k rows and visit_times - 5.3 million rows.
I m trying to write a query that can join spot_times.spot_date on visit_times.visit_date based on a 10 minute window.
Both date fields are indexed and column type datetime.
I have written the following sql which takes hours to run.
Select spot_date, count(visit_date) total_visits
From spot_times st
Left
Join visit_times v
on v.visit_date between st.spot_date and st.spot_date + interval 10 minute
group by 1;
This query takes hours to run.
My explain plan looks like the query is not using the indexes.
Explain plan
Please help.
Range queries are notoriously difficult to get useful index performance with on large datasets.
You might be able to get some benefit out of partitioning visit_times by date range: https://dev.mysql.com/doc/refman/8.0/en/partitioning-range.html
Just thought it might be useful for anyone that came across the same issue.
I started off by adding an auto_increment column visit_id on the visits_times table ordered by visit_date field.
Idea is to get the visit_id nearest to st.spot_date and st.spot_date + interval 10 minute.Then subtracting the visit_id which should be the total visits between the range.
Created a function to return visit_id for a date and interval.Function uses the visit_date index and loops until it finds a record adding a second on every loop.
DELIMITER //
DROP function IF EXISTS `spot_time_function` //
CREATE function `spot_time_function`( p_datetime datetime, p_time int)
returns int
BEGIN
declare v_id int ;
declare z int;
set z = 0;
time_loop: LOOP
select visit_id into v_id from visit_times where visit_date = p_datetime + interval p_time minute + interval z second limit 1;
IF v_id is not null THEN
LEAVE time_loop;
END IF;
SET z = z + 1;
END LOOP;
return v_id;
END //
DELIMITER ;
So final query looks like.
Select
spot_date,
spot_time_function(spot_date,10) - spot_time_function(spot_date,0) as total_visit
From spot_times;
Above query runs in 0.110 sec.

Mysql Add days onto date from two different columns

Hi my problem relates to adding days onto a date from two different tables in MySql in the Mamp environment.
Membership type to Membership transaction is 1 to many
The link is type_id
Date is in yyyy/mm/dd format also as this is the only format that Mamp will allow from my research.
I want a new end date column that links to the column duration from the membership type table. I want to add Duration_day onto start_date to produce an end date that matches up with the type_id. (So they link up to give the correct end date)
I want it to be automatically calculated when the start date and type-id are saved
Any help would be appreciated
Thanks
You will need a trigger on INSERT/UPDATE - a calculated column (for MySQL v5.7.6+) will not work in your case (it can only refer to columns in the same table).
The 2 triggers may look like this
CREATE DEFINER = 'root'#'%' TRIGGER `m_duration_ins_tr1` BEFORE INSERT ON `membership`
FOR EACH ROW
BEGIN
DECLARE duration INTEGER;
SELECT m_duration INTO duration FROM membership_type WHERE id = NEW.type_id;
SET NEW.end_date := DATE_ADD(NEW.start_date, INTERVAL duration DAY);
END;
CREATE DEFINER = 'root'#'%' TRIGGER `m_duration_ins_tr1` BEFORE UPDATE ON `membership`
FOR EACH ROW
BEGIN
DECLARE duration INTEGER;
SELECT m_duration INTO duration FROM membership_type WHERE id = NEW.type_id;
SET NEW.end_date := DATE_ADD(NEW.start_date, INTERVAL duration DAY);
END;

Group by day and still show days without rows?

I have a log table with a date field called logTime. I need to show the number of rows within a date range and the number of records per day. The issue is that i still want to show days that do not have records.
Is it possible to do this only with SQL?
Example:
SELECT logTime, COUNT(*) FROM logs WHERE logTime >= '2011-02-01' AND logTime <= '2011-02-04' GROUP BY DATE(logTime);
It returns something like this:
+---------------------+----------+
| logTime | COUNT(*) |
+---------------------+----------+
| 2011-02-01 | 2 |
| 2011-02-02 | 1 |
| 2011-02-04 | 5 |
+---------------------+----------+
3 rows in set (0,00 sec)
I would like to show the day 2011-02-03 too.
MySQL will not invent rows for you, so if the data is not there, they will naturally not be shown.
You can create a calendar table, and join in that,
create table calendar (
day date primary key,
);
Fill this table with dates (easy with a stored procedure, or just some general scripting), up till around 2038 and something else will likely break unitl that becomes a problem.
Your query then becomes e.g.
SELECT logTime, COUNT(*)
FROM calendar cal left join logs l on cal.day = l.logTime
WHERE day >= '2011-02-01' AND day <= '2011-02-04' GROUP BY day;
Now, you could extend the calendar table with other columns that tells you the month,year, week etc. so you can easily produce statistics for other time units. (and purists might argue the calendar table would have an id integer primary key that the logs table references instead of a date)
In order to accomplish this, you need to have a table (or derived table) which contains the dates that you can then join from, using a LEFT JOIN.
SQL operates on the concept of mathematical sets, and if you don't have a set of data, there is nothing to SELECT.
If you want more details, please comment accordingly.
I'm not sure if this is a problem that should be solved by SQL. As others have shown, this requires maintaining a second table that contains the all of the individual dates of a given time span, which must be updated every time that time span grows (which presumably is "always" if that time span is the current time.
Instead, you should use to inspect the results of the query and inject dates as necessary. It's completely dynamic and requires no intermediate table. Since you specified no language, here's pseudo code:
EXECUTE QUERY `SELECT logTime, COUNT(*) FROM logs WHERE logTime >= '2011-02-01' AND logTime <= '2011-02-04' GROUP BY DATE(logTime);`
FOREACH row IN query result
WHILE (date in next row) - (date in this row) > 1 day THEN
CREATE new row with date = `date in this row + 1 day`, count = `0`
INSERT new row IN query result AFTER this row
ADVANCE LOOP INDEX TO new row (`this row` is now the `new row`)
END WHILE
END FOREACH
Or something like that
DECLARE #TOTALCount INT
DECLARE #FromDate DateTime = GetDate() - 5
DECLARE #ToDate DateTime = GetDate()
SET #FromDate = DATEADD(DAY,-1,#FromDate)
Select #TOTALCount= DATEDIFF(DD,#FromDate,#ToDate);
WITH d AS
(
SELECT top (#TOTALCount) AllDays = DATEADD(DAY, ROW_NUMBER()
OVER (ORDER BY object_id), REPLACE(#FromDate,'-',''))
FROM sys.all_objects
)
SELECT AllDays From d