Let’s consider table
Video(
IDvideo(PK),
Date,
Description,
User
)
with mysql I have no way of writing assertions.
Is it possible to simulate the following assertion using one or more triggers ?
create assertion asser1
check (0 =
( select count(*)
from Video
where Date >= DATE_SUB(current_date(),INTERVAL 1 YEAR )
&& Date<=current_date()
group by User
having count(*) > 200
)
)
how should I write that trigger?
Well, the problem is that MySQL doesn't have an equivalent of a STOP ACTION command. So basically, the work arounds are quite dirty:
One way is that you can violate a constraint inside the trigger to bubble an error and cancel the insert:
CREATE TABLE stop_action (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(35),
UNIQUE KEY (id, name)
);
INSERT INTO stop_action (1, 'Assert Failure');
Then, in the trigger, just try to:
INSERT INTO stop_action (1, 'Assert Failure');
The benefit of that, is that the error that's returned will be a duplicate key error, and the text will include "Assert Failure".
So then your trigger would become:
delimiter |
CREATE TRIGGER asser1_before BEFORE INSERT ON test1
FOR EACH ROW BEGIN
SELECT count(*) INTO test FROM (select count(*)
from Video
where Date >= DATE_SUB(current_date(),INTERVAL 1 YEAR )
&& Date<=current_date()
group by User
having count(*) > 200);
IF test != 0 THEN
INSERT INTO stop_action (1, 'Assert Failure');
END IF;
END;
|
delimiter ;
Now, you'd need to do this before UPDATE as well, otherwise you could update the date into an invalid state. But otherwise, that should at least get you started...
Related
Hello I am new for MySQL. I have 2 table, a data table and a log/history table. I would like to make a trigger, that put the old data from Data to Data_log if any of the column change on the Data. I made a trigger but I don't know how to check if the value of any column changes. Lately I would like to create some procedure/view which can return one line data on a specific date. Like return all field from ID 1 on 2022-03-27
Data:
ID
name
data
price
1
thing1
desc of t1
100
2
thing2
desc of t2
300
Data_log:
log_id
data_id
column_name
old_data
date
1
1
data
desc t1
2022-03-28 06:49:14
2
2
price
600
2022-03-28 11:34:46
3
1
price
4400
2022-03-28 09:15:54
Trigger (only check price column):
DELIMITER //
CREATE TRIGGER `log_old_data` BEFORE UPDATE ON `data`
INSERT INTO data_log
(
data_id,
old_data
)
VALUES
(
OLD.id,
OLD.price <- I need here a Select I think
);
END//
Since you have few columns, it may be simpler to do it "by hand" for every columns
DELIMITER //
CREATE TRIGGER `log_old_data` BEFORE UPDATE ON `data`
IF NEW.name != OLD.name THEN
INSERT INTO data_log (data_id, old_data) VALUES (OLD.id, OLD.name);
END IF;
IF NEW.data != OLD.data THEN
INSERT INTO data_log (data_id, old_data) VALUES (OLD.id, OLD.data);
END IF;
IF NEW.price != OLD.price THEN
INSERT INTO data_log (data_id, old_data) VALUES (OLD.id, OLD.price);
END IF;
END //
DELIMITER ;
PS: I did not test it, but it should work. If it doesn't, leave your mysql version to allow me to test on your version
For the SELECT part, since yo record every change on a separate table you only have to do a query on it
SELECT * FROM log_old_data WHERE `log_id` = 1 AND DATE(`date`) = '2022-03-27';
PS: Careful, DATE() on a WHERE condition may not be the perfect choice, it will not use indexes. I use generated columns to add index on date for this kind of case.
I have the following stored procedure that takes a key and a timestamp and updates the timestamp using the key if the key is already present, otherwise it inserts the key along with the accompanying timestamp value:
CREATE PROCEDURE `sp_AddUpdateTimestamp`(IN timestampKey VARCHAR(50), IN timestampValue TIMESTAMP)
BEGIN
IF(timestampKey IS NOT NULL) THEN
IF(timestampValue IS NOT NULL) THEN
IF EXISTS (SELECT Name FROM StoredTimestamps WHERE Name = timestampKey) THEN
UPDATE StoredTimestamps SET Timestamp = timestampValue WHERE Name = timestampKey;
ELSE
INSERT INTO StoredTimestamps (Name, Timestamp) VALUES (timestampKey, timestampValue);
END IF;
ELSE
IF EXISTS (SELECT Name FROM StoredTimestamps WHERE Name = timestampKey) THEN
UPDATE StoredTimestamps SET Timestamp = NOW() WHERE Name = timestampKey;
ELSE
INSERT INTO StoredTimestamps (Name, Timestamp) VALUES (timestampKey, NOW());
END IF;
END IF;
END IF;
END
To me, this is a lot of conditional logic and makes this procedure very verbose. Does anyone know a way this logic can be simplified?
You want INSERT ON DUPLICATE KEY UPDATE:
INSERT INTO StoredTimestamps (Name, Timestamp)
VALUES (timestampKey, COALESCE(timestampValue, NOW()) )
ON DUPLICATE KEY UPDATE Timestamp = VALUES(timestampValue);
The ON DUPLICATE KEY sets the Timestamp to the value being inserted, when the Name already exists.
For this to work, you need a unique index/constraint on Name:
ALTER TABLE StoredTimestamps ADD CONSTRAINT unq_StoredTimestamps_Name UNIQUE (Name);
In a stored procedure, I would be careful about naming conventions as well:
CREATE PROCEDURE usp_AddUpdateTimestamp (
IN in_timestampKey VARCHAR(50),
IN in_timestampValue TIMESTAMP)
)
BEGIN
INSERT INTO StoredTimestamps (Name, Timestamp)
VALUES (in_timestampKey, COALESCE(in_timestampValue, NOW()))
ON DUPLICATE KEY UPDATE Timestamp = VALUES(timestampValue)
END;
I prefer usp_ rather than sp_ due to my history of using SQL Server, where sp_ is discouraged because it is used for system stored procedures. That is simply a habit and not really relevant to MySQL.
I am running the following procedure in mysql:
create procedure addSavingAccount(id int(10), account_number varchar(10))
begin
insert into saving values(id,'savings', account_number, 0);
end //
However, when I try to call it, it gives me this error:
mysql> call addSavingAccount(103, 'B505');
ERROR 1136 (21S01): Column count doesn't match value count at row 1
I checked on anything that could be linked to it, including triggers. But everything seems like it should be working. Here is my list of triggers:
create trigger balance_change_saving after update on saving for each row
begin
if old.balance != new.balance
then
insert into balance_update_history values(null, 'saving', new.account_number, old.balance, new.balance,
(SELECT NOW()), (select USER()));
end if;
end//
create trigger balance_insert_saving after insert on saving for each row
begin
insert into balance_update_history values(null, 'saving', new.account_number, 0, new.balance, (select now()),
(select user()));
end //
create trigger balance_delete_saving after delete on saving for each row
begin
insert into balance_update_history values(null, null, null, old.balance, null,
(SELECT NOW()), (select USER()));
end //
And here is where I define the table:
create table if not exists saving(account_number varchar(10) , customer_id int(10), balance decimal(8,2), primary key(account_number));
I'd just really like to to figure this out.
There are three columns based on your table create statement, not four. (what is the last 0 in that insert?)
Also, in the procedure, it appears that your insert values are out-of-order relative to the table creation order? So you can either rearrange the insert values to match the table, OR specify the columns with the insert.
A nearly identical question was asked here using an IF statement, but he didn't get an actionable answer, just suggested to go here where no IF statements are being used. I've tried to write both an IF statement and a conditional statement using the latter link but I'm stuck (see below).
I want to be able to conditionally insert a row only if the previous insert attempt actually inserted a row (ROW_COUNT > 0). The previous insert could have been duplicate data, so I'm deliberately setting it's LAST_INSERT_ID to null so no subsequent child inserts can occur with that LAST_INSERT_ID. The SQL script is created by a C# script, so it would be very possible that the LAST_INSERT_ID is not pointing to where you'd expect.
Here's a very small example of the script generated code (there are ~3 million rows in the final database):
SET #Vendors_Vendor_ID = (SELECT vendor_ID FROM VENDORS WHERE vendorName = 'PCA');
INSERT IGNORE INTO PCBID (PCBID, PCBDrawing, AssemblyDrawing, PONumber, Vendors_Vendor_ID)
VALUES (11001, '10405', '41606', '091557.5', #Vendors_Vendor_ID);
SET #eventType_ID = (SELECT EVENTTYPE_ID FROM EVENTTYPES WHERE EVENTTYPE = 'Creation');
SET #USER = 'CTHOMAS';
INSERT IGNORE INTO EVENTS (PCBID, EVENTTYPE_ID, DATETIME, USER)
VALUES (11001, #eventType_ID, '2009-06-15T13:15:27', #USER);
SET #EVENT_ID = IF(ROW_COUNT() > 0, LAST_INSERT_ID(), null);
-- THIS DOES NOT WORK
SELECT IF(#EVENT_ID != null,
INSERT INTO EVENTDETAILS (EVENT_ID, ITEMNAME, ITEMVALUE)
VALUES (#EVENT_ID, 'Notes', 'WO#89574'),
null);
-- THIS DOESN'T WORK EITHER
INSERT INTO EVENTDETAILS (EVENT_ID, ITEMNAME, ITEMVALUE)
SELECT #EVENT_ID, 'Notes', 'WO#89574'
WHERE #EVENT_ID != null;
The PCBID table is not a problem for duplicate data, and the Events table has a composite unique key which prevents duplicate data by using INSERT IGNORE:
CREATE TABLE IF NOT EXISTS `uniqueTest`.`events` (
`Event_ID` INT(11) NOT NULL AUTO_INCREMENT ,
`PCBID` INT(11) NOT NULL ,
`EventType_ID` INT(11) NOT NULL ,
`DateTime` DATETIME NOT NULL ,
`User` VARCHAR(45) NOT NULL ,
PRIMARY KEY (`Event_ID`) ,
UNIQUE KEY `PDU_Index` (`PCBID`, `DateTime`, `User`),
The Problem:
I need to be able to do a conditional insert based on the previous insert attempt into the Events table, if it was ignored (because it's duplicate data), don't insert any child rows either. There's currently no way to make any of the EventDetail data unique, there could be multiple rows of legitimate data based on a given Event_ID.
There are four levels deeper possible below the Events table depending on what type of data it is, if the event data doesn't get inserted because it's duplicate data, no child data gets written either (because it'll be duplicate as well).
Your second try was nearly right. You've got to check of NULL values with IS NOT NULL. So use
INSERT INTO EVENTDETAILS (EVENT_ID, ITEMNAME, ITEMVALUE)
SELECT #EVENT_ID, 'Notes', 'WO#89574' FROM DUAL
WHERE #EVENT_ID IS NOT NULL; -- instead of !=
or
INSERT INTO EVENTDETAILS (EVENT_ID, ITEMNAME, ITEMVALUE)
SELECT t.* FROM (
SELECT #EVENT_ID, 'Notes', 'WO#89574'
) t
WHERE #EVENT_ID IS NOT NULL; -- instead of !=
The first one cannot work:
-- THIS DOES NOT WORK
SELECT IF(#EVENT_ID != null,
INSERT INTO EVENTDETAILS (EVENT_ID, ITEMNAME, ITEMVALUE) ...
because the syntax of IF is
IF(expr1,expr2,expr3)
If expr1 is TRUE (expr1 <> 0 and expr1 <> NULL) then IF() returns expr2; otherwise it returns expr3. IF() returns a numeric or string value, depending on the context in which it is used.
Conditional execution of statements is only possible in stored routines. The IF syntax of stored routines would allow something like
IF #EVENT_ID IS NOT NULL THEN
INSERT INTO EVENTDETAILS (EVENT_ID, ITEMNAME, ITEMVALUE) ...
END IF
You've got to distinguish those both syntax versions.
I have a part of my function mentioned below.
When I execute the function I get an error saying primary key violation for alarm_id
SELECT COUNT(DISTINCT exception_sub_type) INTO v_count FROM mdas.alarm_configuration;
IF v_count > 0 THEN
FOR i IN 1..v_count
LOOP
SELECT ((MAX(alarm_id))+1) INTO v_alarm_id FROM mdas.alarm_configuration;
RAISE NOTICE 'ALARM ID (%)',v_alarm_id;
INSERT INTO mdas.alarm_configuration
(alarm_id,
exception_sub_type,
exception_type,
from_range,
groups,
priority,
sub_group,
to_range,
org_unit_id,
status)
SELECT DISTINCT ON(exception_sub_type) v_alarm_id,
exception_sub_type,
exception_type,
from_range,
v_group_name,
priority,
v_subgroup_name,
to_range,
v_org_unit_id,
status
FROM mdas.alarm_configuration
WHERE groups = v_group_name AND mdas.alarm_configuration.org_unit_id = v_org_unit_id;
END LOOP;
END IF;
Note: This is only a part of the function. The function has three arguments v_org_unit_id, v_group_name and v_subgroup_name. My primary key is on alarm_id, which I want to increment by 1 and insert based on the MAX which is available in the table.
In your environment, when more than two client call the function at the same time, they will see the same alarm_id, so raise unique violated.
for exp :
SESSION A :
select max(id) into v_id from test;
insert into test values (v_id);
.. not end else.
SESSION B :
select max(id) into v_id from test; -- this v_id will same as session a.
insert into test values (v_id);
and then session A commit;
SESSION B will raise error.
ERROR: duplicate key value violates unique constraint "test_pkey"
DETAIL: Key (id)=(?) already exists.
You can use PostgreSQL serial deal this problem.
LIKE :
digoal=# create table test (id serial primary key, other_cols type ...);
CREATE TABLE
and not need to use max(id), you only need type other columns's values.
like
insert into test (other_cols,...) values (...);