MySQL insert adds rows with id's that shouldn't exist - mysql

I'm facing something weird here, i have a database table from which i select certain rows, in total ~43.000, these fetched rows contain a field called cns_id (note, this is not the primary key/auto increment field), and are inserted into a diffrent table.
Although everything seems to run fine, each time i end up with 94 inserted rows that have a cns_id of 2147483647, an id which doesn't even exist inside the table from which i fetch the data.
Below is the stored procedure i'm using, although this issue also happens when i perform the same kind of query with Eloquent.
DELIMITER //
CREATE PROCEDURE FillCnsGroupWithRates
(
IN groupId INT(10)
)
BEGIN
SET #var1 = groupId;
INSERT INTO
cns_group_rates
(
cns_id,
other_cns_id,
ppc,
pps,
max_price,
notes,
cns_group_id,
created_at,
updated_at
)
SELECT
cns_id,
other_cns_id,
ppc,
pps,
max_price,
notes,
#var1,
now(),
now()
FROM
cns_rates
WHERE
cns_id NOT LIKE '319%'
AND
client_id IS NULL
AND
subordinates IS NULL
AND
valid_from IS NULL;
END //
DELIMITER ;

Related

MySQL create trigger for logging table

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.

This Delete statement in a MySql stored procedure does not work

I have this stored procedure to store errors in an Elmah table:
CREATE DEFINER=`b4d7eb33be6399`#`%` PROCEDURE `elmah_LogError`(
IN ErrorId CHAR(36),
IN Application varchar(60),
IN Host VARCHAR(30),
IN Type VARCHAR(100),
IN Source VARCHAR(60),
IN Message VARCHAR(500),
IN User VARCHAR(50),
IN AllXml TEXT,
IN StatusCode INT(10),
IN TimeUtc DATETIME
)
MODIFIES SQL DATA
BEGIN
INSERT INTO `elmah_error` (
`ErrorId`,
`Application`,
`Host`,
`Type`,
`Source`,
`Message`,
`User`,
`AllXml`,
`StatusCode`,
`TimeUtc`
) VALUES (
ErrorId,
Application,
Host,
Type,
Source,
Message,
User,
AllXml,
StatusCode,
TimeUtc
);
DELETE FROM `elmah_error` WHERE ErrorId NOT IN (
SELECT ErrorId
FROM (
SELECT ErrorId
FROM `elmah_error`
ORDER BY TimeUtc DESC
LIMIT 100
) x
);
END
I added the DELETE part to keep 100 last records only. The problem is that it does nothing. However, if I take this DELETE code alone and run it in a query window inside MySql Workbench, it correctly deletes records after the 100 I want to keep. Strange no?
Put backticks on column names:
DELETE FROM `elmah_error` WHERE `ErrorId` NOT IN (
SELECT `ErrorId`
FROM (
SELECT `ErrorId`
FROM `elmah_error`
ORDER BY `TimeUtc` DESC
LIMIT 100
) x
);
Otherwhise they are threated as variables (because of same names) within procedure body.
I had a similar issue where I was trying to delete records older than x days (like every example out there). The problem was TimeUtc was also a parameter thus I needed to reference the table: WHERE elmah_error.TimeUtc < (NOW() - INTERVAL 3 MONTH);
Probably the same issue with ErrorId.

Conditional insert based on LAST_INSERT_ID

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.

Multi-row action for Instead of trigger SQL Server

