Mysql/adventureworks database/trigger question - mysql

Good afternoon I have been having some trouble trying to understand this question in class.The purpose of this assignment is to add parameters around data entry through triggers that are launched when specific conditions are met.
Due to warehousing limitations, inventory over 800 units needs to be sent to an external storage site and tracked separately. You have been asked to monitor when an update will exceed this boundary so it can be addressed in production meetings.
Write a trigger titled "tgrExcessInventory" for the Production.ProductInventory table to ensure the quantity can never exceed 800 units. This is step one.
Modify the trigger created in step 1 to execute its check code only if the Quantity column is updated. I successfully created the trigger but I am having trouble understanding how to modify it? This is what I have so far. I Have seen a few other post on here similar to this question, but I haven't seen any with the modification done to it. I feel it'something small im missing. From my understanding I need to write an alter statement?
CREATE TRIGGER tgrExcessInventory
on Production.ProductInventory
FOR UPDATE
AS
IF EXISTS
(SELECT 'True'
FROM Inserted i
JOIN Deleted d
ON i.productID = d.ProductID
AND i.locationID = d.LocationID
WHERE (d.quantity + i.quantity) >= 800 OR
i.quantity >=800
)Begin
RAISERROR('Cant increase supply where units would be over 800
units',16,1)
ROLLBACK TRAN
END
Then I did the alter function
ALTER TRIGGER [Production].[tgrExcessInventory]
on [Production].[ProductInventory]
FOR UPDATE
AS
IF EXISTS
(SELECT 'True'
FROM Inserted I
JOIN Deleted D
ON i.Quantity = d.quantity
AND i.Quantity = d.Quantity
WHERE (d.quantity + i.quantity) >= 800 OR
i.quantity >=800
)Begin
RAISERROR('Cant increase supply where units would be over 800 units',16,1)
ROLLBACK TRAN
END
Seems to work? I believe i did this right any tips would be appreciated thanks for your time

If you read here: https://dba.stackexchange.com/questions/193219/alter-procedure-in-mysql
regarding ALTER PROCEDURE
This statement can be used to change the characteristics of a stored procedure. More than one change may be specified in an ALTER PROCEDURE statement. However, you cannot change the parameters or body of a stored procedure using this statement; to make such changes, you must drop and re-create the procedure using DROP PROCEDURE and CREATE PROCEDURE.
You can easily alter a procedure using an alter statement if it is a minor change. If you want to make bigger changes, you should use SHOW CREATE PROCEDURE tgrExcessInventory then make your changes, drop the existing procedure DROP PROCEDURE IF EXIST tgrExcessInventory and run the CREATE PROCEDURE statement with your changes

Related

MySQL stored procedure on big table eats server disk space

I have inherited a MySQL InnoDB table with around 500 million rows. The table has IP numbers and the name of the ISP to which that number belongs, both as strings.
Sometimes, I need to update the name of an ISP to a new value, after company changes such as mergers or rebranding. But, because the table is so big, a simple UPDATE...WHERE statement doesn't work - The query usually times out, or the box runs out of memory.
So, I have written a stored procedure which uses a cursor to try and make the change one record at a time. When I run the procedure on a small sample table, it works perfectly. But, when I try to run it against the whole 500 million row table in production, I can see a temporary table gets created (because a /tmp/xxx.MYI and /tmp/xxx.MYD file appear). The temporary table file keeps growing in size until it uses all available disk space on the box (around 40 GB).
I'm not sure why this temporary table is necessary. Is the server trying to maintain some kind of rollback state? My real question is, can I change the stored procedure such that the temporary table is not created? I don't really care if some, but not all of the records get updated - I can easily add some reporting and just keep running the proc until no records are altered.
At this time, architecture changes are not really an option – I can't change the structure of the table, for example.
Thanks in advance for any help.
David
This is my stored proc;
DELIMITER $$
DROP PROCEDURE IF EXISTS update_isp;
CREATE PROCEDURE update_isp()
BEGIN
DECLARE v_finished INT DEFAULT 0;
DECLARE v_num VARCHAR(255) DEFAULT "";
DECLARE v_isp VARCHAR(255) DEFAULT "";
DECLARE ip_cursor CURSOR FOR
SELECT ip_number, isp FROM ips;
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
OPEN ip_cursor;
get_ip: LOOP
IF v_finished = 1 THEN
LEAVE get_ip;
END IF;
FETCH ip_cursor INTO v_num, v_isp;
IF v_isp = 'old name' THEN
UPDATE ips SET isp = 'new name' WHERE ip_number = v_num;
END IF;
END LOOP get_ip;
CLOSE ip_cursor;
END$$
DELIMITER ;
CALL update_isp();
I have also tried wrapping the update statement in a transaction. It didn't make any difference.
[EDIT] My assumption below, that a simple counting procedure does not create a temporary table, was wrong. The temporary table is still created, but it grows more slowly and the box does not run out of disk space before the procedure completes.
So the problem seems to be that any use of a cursor in a stored procedure results in a temporary table being created. I have no idea why, or if there is any way to prevent this.
If your update is essentially:
UPDATE ips
SET isp = 'new name'
WHERE isp = OLDNAME;
I am guessing that this update -- without the cursor -- will work better if you have an index on isp(isp):
create index idx_isp_isp on isp(isp);
Your original query should be fine once this index is created. There should be no performance issue updating a single row even in a very large table. The issue is in all likelihood finding the row, not updating it.
I don't think there is a solution to this problem.
From this page; http://spec-zone.ru/mysql/5.7/restrictions_cursor-restrictions.html
In MySQL, a server-side cursor is materialized into an internal
temporary table. Initially, this is a MEMORY table, but is converted
to a MyISAM table when its size exceeds the minimum value of the
max_heap_table_size and tmp_table_size system variables.
I misunderstood how cursors work. I assumed that my cursor functioned as a pointer to the underlying table. But, it seems MySQL must build the full result set first, and then give you a pointer to that. So, I don't really understand the benefits of cursors in MySQL. Thanks to everyone who tried to help.
David
If the table has some numerical index also you can specify a
WHERE myindex > 123 AND myindex < 456
in your update query and do that for a couple of intevals (with a loop for example) until the whole table is covered.
(sorry, my rep is too low to ask in the comment section, so I'll just post my guess-answer here to be able to comment on)
You could try to fake a numerical index with
SELECT ROW_NUMBER() as n, thetable.* FROM thetable ORDER BY oneofyourcolumns;
and then try what I suggested above.

Sqlserver standard trigger order

I have database partially created and edited by an external CRM where certain tables have multiple (at most 2) after triggers on them. This is due to 1 trigger being auto generated by the CRM (over which I have limited control) and the other containing my code.
The CRM trigger generates the primary key for the datarow inserted. My trigger needs to access that primary key in order to write it to another table as foreign key. I use
Select #id=max(id) from mytable
since Scope_Identity did not produce the desired result somehow.
This worked until I let the CRM recreate the table and its own trigger. The maximum id of that table selected by my trigger seemed to always be actual_id - 1.
When I altered my trigger using the same code it has always had the procedure worked again.
My question is:
Does SQL server (I am using SQL Server 2008) set its trigger order by creation time?
And:
Is
sp_settriggerorder #triggername='mycustomtrigger', #order='Last', #stmttype='INSERT'
going to change this permanently or do I have to call that procedure again, every time the CRM recreates its trigger? (using DROP and CREATE, not ALTER)
Hope the answers to that will help someone looking at the same issue.
Regards
It's not documented, but I believe that the LAST setting will stay with a trigger, provided it is not modified. (Contrariwise, it is documented that a trigger will lose this setting if it is modified). However, it seems to work:
create table T (ID int not null)
go
create trigger T_T1 on T
after insert
as
RAISERROR('T1',10,1) WITH NOWAIT
go
create trigger T_T2 on T
after insert
as
RAISERROR('T2',10,1) WITH NOWAIT
go
create trigger T_T3 on T
after insert
as
RAISERROR('T3',10,1) WITH NOWAIT
go
insert into T(ID) values (1)
go
sp_settriggerorder 'T_T2','Last','INSERT'
go
insert into T(ID) values (2)
go
drop trigger T_T1
go
create trigger T_T1 on T
after insert
as
RAISERROR('T1',10,1) WITH NOWAIT
go
insert into T(ID) values (3)
Results:
T1
T2
T3
(1 row(s) affected)
T1
T3
T2
(1 row(s) affected)
T3
T1
T2
(1 row(s) affected)
As to your first question, however:
Does SQL server (I am using SQL Server 2008) set its trigger order by creation time?
It also appears to, but I would not rely on that. sp_settriggerorder is the only place where any ordering is documented.
Finally, as mentioned in my comment, I wouldn't rely on your current Select #id=max(id) from mytable method - it could be broken for a number of reasons, but the most important is that a trigger is fired once per method, and may fire in response to multiple rows, so you ought to write triggers to use the inserted pseudo-table instead (and expect it to contain 0, 1 or multiple rows).

How to sync values in a table column in mysql trigger

I need to sync values in a table column in mysql trigger while having the same value in another column. Here is an example of my table:
id___MP____sweek
1____2_____1
2____2_____1
3____1_____2
4____1_____2
5____3_____3
6____3_____3
If a user changes, for example, MP in the first row (id=1) from 2 to 4, then the value of MP with the same sweek has to be changed (e.g., id=2, MP becomes also 4).
I wrote a BEFORE UPDATE tigger that does not work:
USE moodle;
DELIMITER $$
CREATE TRIGGER trigger_course_minpostUPD BEFORE UPDATE ON moodle.mdl_course_sections FOR EACH ROW
BEGIN
IF NEW.MP <> OLD.MP THEN
BEGIN
SET #A=NEW.MP;
SET NEW.MP = #A
WHERE OLD.sweek=NEW.sweek;
END;
END IF;
END$$
DELIMITER ;
From within a MySQL trigger you are not able to affect other rows on the same table.
You would want to say something like:
UPDATE my_table SET MP=NEW.MP WHERE sweek = NEW.sweek
But - sorry - no go.
There are hack around this -- and ugly ones, too.
If your table is MyISAM, you can wrap it up with a MERGE table, and act on the MERGE table instead (MySQL doesn't realize at that point you're actually hacking around it).
However, using MyISAM as a storage engine may not be a good thing -- today's focus is on InnoDB, a much more sophisticated engine.
Another trick is to try and use the FEDERATED engine. See relevant post by Roland Bouman. Again, this is a dirty hack.
I would probably let the application do the thing within the same transaction.

MySQL: trigger with clause "instead of update"

I'm trying to create a database with history in mind (experience shows you'll have to do this one day or another).
I've asked here database-design-how-to-handle-the-archive-problem but there's no better anser than the link here.
My problem is about where to do the code and technically, how (MySQL gives me headaches). First I've started doing this in Php: before doing any insert, duplicate the record mark it as "obsolete" then modify the record.
But there's a dependency problem (manytomany and manytoone associations must be updated as well) which implies coding (one way or another) all the dependancies and updates that come with the tables (which is not acceptable).
So I'm thinking about doing all the work on the database server side. This would greatly simplify my Php code.
The problem is that I have to "archive" the current record before modifying it. To do so, the code must be in a trigger "before update".
Here's my code:
DELIMITER ;;
DROP TRIGGER IF EXISTS produit_trigger_update_before;
CREATE TRIGGER produit_trigger_update_before
BEFORE UPDATE ON produit
FOR EACH ROW BEGIN
/* */
INSERT INTO produit SET
id_origine = OLD.id_origine,
date_v_creation = OLD.date_v_creation,
date_v_start = OLD.date_v_debut,
date_v_end = NOW(),
...
last_record = OLD.last_record;
/* Dependancies : */
SET #last=LAST_INSERT_ID();
UPDATE categorie_produit SET id_produit=#last
WHERE id_produit = OLD.id;
UPDATE produit_attribut SET id_produit=#last
WHERE id_produit = OLD.id;
END;;
DELIMITER ;;
If I get this code working, all my problems are gone. But damn it, it's not working:
mysql> update produit set importance=3;
ERROR 1442 (HY000): Can't update table 'produit' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
mysql> Bye
In this page there's a working sample, which uses INSTEAD OF UPDATE clause in the trigger. MySQL doesn't seem to support this.
So my question is both conceptual (= have you any other "principle" that could work) and/or technical (= can you make this trigger work).
If I get this code working, all my problems are gone. But damn it, it's not working:
As a rule you can't have a trigger on table A trigger inserts into table A - since that could cause an endless loop. (Trigger mutation in Oracle terms)
Personally I would not do this using triggers. Triggers can do "audit logging" - but this is not what you want here.
I suggest you solve it programatically - either with a PHP function or a MySQL stored procedure (whatever your preference) that you call something like "ModifyProduit".
The code would then do basically what you have the trigger above do. (It might be easier to just have the code set date_v_end on the current row, and then insert a completly new row. That way you don't have to mess around with updating your referenced tables)
you can do history of a table with an auxiliary table like this (i've done this for many tables on mysql and the speed is very good):
table produit_history has the same structure as produit + 2 additional columns: "history_start DATETIME NOT NULL" and "history_stop DATETIME DEFAULT NULL".
there are 3 triggers on produit table:
AFTER INSERT: in this trigger there is a simple insert into produit_history of the same data with history_start = NOW() and history_stop = NULL (NULL means the current row is valid)
AFTER UPDATE: this trigger performs two queries. The first is un update like this:
UPDATE produit_history set history_stop = NOW() WHERE id_origine = OLD.id_origine AND history_stop IS NULL;
The second query is an insert identical to the one in the AFTER INSERT trigger.
AFTER DELETE: this triggers there is a simple update which is identical to the one in the AFTER UPDATE.
You can then query this history table and obtain snapshots at whatever time you're interested in with the following where condition:
WHERE (history_start &lt= "interesting_time" AND (history_stop IS NULL OR history_stop &gt "interesting_time"))

MySQL trigger : is it possible to delete rows if table become too large?

When inserting a new row in a table T, I would like to check if the table is larger than a certain threshold, and if it is the case delete the oldest record (creating some kind of FIFO in the end).
I thought I could simply make a trigger, but apparently MySQL doesn't allow the modification of the table on which we are actually inserting :
Code: 1442 Msg: Can't update table 'amoreAgentTST01' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
Here is the trigger I tried :
Delimiter $$
CREATE TRIGGER test
AFTER INSERT ON amoreAgentTST01
FOR EACH ROW
BEGIN
DECLARE table_size INTEGER;
DECLARE new_row_size INTEGER;
DECLARE threshold INTEGER;
DECLARE max_update_time TIMESTAMP;
SELECT SUM(OCTET_LENGTH(data)) INTO table_size FROM amoreAgentTST01;
SELECT OCTET_LENGTH(NEW.data) INTO new_row_size;
SELECT 500000 INTO threshold;
select max(updatetime) INTO max_update_time from amoreAgentTST01;
IF (table_size+new_row_size) > threshold THEN
DELETE FROM amoreAgentTST01 WHERE max_update_time = updatetime; -- and check if not current
END IF;
END$$
delimiter ;
Do you have any idea on how to do this within the database ?
Or it is clearly something to be done in my program ?
Ideally you should have a dedicated archive strategy in a separate process that runs at off-peak times.
You could implement this either as a scheduled stored procedure (yuck) or an additional background worker thread within your application server, or a totally separate application service. This would be a good place to put other regular housekeeping jobs.
This has a few benefits. Apart from avoiding the trigger issue you're seeing, you should consider the performance implications of anything happening in a trigger. If you do many inserts, that trigger will do that work and effectively half the performance, not to mention the lock contention that will arise as other processes try to access the same table.
A separate process that does housekeeping work minimises lock contention, and allows the work to be carried out as a high-performance bulk operation, in a transaction.
One last thing - you should possibly consider archiving records to another table or database, rather than deleting them.