How to convert mysql/mariadb row in triggers (NEW/OLD) into json - mysql

how i can convert a row inside a mysql/mariadb trigger into an json object with new JSON features?
BEGIN
CALL my_audit_insert(tableName, id, ... JSON_OBJECT(NEW) ...);
END
Is there any possibility to get programatically columns of NEW or OLD?
First Try - Create a Statement
Idea is to get colums from system tables and get each value from NEW/OLD programatically
BEGIN
SET #s = 'SELECT NEW.? INTO #result';
PREPARE stmt FROM #s;
SET #a = 'id';
EXECUTE stmt USING #a;
CALL audit_insert(NEW.id, 'pages', JSON_ARRAY(result));
END
(1336): Dynamic SQL is not allowed in stored function or trigger
Second Idea - Select the row via PrimaryKey as JSON_Object in after-triggers
procedure spGetJson from https://stackoverflow.com/a/35957518/7080961
DROP PROCEDURE IF EXISTS `spGetJson`;
DELIMITER //
CREATE DEFINER=`root`#`%` PROCEDURE `spGetJson`(pTableName varchar(45), pId int, out pJson JSON)
begin
select group_concat(concat("'", COLUMN_NAME, "', ", COLUMN_NAME) separator ',')
into #cols
from information_schema.columns
where TABLE_NAME = pTableName and TABLE_SCHEMA = database();
set #q = concat('select json_object(', #cols, ') INTO #a from ', pTableName);
if pId is not null then
set #q = concat(#q, ' where id = ', pId);
end if;
set #q = concat(#q, ';');
prepare statement from #q;
execute statement;
deallocate prepare statement;
SET pJson = #a;
end//
DELIMITER;
After Insert Trigger:
BEGIN
CALL spGetJson('pages', NEW.id, #a);
CALL audit_insert(NEW.id, 'pages', #a);
END
same: (1336): Dynamic SQL is not allowed in stored function or trigger
Conclusion:
have to wait for this feature: https://bugs.mysql.com/bug.php?id=89366
or switch to postresql

Related

Extract data from multiple tables (mysql)

My problem: I have a few tables in mysql database with column "URL". I want to extract all of URLs to text file or another table.
Is it good way to go?
DELIMITER $$
CREATE or replace PROCEDURE link()
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE tablename varchar(1000) DEFAULT "";
DECLARE link_tables
CURSOR for
SELECT table_name FROM information_schema.columns WHERE table_name like '__baza%' and COLUMN_NAME like 'url';
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
OPEN link_tables;
getTable: LOOP
fetch link_tables into tablename;
IF finished = 1 THEN s
LEAVE getTable;
END IF;
select url from tablename into ... <<<< is it good idea? what to do next?
END LOOP getTable;
close link_tables;
END$$
DELIMITER ;
CALL link();
CREATE PROCEDURE link_urls (prefix VARCHAR(64))
BEGIN
SELECT GROUP_CONCAT( CONCAT( 'SELECT url FROM ', table_name) SEPARATOR ' UNION ')
INTO #sql
FROM information_schema.columns
WHERE table_name like CONCAT(prefix, '%');
SET #sql = CONCAT( 'CREATE TABLE urls ', #sql );
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END
fiddle

Passing a column name as parameter to a stored procedure in mySQL

I'm creating some stored procedures to manage my DB.
In particular, i want to create a stored procedore to edit a column, of a specific row, but i want to do it dinamically, passing the column name as an argument.
That's what i want to do
CREATE PROCEDURE myDB.edit_myTable(
IN key CHAR(16),
IN col VARCHAR(100),
new_value VARCHAR(200)
)
UPDATE myDB.myTable SET col = new_value
Using the parameter keyi find the specific row in myTablethat i want to edit, and i want to use the parameter col to edit just the column that i want.
I've already tried using CONCATE()or defining local variables, as i read on other topic, but i haven't find a solution.
Any help?
You would need to use dynamic SQL :
DELIMITER //
CREATE PROCEDURE myDB.edit_myTable(
IN key CHAR(16),
IN col VARCHAR(100),
new_value VARCHAR(200)
)
BEGIN
SET #s = CONCAT(
'UPDATE myDB.myTable SET `',
col, '` = ', QUOTE(new_value),
' WHERE key = ', QUOTE(key)
);
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
//
DELIMITER;
Please note that, as commented by Paul Spiegel, using a variable for column name creates a risk of SQL injection. One solution for improve security would be to make sure that the input col does exists in the target table, using MySQL information schema :
DELIMITER //
CREATE PROCEDURE myDB.edit_myTable(
IN key CHAR(16),
IN col VARCHAR(100),
new_value VARCHAR(200)
)
BEGIN
DECLARE col_exists INT;
SELECT COUNT(*) INTO col_exists
FROM information_schema.COLUMNS
WHERE TABLENAME = 'mytable' AND COLUMN_NAME = col;
IF (col_exists != 1) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = CONCAT('Column ', col, ' does not exist in table mytable');
END IF;
SET #s = CONCAT(
'UPDATE myDB.myTable SET `',
col, '` = ', QUOTE(new_value),
' WHERE key = ', QUOTE(key)
);
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
//
DELIMITER;

how to set value to output parameter in dynamic sql procedure?

