MySQL Creating trigger on view - Error 1347 - mysql

I'm supposed to write a trigger that checks BEFORE INSERT whether the record already exists, and then take some actions. I'm stuck with this error returned on issuing an INSERT statement against Koncerty view:
Error Code: 1347. 'BazyLista3.Koncerty' is not BASE TABLE
Here's the code for view Koncerty:
CREATE VIEW Koncerty (`nazwa_klubu`, `adres_klubu`, `nazwa_zespolu`,
`ilosc_czlonkow_zespolu`, `data_wystepu`) AS
( SELECT `nazwa_klubu`, `kb`.`adres`, `nazwa_zespolu`, `zs`.`ilosc_czlonkow`,
`data_wystepu` FROM `Koncert` AS kc
INNER JOIN `Klub` AS kb ON `kc`.`nazwa_klubu` = `kb`.`nazwa`
INNER JOIN `Zespol` AS zs ON `kc`.`nazwa_zespolu` = `zs`.`nazwa` );
And my trigger, where I have this error:
DROP TRIGGER IF EXISTS `before_koncerty_insert`
DELIMITER $$
CREATE TRIGGER `before_koncerty_insert` BEFORE INSERT ON `Koncerty`
FOR EACH ROW
BEGIN
DECLARE i INT DEFAULT 0;
SELECT COUNT(*) INTO i FROM `Koncerty` WHERE
`nazwa_klubu` = NEW.`nazwa_klubu` AND
`adres_klubu` = NEW.`adres_klubu` AND
`nazwa_zespolu` = NEW.`nazwa_zespolu` AND
`ilosc_czlonkow_zespolu` = NEW.`ilosc_czlonkow_zespolu` AND
`data_wystepu` = NEW.`data_wystepu`;
IF i > 0 THEN
SIGNAL SQLSTATE '58005'
SET MESSAGE_TEXT = 'Blad! Taka krotka juz istnieje';
END IF;
END $$
DELIMITER ;
Tables
CREATE TABLE IF NOT EXISTS `Klub`
(
`nazwa` varchar(50) NOT NULL,
`adres` varchar(70) NOT NULL,
PRIMARY KEY (`nazwa`)
) ENGINE = InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `Zespol`
(
`nazwa` varchar(50) NOT NULL,
`ilosc_czlonkow` int(3) NOT NULL,
PRIMARY KEY (`nazwa`)
) ENGINE = InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `Koncert`
(
`nazwa_klubu` varchar(50) NOT NULL,
`nazwa_zespolu` varchar(50) NOT NULL,
`data_wystepu` datetime NOT NULL,
FOREIGN KEY (`nazwa_klubu`) REFERENCES `Klub`(`nazwa`),
FOREIGN KEY (`nazwa_zespolu`) REFERENCES `Zespol`(`nazwa`)
) ENGINE = InnoDB DEFAULT CHARSET=utf8;
How do I get around this issue?

MySQL does not support triggers on views.
The way to do this task is to make Koncerty a table without keys, in order to then operate on it with trigger actions. It all worked out just as I wanted.
Also, making UNIQUE CONSTRAINTS on records in tables does the job for the unique record, because it pops up an error when trying to add anything alike.

MySQL does not support triggers on views, from MySQL reference manual:
You cannot associate a trigger with a TEMPORARY table or a view.

The error might come if the view has used the database name in it. For example if a view has used a table as <dbname>.<tablename>:
create or replace view dealer_current_stage as
select
dsc.dealer_id,
dsc.new_stage current_stage,
dsc.change_date last_stage_change_date
from
dealer_current_stage_pre dcsp
left join
risk.dealer_stage_changes dsc ON dsc.change_date = dcsp.max_stage_change_date
and dsc.dealer_id = dcsp.dealer_id
group by dsc.dealer_id;
Here, risk is a different database and its table is being used. So better do not use the separate database and table, if you have to use it then consider the privileges, etc.

Related

Idempotent table and index creation with value insertion in MySQL