Scenario/background:
I am trying to create a table of "Tests". For purposes of this question my table will only have 5 columns defined as follows:
CREATE TABLE TestTable
(
Id UNIQUEIDENTIFIER DEFAULT NEWID() NOT NULL,
Name VARCHAR(75) NOT NULL,
DateRequested DATETIME NOT NULL DEFAULT GETDATE(),
TestYear AS YEAR(DateRequested) PERSISTED NOT NULL, -- computed column that shows the year the test was requested. I want to persist this column so I can index on it if need be.
TestNumber CHAR(4) NOT NULL DEFAULT '0000', -- need this to auto increment but also needs to reset for the first test of the year.
CONSTRAINT TestTablePK PRIMARY KEY(Id)
);
GO
My requirement is that I want the 'TestNumber' to "auto-increment" based on the year. For example:
GUID, Test 1 in Old Yr, 2013-01-01 05:00:00.000, 2013, 0001
GUID, Test 2 in Old Yr, 2013-12-25 11:00:00.000, 2013, 0002
GUID, Test 3 in Old Yr, 2013-12-26 09:00:00.000, 2013, 0003
...., ................, ......................., ...., N
GUID, Test N in Old Yr, 2013-12-31 09:00:00.000, 2013, N+1
GUID, Test 1 in New Yr, 2014-01-01 11:00:00.000, 2014, 0001 <-- reset to 1
I was thinking that it would be an auto-increment column but how would I reset it based on this being the first test of a new year? So my incorrect solution thus far has been an "instead of insert" trigger defined as follows:
CREATE TRIGGER InsteadOfInsertTrigger ON dbo.TestTable
INSTEAD OF INSERT AS
BEGIN
-- Get the year of the test request being inserted from the pseudo-insert table.
DECLARE #TestYear INT;
SET #TestYear = (SELECT YEAR(DateRequested) FROM inserted);
-- Grab the maximum TestNumber from TestTable based on the year
-- that we are inserting a record for.
DECLARE #MaxTestNumber INT;
SET #MaxTestNumber = (SELECT MAX(TestNumber) FROM dbo.TestTable WHERE TestYear = #TestYear);
-- If this is the first test of the year being inserted it is a special case
IF #MaxTestNumber IS NULL
BEGIN
SET #MaxTestNumber = 0;
END;
-- Here we take the MaxTestNumber, add 1 to it, and then pad it with
-- the appropriate number of zero's in front of it
DECLARE #TestNumber VARCHAR(4);
SET #TestNumber = (SELECT RIGHT('0000' + CAST((#MaxTestNumber + 1) AS VARCHAR(4)), 4));
INSERT INTO dbo.TestTable(Name, DateRequested, TestNumber)
SELECT Name, DateRequested, #TestNumber FROM inserted;
END;
GO
Now here is some DML showing the trigger in action:
INSERT INTO TestTable(Name, DateRequested)
VALUES('Some Test', '05-05-2013');
INSERT INTO TestTable(Name, DateRequested)
VALUES('Some Other Test', '12-25-2013');
INSERT INTO TestTable(Name, DateRequested)
VALUES('Blah Blah', '12-31-2013');
INSERT INTO TestTable(Name, DateRequested)
VALUES('Foo', '01-01-2014');
SELECT * FROM TestTable ORDER BY TestYear ASC, TestNumber ASC;
So as you can see my trigger works for single row inserts but a keen eye will be able to tell it will not work for multi-row inserts.
CREATE TABLE TempTestTable
(
Name VARCHAR(75) NOT NULL,
DateRequested DATETIME NOT NULL DEFAULT GETDATE()
);
GO
INSERT INTO TempTestTable(Name, DateRequested)
VALUES('Test1', '01-01-2012');
INSERT INTO TempTestTable(Name, DateRequested)
VALUES('Test2', '12-25-2012');
INSERT INTO TempTestTable(Name, DateRequested)
VALUES('Test3', '01-01-2013');
INSERT INTO TempTestTable(Name, DateRequested)
VALUES('Test4', '01-01-2014');
-- This doesnt work because it is a multi-row insert.
INSERT INTO TestTable(Name, DateRequested)
SELECT Name, DateRequested FROM TempTestTable;
My Question
I realize that I can probably handle this with stored procedures and force users to use stored procedures when updating the tables but I want to be extra careful and prevent sysadmins from being able to do direct inserts to the table with an incorrect 'TestNumber'.
So StackOverflow, my question is how can I achieve this? Would I use a cursor inside of my InsteadOfInsertTrigger? I am looking for alternatives.
Not the neatest thing I'll ever write, but seems to do the job:
CREATE TRIGGER InsteadOfInsertTrigger ON dbo.TestTable
INSTEAD OF INSERT AS
BEGIN
;With Years as (
select i.TestYear,
COALESCE(MAX(tt.TestNumber),0) as YMax
from inserted i left join TestTable tt
on i.TestYear = tt.TestYear
group by i.TestYear
), Numbered as (
select i.ID,i.Name,i.DateRequested,
RIGHT('000' + CONVERT(varchar(4),
ROW_NUMBER() OVER (PARTITION BY i.TestYear
ORDER BY i.DateRequested,i.Id) + YMax)
,4) as TestNumber
from inserted i
inner join
Years y
on
i.TestYear = y.TestYear
)
insert into TestTable (Id,Name,DateRequested,TestNumber)
select Id,Name,DateRequested,TestNumber from Numbered;
END;
The first CTE (Years) finds the highest used number for each year that's of interest. The second CTE (Numbered) then uses those values to offset a ROW_NUMBER() that's being assessed over all rows in inserted. I picked the ORDER BY columns for the ROW_NUMBER() so that it's as deterministic as possible.
(I was confused about one thing for a while, but it turns out that I can use TestYear from inserted rather than having to repeat the YEAR(DateRequested) formula)

mysql triggers simulating assertions

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...