Using result of SQL Query as table name in mysql trigger - mysql

I've to write a trigger on my table which will perform the following functions.
Before Update on row, check price of Item
If price has changed from the last price, then select the table name, where to insert the item name, from another table having type of item and the associated table name.
Insert the item name in the selected table.
To put simply i've a table(TypeNameTable) having item categories and corresponding table names, if the price of item has changed then i've to get the table name from the TypeNameTable and insert the item name in the table, which is retrieved from TypeNameTable.
I'm not able to insert into table when I get the table names dynamically. Please suggest how to do it. Here's what I'm doing:
BEGIN
#declare countryTableName varchar(50);
declare itemPrice int;
declare itemTableName text;
IF (New.Price != Old.Price) THEN
SET countryTableName = (select `ItemManager`.`TypeNames`.`TypeTableName`
from `ItemManager`.`TypeNames`
where `ItemManager`.`TypeNames`.`ItemType` = NEW.ItemType);
INSERT INTO `ItemManager`.itemTableName
( `ItemName`, `ItemPrice`,
VALUES
( NEW.Name, New.Price );
END IF;
END$$
I get the error
ItemManager.itemTableName doesn't exists.

Answering my own question.
Figured out that using Dynamic SQL is not allowed in MySQL triggers . The restrictions are listed here.
However it's possible in Oracle where we can use PRAGMA AUTONOMOUS_TRANSACTION which executes the query in new context, and hence supports Dynamic SQL.
Example listed here at Point 27 .

You could CONCAT() your INSERT statement into a variable and execute that as PREPARED STATEMENT, someting like
...
SET #sql := CONCAT( 'INSERT INTO ', itemTableName, ' ... ' );
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
...
afaik this is the only way to process dynamically generated SQL in stored routines and triggers.

If it is possible, I'd suggest you to change design a little. Instead of different tables you can create one table itemTable.
...
IF (New.Price != Old.Price) THEN
INSERT INTO `ItemManager`.`itemTable`
( `ItemName`, `ItemPrice`,
VALUES
( NEW.Name, New.Price );
END IF;
...
If there are different item properties, this table can be a parent table for specific child tables.

Related

How to find the sum of all the numeric values present in a MySQL table's column which has datatype as varchar and are separated by commas

I have a table which has been created using the following query
create table damaged_property_value
(case_id int, property_value varchar(100) );
insert into damaged_property_value (1,'2000'),(2,'5000,3000,7000');
The problem is I need to find the total value of all the properties that have been damaged.
I am writing the following query to return the sum:
select SUM(cast(property_value as unsigned)) from damaged_property_value;
It returns the sum as 7000, i.e , 2000+5000. It is not considering the value of property which are separated by commas.
Note that 5000,3000 and 7000 are values of three different properties that have been damaged in a particular case. It should have produced 17000 as an answer.
How to solve this problem.
Please help!
As was said, the best solution would be to fix the data structure.
Now, just for the fun of solving the problem, and after much research, I managed to do the following (it requires the case_id to be sequential, starting at 1) that calculates the values of the property_value strings and puts them into the new actual_value field.
drop table if exists damaged_property_value;
create table damaged_property_value
(case_id int primary key, property_value varchar(100), actual_value int );
insert into damaged_property_value (case_id, property_value) values (1,'2000'),(2,'5000,3000,7000'),(3, '7000, 2000'),(4, '100,200,300,400,500,600');
drop procedure if exists Calculate_values;
DELIMITER $$
CREATE PROCEDURE Calculate_values()
BEGIN
DECLARE count INT;
SET count = 1;
label: LOOP
select
concat('update damaged_property_value set actual_value = ',
replace((select property_value from damaged_property_value where case_id = count), ",", "+"),
' where case_id = ', count, ';')
into #formula;
#select #formula;
prepare stmt from #formula;
execute stmt;
deallocate prepare stmt;
SET count = count +1;
IF count > (select count(*) from damaged_property_value) THEN
LEAVE label;
END IF;
END LOOP label;
END $$
DELIMITER ;
CALL Calculate_values();
select * from damaged_property_value;
/* select SUM(actual_value) from damaged_property_value; */

Composing a dynamic query in a stored procedure in MYSQL

I am creating a stored procedure in MySQL in order to execute the same queries in several parts of my application.
One of the two tables, as you can guess from the code, will be different depending on the script that will execute the stored procedure. The fields of the second query will be the same in number and type. The name of the table and the name of a column will change.
In order to make a dynamic query, I have used the CONCAT() command but I don't like it very much because it contains too many fragmented parts and too many quotes. Is there a more elegant way to compose a dynamic query like this?
BEGIN
INSERT IGNORE INTO tag (cod, tag) values (cod_tag_in, tag_in);
SET #query = CONCAT("INSERT INTO ", table_in, " (cod, ", table_field_in, ", tag_cod) values ('", app_cod_in, "','", section_cod_in, "', (SELECT cod FROM tag WHERE tag = '", tag_in, "'))");
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
Here is how I would write this routine if the table name and field name are really dynamic:
BEGIN
INSERT IGNORE INTO tag (cod, tag) values (cod_tag_in, tag_in);
SET #query = CONCAT('INSERT INTO `', table_in, '` (cod, `', table_field_in, '`, tag_cod) values (?, ?, ?)');
SET #cod = app_cod_in, #field = section_code_in, #tag_cod = cod_tag_in;
PREPARE stmt FROM #query;
EXECUTE stmt USING #cod, #field, #tag_cod;
DEALLOCATE PREPARE stmt;
END
Use query parameters so you don't have to use all those meticulous quotes for the values. This also protects you in case the values themselves contain quote characters.
I don't see the need for the subquery, since the value you just inserted in the the tag table should be the same as cod_tag_in anyway.
You do need the breaks in quotes for the dynamic table name and dynamic field name, because you can't use parameters as table or column identifiers.
I put back-ticks around the table and field name, just in case these identifiers require them (conflict with SQL reserved keywords, contain whitespace or punctuation, etc.).
However, you shouldn't even use dynamic SQL for this at all. You should use a CASE statement for the tables you need to support:
BEGIN
INSERT IGNORE INTO tag (cod, tag) values (cod_tag_in, tag_in);
CASE table_in
WHEN 'mytable1' THEN
INSERT INTO mytable1 (cod, myfield1, tag_cod) VALUES (app_cod_in, section_code_in, cod_tag_in);
WHEN 'mytable2' THEN
INSERT INTO mytable2 (cod, myfield2, tag_cod) VALUES (app_cod_in, section_code_in, cod_tag_in);
WHEN 'mytable3' THEN
INSERT INTO mytable3 (cod, myfield3, tag_cod) VALUES (app_cod_in, section_code_in, cod_tag_in);
ELSE
SIGNAL SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Unknown table';
END CASE;
END
This means you don't have to worry about CONCAT()-ing any fragments of SQL, and you don't have to worry about SQL injection risks, and you don't have to worry about table or column identifiers that have conflicting characters.
But you are limited to the tables you have coded in the CASE statement. If you are in the habit of creating new tables on the fly frequently, you'd have to replace the stored procedure with longer and longer CASE statements. But if you need to do that, I'd reconsider the design that so frequently requires new tables of similar structure.

How to count values from the database table column?

I want to convert game tag data found here into a dimension table of a star schema.
But the problem is that steamspy_tag_data table is organised as such every column name is tag name of a game and one game can have multiple tags. For example, lets say I have game Warcraft3 with appid 30 it would be in a table like this.
appid|strategy|action|shooter|fantasy|
-----+--------+------+-------+-------
30 6345 1452 0 6340
Column value greater than 0 signifies amount of user votes that voted certain game to be of that game tag. For Warcraft3 game with appid 30 - 6345 users voted it classifies as strategy 1452 users it classifies as action, .. etc.
Some columns for example "abstract" (column) tag has almost all 0 throughout the whole column meaning almost no game uses that tag, so to simplify 372 columns with over 29k row value into something more compact I want to run a query that would count non-zero value per every tag column and put them in the new table "tagovi" for better visibility which columns(tags) have relatively low game usage count.
so far I came up with this:
CREATE DEFINER=`root`#`localhost` PROCEDURE `zbroji_tagove`()
BEGIN
DECLARE i INTEGER;
DECLARE total_row_count INTEGER;
DECLARE dummy VARCHAR(255);
DECLARE zbrojeno INTEGER;
DECLARE trenutna VARCHAR(255);
DECLARE kursor CURSOR FOR
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = 'sppi' AND table_name = 'steamspy_tag_data'
ORDER BY ordinal_position;
SELECT FOUND_ROWS() into total_row_count;
open kursor;
FETCH kursor into dummy;
SET i = 1;
ponavljanje: LOOP
IF i > total_row_count THEN
CLOSE kursor;
LEAVE ponavljanje;
END IF;
FETCH kursor INTO trenutna;
SET zbrojeno = 0;
SET zbrojeno = (SELECT COUNT(*) FROM steamspy_tag_data where trenutna <> 0);
INSERT INTO tagovi(kategorija,broj_igra)
VALUES (
(trenutna),(zbrojeno)
);
SET i = i + 1;
end LOOP;
END
New table tagovi has 3 columns (ID auto_increment, kategorija Varchar(255), broj_igra INTEGER).
When I execute my stored procedure "zbroji_tagove"() I get SQL ERROR CODE 1292; Truncated incorrect DOUBLE value 'some_tag_name'. So somehow sql treats variable value as value instead of column at line SET zbrojeno = (SELECT COUNT() FROM steamspy_tag_data where trenutna <> 0*);
Is there a way for me to accomplish what I want inside MySQL environment?
You can't use variables like that, you need a prepared statement
See
#sql = CONCAT("SELECT COUNT(*) INTO zbrojeno FROM steamspy_tag_data where ",trenutna," <> 0);";
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;

How to set a local list/tuple variable in mysql

Is there a way to do the following in mysql?
SET #studios = ('Disney', 'Warner Bros.', 'Fox');
SELECT * FROM movies WHERE provider IN #studios;
When I try doing the above I get the error:
Operand should contain 1 column(s)
The error is coming from your initial assignment. You cannot assign lists to variables.
The only way of doing this in MySQL is to either create a temp table to hold the values, and then do ... IN (SELECT someVal FROM thatTemp), or to dynamically create the query with the values directly in the query string.
Example temp table creation:
CREATE TEMPORARY TABLE `someTemp` ( someVal VARCHAR(16) );
INSERT INTO `someTemp` (someVal) VALUES ('a'), ('b'), ('c');
SELECT * FROM myTable WHERE myField IN (SELECT someVal FROM someTemp);
DELETE TEMPORARY TABLE `someTemp`;
Alternatively, there is also FIND_IN_SET, which could be used like this:
SET #list = 'a,b,c';
SELECT * FROM myTable WHERE FIND_IN_SET(myField, #list) <> 0;
but this method probably has extremely poor performance (and may not be useable if your "myField" values may contain commas).
It is not possible to set a tuple/list/array in a user-defined variable in MySQL. You can use Dynamic SQL for the same:
-- we use single quotes two times to escape it
SET #studios = '(''Disney'', ''Warner Bros.'', ''Fox'')';
-- generate the query string
SET #query = CONCAT('SELECT * FROM movies WHERE provider IN ', #studios);
-- prepare the query
PREPARE stmt FROM #query;
-- execute it
EXECUTE stmt;
-- deallocate it
DEALLOCATE PREPARE stmt;
You could concatenate your list to a string, and use FIND_IN_SET as your criteria. Might not be super efficient, but makes the code quite easy to read and maintain.
Looks like this:
SET #studios = CONCAT_WS(',',
'Disney',
'Warner Bros.',
'Fox'
);
SELECT * FROM movies
WHERE FIND_IN_SET(provider, #studios) <> 0;

EDIT mysql tablename to combine with value of one column (concatenate)

I have a table called 'Details', the Details table has a few columns, one being 'TicketNumber'.
What I am hoping to do is name the table 'Details_TicketNumber' --- not the actual word but the value of the first/highest ticket number.
e.g if the TicketNumber is '12345'
the table name would be Details_12345
How would I be able to do this? I've been searching for a few hours and no luck today.. Thanks
This is what I have tried (and realized it wouldn't work)
attempt 1:
Rename table details
to (select concat("details",details.ticketnumber));
Attempt 2:
set #sql = CONCAT(details,
details.TicketNumber)
);
prepare s from #sql;
execute s;
As suggested by #JeffUK and the reference he pointed out. I believe this should work inside a stored procedure.
DECLARE highest_ticket INT DEFAULT 0;
SELECT MAX(ticket_number) INTO highest_ticket
FROM details;
set #s = CONCAT('RENAME TABLE details to details_',highest_ticket);
prepare renameTable from #s;
EXECUTE renameTable ;
DEALLOCATE PREPARE renameTable ;