I have some SQL Server schema changes that I'm trying to convert to MySQL. I know about CREATE TABLE IF NOT EXISTS in MySQL. I don't think I can use that here.
What I want to do is create a table in MySQL, with an index, and then insert some values all as part of the "if not exists" predicate. This was what I came up with, though it doesn't seem to be working:
SET #actionRowCount = 0;
SELECT COUNT(*) INTO #actionRowCount
FROM information_schema.tables
WHERE table_name = 'Action'
LIMIT 1;
IF #actionRowCount = 0 THEN
CREATE TABLE Action
(
ActionNbr INT AUTO_INCREMENT,
Description NVARCHAR(256) NOT NULL,
CONSTRAINT PK_Action PRIMARY KEY(ActionNbr)
);
CREATE INDEX IX_Action_Description
ON Action(Description);
INSERT INTO Action
(Description)
VALUES
('Activate'),
('Deactivate'),
('Specified');
END IF
I can run it once, and it'll create the table, index, and values. If I run it a second time, I get an error: Table Action already exists. I would have thought that it wouldn't run at all if the table already exists.
I use this pattern a lot when bootstrapping a schema. How can I do this in MySQL?
In mysql compound statements can only be used within stored programs, which includes the if statement as well.
Therefore, one solution is to include your code within a stored procedure.
The other solution is to use the create table if not exists ... with the separate index creation included within the table definition and using insert ignore or insert ... select ... to avoidd inserting duplicate values.
Examples of options:
Option 1:
CREATE TABLE IF NOT EXISTS `Action` (
`ActionNbr` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`Description` VARCHAR(255) NOT NULL,
INDEX `IX_Action_Description` (`Description`)
) SELECT 'Activate' `Description`
UNION
SELECT 'Deactivate'
UNION
SELECT 'Specified';
Option 2:
DROP PROCEDURE IF EXISTS `sp_create_table_Action`;
DELIMITER //
CREATE PROCEDURE `sp_create_table_Action`()
BEGIN
IF NOT EXISTS(SELECT NULL
FROM `information_schema`.`TABLES` `ist`
WHERE `ist`.`table_schema` = DATABASE() AND
`ist`.`table_name` = 'Action') THEN
CREATE TABLE `Action` (
`ActionNbr` INT AUTO_INCREMENT,
`Description` NVARCHAR(255) NOT NULL,
CONSTRAINT `PK_Action` PRIMARY KEY (`ActionNbr`)
);
CREATE INDEX `IX_Action_Description`
ON `Action` (`Description`);
INSERT INTO `Action`
(`Description`)
VALUES
('Activate'),
('Deactivate'),
('Specified');
END IF;
END//
DELIMITER ;
CALL `sp_create_table_Action`;

Creating a trigger that pulls information from another table in phpMyAdmin

I am trying to create a fairly complicated Trigger and I'm not sure if it can be done on phpMyAdmin.
Right now I have this query that creates a table with all the information I need from it.
CREATE TABLE SeniorDB_Shipping
SELECT
SeniorDB_Invoice.ID_Order,
SeniorDB_Customer.MCT_Code,
SeniorDB_Customer.Customer_Name,
SeniorDB_Customer.Customer_Address,
SeniorDB_Customer.Customer_City,
SeniorDB_Customer.Customer_State,
SeniorDB_Invoice.Shipping_Company
FROM SeniorDB_Customer
Join SeniorDB_Invoice ON SeniorDB_Customer.MCT_Code = SeniorDB_Invoice.MCT_Code
As you can see in the image, when I run the query, it pulls in information from the tables above the information. I'm trying (and failing) to create a trigger that will do this same thing without having to create a brand new table every single time. All the other posts I have seen are similar in regards to creating a table instead of inserting to a table.
What the trigger does is, when I enter the ID_Order, the rest of the information will get pulled from the database.
This is the trigger I have so far:
delimiter ~
create trigger SeniorDB_Shipping before insert on SeniorDB_Shipping
for each row begin
set new.SeniorDB_Shipping.MCT_Code = new.SeniorDB_Customer.MCT_Code,;
set new.SeniorDB_Shipping.Customer_Name = new.SeniorDB_Customer.Customer_Name,;
set new.SeniorDB_Shipping.Customer_Address = new.SeniorDB_Customer.Customer_Address,;
set new.SeniorDB_Shipping.Customer_City = new.SeniorDB_Customer.Customer_City,;
set new.SeniorDB_Shipping.Customer_State = new.SeniorDB_Customer.Customer_State,;
set new.SeniorDB_Shipping.Shipping_Company = new.SeniorDB_Customer.Shipping_Company,;
end~
delimiter ;
I feel like I'm right there. I just can't link it to when I enter the ID_Order.
This is the page if you would like to see the databases: http://polonium.forest.usf.edu/~sngamwon/SeniorProject/SeniorDB_Order.php
Ok, so you'll need to run this once:
/* Create the table with a primary key */
create table `SeniorDB_Shipping` (
`id` INT unsigned AUTO_INCREMENT NOT NULL,
primary key(id),
`ID_Order` int NOT NULL,
`MCT_Code` int NOT NULL,
`Customer_Name` varchar(255) NOT NULL,
`Customer_Address` varchar(255) NOT NULL,
`Customer_City` varchar(255) NOT NULL,
`Customer_State` varchar(255) NOT NULL,
`Shipping_Company` varchar(255) NOT NULL
) ENGINE=MyISAM CHARACTER SET=utf8 COLLATE utf8_general_ci;
Then you can run
/* Insert statement */
INSERT INTO `SeniorDB_Shipping` (
`ID_Order`,
`MCT_Code`,
`Customer_Name`,
`Customer_Address`,
`Customer_City`,
`Customer_State`,
`Shipping_Company`
) SELECT
invoice.ID_Order,
customer.MCT_Code,
customer.Customer_Name,
customer.Customer_Address,
customer.Customer_City,
customer.Customer_State,
invoice.Shipping_Company
FROM
SeniorDB_Customer as customer
Join SeniorDB_Invoice as invoice
ON customer.MCT_Code = invoice.MCT_Code;
I've run this in my own PHPMyAdmin, so it works. But I obviously don't have your schema. Known issues:
This will populate SeniorDB_Shipping with ALL the data from your two tables each time. Modify the query as required to select only recent data if that's not what you want. If ID_Order is a primary key you could check that doesn't already exist.