I have tried with following code. But it shows error msg like this undeclared variable :nt.
CREATE DEFINER=`root`#`localhost` PROCEDURE `get_ntime`(in tb varchar(50),in d int, out nt varchar(50))
BEGIN
SET #statment = concat('Select ntime into nt from ',tb);
SET #date = CONCAT(' WHERE date = "', d, '"');
SET #statmen = CONCAT(#statment, #date);
PREPARE stmt FROM #statment;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
When used within single quotes nt will not be treated as variable but a literal.
Use local variable to set value into and assign the same to out param after execution.
Example:
DELIMITER //
CREATE DEFINER=`root`#`localhost`
PROCEDURE `get_ntime`( in tb varchar(50), in d int, out nt varchar(50) )
BEGIN
SET #staetment = concat( 'Select ntime into #nTime from ', tb );
-- SET #date = CONCAT( ' WHERE date = "', d, '"' );
SET #date = CONCAT( ' WHERE date = ?' );
SET #statement = CONCAT( #statement, #date );
SET #dt := d;
PREPARE stmt FROM #statement;
-- EXECUTE stmt;
EXECUTE stmt using #dt;
DEALLOCATE PREPARE stmt;
SET nt := #nTime;
END;
//
DELIMITER ;
Prepared statements have session scope, mysql doesn't know you want to use your prepared statement inside stored procedure only. You deallocate the statement immediately, but it doesn't have to be always like that.
Thats why mysql simply disallows using anything that has less scope inside your prepared statement - as is the case with in and out parameters, which have a scope of stored procedure.
As a workaround mentioned in mysql prepare statement manual you can use user variable inside your prepared statement and then SET your out paremeter to that user variable.

Mysql Stored Procedure Ignores Parameters and WHERE conditions

I have the following which works great:
SET #param = '%string%';
SELECT *
FROM tbl
WHERE IF(#param IS NOT NULL, LIKE #param, TRUE);
However when I change to this:
DELIMITER //
CREATE PROCEDURE test(IN param VARCHAR(255))
BEGIN
SET #param = param;
SELECT *
FROM tbl
WHERE IF(#param IS NOT NULL, LIKE #param, TRUE);
END //
DELIMITER ;
CALL test('%string%');
All rows are returned as if there were no parameter passed in.
I assume that there is some limitation or restriction with MySQL that I am missing?
Not sure but please try this with prepared statement
DELIMITER //
CREATE PROCEDURE test(IN param VARCHAR(255))
BEGIN
SET #param = param;
SET #q = CONCAT("SELECT *
FROM tbl
WHERE IF(",#param," IS NOT NULL, LIKE ",#param,", TRUE)");
PREPARE stmt FROM #q;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
This statement is syntactically wrong and won't get compiled:
WHERE IF(#param IS NOT NULL, LIKE #param, TRUE);
and you can't just place variable values like that to construct an SQL statement.
You need a Prepared Statement to prepare and execute such statements.
Try the following: (you need to replace column_name with valid column name from your table.
DELIMITER //
DROP PROCEDURE IF EXISTS test //
CREATE PROCEDURE test( IN param VARCHAR( 255 ) )
BEGIN
SET #param = param;
SET #query = CONCAT( 'SELECT * FROM tbl WHERE column_name LIKE ' );
SET #query = CONCAT( #query, ' IF( \'', #param, '\' IS NOT NULL, \'' );
SET #query = CONCAT( #query, #param, '\', TRUE )' );
PREPARE stmt FROM #query;
EXECUTE stmt;
DROP PREPARE stmt; -- deallocate prepare stmt;
END;
//
DELIMITER ;
CALL test('%string%');
If you run select #query, it will return following statement:
SELECT * FROM tbl
WHERE column_name LIKE IF( '%string%' IS NOT NULL, '%string%', TRUE )

MySql Stored Procedure and Comma Separated Varchar with In Statement

I'm trying to create a stored procedure which will take a comma separated list as a value and push that into a select statement with an IN clause. I'm aware of find_in_set which works, but does have a performance overhead. I've also trialled a prepare statement but the problem remains.
My Example:
My parameter is sp1 and contains the value: 'valuex, valuey, valuez'.
BEGIN
set #sql = concat('select * from tablename WHERE assignedTo in (', sp1, ')');
PREPARE q FROM #sql;
execute q;
END
As expected, this throws an error since SQL will treat the value as column names. My question is how do I achieve: 'valuex','valuey','valuez' FROM 'valuex, valuey, valuez'?
You can use replace function:
BEGIN
set #sql = concat("select * from tablename WHERE assignedTo in ('", replace(sp1,",","','"), "')");
PREPARE q FROM #sql;
execute q;
END
An option is:
DELIMITER $$
DROP PROCEDURE IF EXISTS `sp_test`$$
CREATE PROCEDURE `sp_test`(`sp1` VARCHAR(50))
BEGIN
SET `sp1` := REPLACE(REPLACE(`sp1`, ', ', ','), ',', '\',\'');
SET #`query` := CONCAT('SELECT `column0`, `column1` FROM `tablename` WHERE `assignedTo` IN (\'', `sp1`, '\');');
PREPARE `stmt` FROM #`query`;
EXECUTE `stmt`;
DEALLOCATE PREPARE `stmt`;
END$$
DELIMITER ;
CALL `sp_test`('valuex, valuey, valuez');
-- SELECT `column0`, `column1` FROM `tablename` WHERE `assignedTo` IN ('valuex','valuey','valuez');