MYSQL: Partitioning Table keeping id unique

We are using a table which has schema like following:-
CREATE TABLE `user_subscription` (
`ID` varchar(40) NOT NULL,
`COL1` varchar(40) NOT NULL,
`COL2` varchar(30) NOT NULL,
`COL3` datetime NOT NULL,
`COL4` datetime NOT NULL,
`ARCHIVE` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`)
)
Now we wanted to do partition on column ARCHIVE. ARCHIVE can have only 2 values 0 or 1 and so 2 partitions.
Actually in our case, we are using partitioning as a Archival process. To do partition, we need to make ARCHIVE column as a part of primary key. But the problem here is that 2 rows can have same ID with different ARCHIVE column value. Actually thats not the main problem for us as 2 rows will be in different partitions. Problem is when we will update the archive column value of one of them to other to move one of the row to archive partition, then it will not allow us to update the entry giving "Duplicate Error".
Can somebody help in this regard?
Unfortunately,
A UNIQUE INDEX (or a PRIMARY KEY) must include all columns in the table's partitioning function
and since MySQL does not support check constraints either, the only ugly workaround I can think of is enforcing the uniqueness manually though triggers:
CREATE TABLE t (
id INT NOT NULL,
archived TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id, archived), -- required by MySQL limitation on partitioning
)
PARTITION BY LIST(archived) (
PARTITION pActive VALUES IN (0),
PARTITION pArchived VALUES IN (1)
);
CREATE TRIGGER tInsert
BEFORE INSERT ON t FOR EACH ROW
CALL checkUnique(NEW.id);
CREATE TRIGGER tUpdate
BEFORE UPDATE ON t FOR EACH ROW
CALL checkUnique(NEW.id);
DELIMITER //
CREATE PROCEDURE checkUnique(pId INT)
BEGIN
DECLARE flag INT;
DECLARE message VARCHAR(50);
SELECT id INTO flag FROM t WHERE id = pId;
IF flag IS NOT NULL THEN
-- the below tries to mimic the error raised
-- by a regular UNIQUE constraint violation
SET message = CONCAT("Duplicate entry '", pId, "'");
SIGNAL SQLSTATE "23000" SET
MYSQL_ERRNO = 1062,
MESSAGE_TEXT = message,
COLUMN_NAME = "id";
END IF;
END //
(fiddle)
MySQL's limitations on partitioning being such a downer (in particular its lack of support for foreign keys), I would advise against using it altogether until the table grows so large that it becomes an actual concern.

MySQL trigger delete passing user ID

I need to create MySQL trigger that would log user ID on delete table row statement which must fit in one query, since I'm using PHP PDO. This is what I've come up so far:
I need the way to pass user ID in the delete query even though it is irrelevant to delete action to be performed:
Normally the query would look like this:
DELETE FROM mytable WHERE mytable.RowID = :rowID
If I could use multiple queries in my statement, I would do it like this:
SET #userID := :userID;
DELETE FROM mytable WHERE mytable.RowID = :rowID;
This way the variable #userID would be set before trigger event fires and it can use it. However since I need to squeeze my delete statement in one query, so I came up with this:
DELETE FROM mytable
WHERE CASE
WHEN #userID := :userID
THEN mytable.RowID = :rowID
ELSE mytable.RowID IS NULL
END
Just a note: RowID will never be null since it's the primary key. Now I have to create a delete trigger to log the user ID to the audit table, however I suppose that in this case trigger will be fired before the delete query itself which means that #userID variable will not be created? This was my idea of passing it as a value to the trigger.
I feel like I'm close to the solution, but this issue is a blocker. How to pass user ID value to the trigger without having multiple queries in the statement? Any thoughts, suggestions?
You can use NEW / OLD mysql trigger extensions. Reference: http://dev.mysql.com/doc/refman/5.0/en/trigger-syntax.html
Here is a sample code :
drop table `project`;
drop table `projectDEL`;
CREATE TABLE `project` (
`proj_id` int(11) NOT NULL AUTO_INCREMENT,
`proj_name` varchar(30) NOT NULL,
`Proj_Type` varchar(30) NOT NULL,
PRIMARY KEY (`proj_id`)
);
CREATE TABLE `projectDEL` (
`proj_id` int(11) NOT NULL AUTO_INCREMENT,
`proj_name` varchar(30) NOT NULL,
`Proj_Type` varchar(30) NOT NULL,
PRIMARY KEY (`proj_id`)
);
INSERT INTO `project` (`proj_id`, `proj_name`, `Proj_Type`) VALUES
(1, 'admin1', 'admin1'),
(2, 'admin2', 'admin2');
delimiter $
CREATE TRIGGER `uProjectDelete` BEFORE DELETE ON project
FOR EACH ROW BEGIN
INSERT INTO projectDEL SELECT * FROM project WHERE proj_id = OLD.proj_id;
END;$
delimiter ;
DELETE FROM project WHERE proj_id = 1;
SELECT * FROM project;
SELECT * FROM projectDEL;

MySQL error in a procedure #1351 - View's SELECT contains a variable or parameter

PhpMyAdmin doesn't accept the parameters "id_jugador_IN" inside the procedure.
This is the procedure we're executing:
SET GLOBAL event_scheduler = 1;
use traviandatabase;
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_monitoritzacio_jugador $$
CREATE PROCEDURE sp_monitoritzacio_jugador(IN id_jugador_IN INT(10))
BEGIN
CREATE OR REPLACE
VIEW dades_ususari AS
SELECT j.nom AS jugador, j.sexe AS sexe, j.edat AS edat, j.correu AS correu, a.nom AS alianca, p.nom AS pais, c.nom_ciutat AS ciutat
FROM jugador AS j
JOIN alianca AS a
JOIN pais AS p
JOIN ciutat AS c
ON j.id_jugador = id_jugador_IN
GROUP BY id_jugador_IN;
END $$
DELIMITER ;
CALL sp_monitoritzacio_jugador(1);
And this is the table "jugador":
DROP TABLE IF EXISTS `jugador`;
CREATE TABLE `jugador` (
`id_jugador` int(10) NOT NULL AUTO_INCREMENT,
`id_raca` int(10) NOT NULL,
`id_pais` int(10) NOT NULL,
`id_alianca` int(10) DEFAULT '0',
`nom` varchar(20) NOT NULL,
`sexe` enum('Home','Dona') NOT NULL,
`edat` int(10) NOT NULL,
`correu` varchar(20) NOT NULL,
PRIMARY KEY (`id_jugador`),
KEY `jugador-alianca` (`id_alianca`),
KEY `id_pais` (`id_pais`),
KEY `id_raca` (`id_raca`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
So it doesn't stop displaying the same error:
MySQL said: 1351 - View's SELECT contains a variable or parameter
As you see - view cannot use variables, it is a limitation. You are trying to create a view with exact WHERE condition; construct the CREATE VIEW statement text firstly, then use prepared statements to execute the statement, it will help you. But, do you really need it, to create, create and create new view?
EDIT: This is the content of the reference in the comment.
Olexandr Melnyk A simple workaround for MySQL's limitation on local variables usage in views is to use a function, which returns variable's value:
create function book_subject
returns varchar(64) as
return #book_subject;
create view thematical_books as
select title
, author
from books
where subject = book_subject();