Fellow residents of the Stack Exchange,
I am currently trying to create a procedure that will reformat data in a very major way. I get a list of data on tickets (summary for random months). The input views always have the same columns, but since I have to display it with the months as columns (which are usually entered as a value in the Monat column on the view).
Sooo, after some major research, trial and error and a lot of headaches I got to make it work. The procedure accepts a single ticket-number and 'returns' all stats on that single ticket.
I do this by:
Iterate over each distinct month, building a CREATE TEMPORARY TABLE statement.
Execute that statement, then deallocate the statement
Iterate over each ticket-value, sorting them into the temporary table with a INSERT INTO ON DUPLICATE KEY UPDATE statement (which I build, execute and deallocate each iteration anew)
Again Iterate over each distinct month, building a last update to fill the summary columns (I know, I could combine this into Step 1, but I tried to keep the steps as separate as I could, just to make it easier to read, I can optimize once I'm done making it work as I want).
Select the temporary table, so it's being returned
Clean up some loose ends, because I'm a habitually neat person.
The good thing: It Works!
The bad thing: It works only for one input value. Whenever I change it to another ticket number, that would require different months for columns it fails with an "Error Code 1054, Unknown Column in field list", referring to an old column (month) the current query ought not have.
I can run the procedure as many times as I want, as long as the columns of the temporary table are identical.
This behavior resets, whenever I drop and recreate the procedure or create a new connection.
Obviously, I'm forgetting to do a cleaning step somewhere along the way, and me being fairly new to SQL in general and MySQL in particular probably didn't even know to look for it :(.
Help would be most appreciated, thanks,
Fred
DELIMITER //
CREATE PROCEDURE proc_get_relevant_tickets(bID VARCHAR(10))
DETERMINISTIC
READS SQL DATA
BEGIN
# Declare storage vairables for withdrawing data from view
DECLARE ID, FiID, ZVB, ZNVB, ZGes, WVB, WNVB, WGes INTEGER;
DECLARE Mon VARCHAR(50);
DECLARE RowVerb, RowNVerb, RowGes, Counter INTEGER;
DECLARE verbucht, nichtverbucht, gesamt VARCHAR(50);
DECLARE currentticket INTEGER;
DECLARE statezges, statewges LONGTEXT;
# Declare runtime stuff
DECLARE done INT DEFAULT FALSE;
DECLARE declaration LONGTEXT;
DECLARE temptext VARCHAR(50);
DECLARE Months CURSOR FOR SELECT DISTINCT Monat FROM view_list_of_tickets t WHERE bID = t.ID;
DECLARE `Values` CURSOR FOR SELECT * FROM view_list_of_tickets t WHERE bID = t.ID;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
# Clean up in case something interrupted last execution
DROP TEMPORARY TABLE IF EXISTS temp_storage_5437485;
# If there are any entries to work upon
IF (SELECT COUNT(t.ID) FROM view_list_of_tickets t WHERE bID = t.ID > 0)
THEN
# Create temporary table to put the values into
SET declaration = 'CREATE TEMPORARY TABLE `temp_storage_5437485` (ID INTEGER PRIMARY KEY, TicketNr INTEGER, Category VARCHAR(50), ';
SET done = FALSE;
OPEN Months;
read_loop: LOOP
FETCH Months INTO temptext;
IF done THEN
LEAVE read_loop;
END IF;
SET declaration = CONCAT(declaration, '`', temptext, ' Zeit` INTEGER DEFAULT 0, ');
SET declaration = CONCAT(declaration, '`', temptext, ' Wert` INTEGER DEFAULT 0, ');
END LOOP;
CLOSE Months;
SET declaration = CONCAT(declaration, '`Gesamt Zeit` INTEGER, `Gesamt Wert` INTEGER);');
SELECT declaration INTO #declaration;
PREPARE stmt FROM #declaration;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
# End of creating the storage container
# Cycle through values and input into temporary table
SET done = FALSE;
SET verbucht = 'Verbucht';
SET nichtverbucht = 'Nicht Verbucht';
SET gesamt = 'Gesamt';
SET currentticket = 0;
SET Counter = 0;
SET RowVerb = 1;
SET RowNVerb = 2;
SET RowGes = 3;
OPEN `Values`;
read_values: LOOP
FETCH `Values` INTO ID, FiID, ZVB, ZNVB, ZGes, WVB, WNVB, WGes, Mon;
IF done THEN
LEAVE read_values;
END IF;
# If a new ticket has been arrived at, increment the counters
IF currentticket > 0 AND ID <> currentticket THEN
SET currentticket = ID;
SET Counter = Counter + 1;
SET RowVerb = RowVerb + 3;
SET RowNVerb = RowNVerb + 3;
SET RowGes = RowGes + 3;
END IF;
IF currentticket = 0 AND ID <> currentticket THEN
SET currentticket = ID;
END IF;
# Insert first (Verbucht) row
SET declaration = CONCAT('INSERT INTO `temp_storage_5437485` (`ID`, `TicketNr`, `', Mon, ' Zeit`, `', Mon, ' Wert`) VALUES (');
SET declaration = CONCAT(declaration, RowVerb, ', ', ID, ', ', ZVB, ', ', WVB, ') ON DUPLICATE KEY UPDATE ');
SET declaration = CONCAT(declaration, '`', Mon, ' Zeit`=', ZVB, ', `', Mon, ' Wert`=', WVB, ';');
SELECT declaration INTO #declaration;
PREPARE stmt FROM #declaration;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
# Insert second (Nicht Verbucht) row
SET declaration = CONCAT('INSERT INTO `temp_storage_5437485` (`ID`, `TicketNr`, `', Mon, ' Zeit`, `', Mon, ' Wert`) VALUES (');
SET declaration = CONCAT(declaration, RowNVerb, ', ', ID, ', ', ZNVB, ', ', WNVB, ') ON DUPLICATE KEY UPDATE ');
SET declaration = CONCAT(declaration, '`', Mon, ' Zeit`=', ZNVB, ', `', Mon, ' Wert`=', WNVB, ';');
SELECT declaration INTO #declaration;
PREPARE stmt FROM #declaration;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
# Insert third (Gesamt) row
SET declaration = CONCAT('INSERT INTO `temp_storage_5437485` (`ID`, `TicketNr`, `', Mon, ' Zeit`, `', Mon, ' Wert`) VALUES (');
SET declaration = CONCAT(declaration, RowGes, ', ', ID, ', ', ZGes, ', ', WGes, ') ON DUPLICATE KEY UPDATE ');
SET declaration = CONCAT(declaration, '`', Mon, ' Zeit`=', ZGes, ', `', Mon, ' Wert`=', WGes, ';');
SELECT declaration INTO #declaration;
PREPARE stmt FROM #declaration;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE temp_storage_5437485 SET Category = verbucht WHERE temp_storage_5437485.ID = RowVerb LIMIT 5;
UPDATE temp_storage_5437485 SET Category = nichtverbucht WHERE temp_storage_5437485.ID = RowNVerb LIMIT 5;
UPDATE temp_storage_5437485 SET Category = gesamt WHERE temp_storage_5437485.ID = RowGes LIMIT 5;
END LOOP;
CLOSE `Values`;
# End of cycling for values input
# Being calculating the total values
SET declaration = 'UPDATE temp_storage_5437485 SET `Gesamt Zeit` = (';
SET statezges = '';
SET statewges = '';
SET done = FALSE;
OPEN Months;
read_loop: LOOP
FETCH Months INTO temptext;
IF done THEN
LEAVE read_loop;
END IF;
# If not the first entry, add more
IF statezges <> '' THEN
SET statezges = CONCAT(statezges, ' + ');
END IF;
IF statewges <> '' THEN
SET statewges = CONCAT(statewges, ' + ');
END IF;
# Add column name
SET statezges = CONCAT(statezges, 'temp_storage_5437485.`', temptext, ' Zeit`');
SET statewges = CONCAT(statewges, 'temp_storage_5437485.`', temptext, ' Wert`');
END LOOP;
CLOSE Months;
SET declaration = CONCAT(declaration, statezges, '), `Gesamt Wert` = (', statewges, ') LIMIT 100000');
SELECT declaration INTO #declaration;
PREPARE stmt FROM #declaration;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
# End calculating the total values
SELECT * FROM temp_storage_5437485;
DROP TEMPORARY TABLE IF EXISTS temp_storage_5437485;
ELSE
SELECT 'is null';
END IF;
DROP TEMPORARY TABLE IF EXISTS temp_storage_5437485;
END //
DELIMITER ;
Related
I have table users, with fields: id, name, age, gender, country, city, comment. Fields maybe null. For example:
cursor.execute('select * from users where id = 12')
cursor.fetchone()
(12, 'alex', 33, 'male', None, None, None)
How I can get back from query only not null fields?
This query must return me just
(12, 'alex', 33, 'male')
I can easily do it with a programming language, but I losing my resources by getting redundant info from tables and it also forced me to add redundant code.
So, you need to get customized table-oriented result with columns dynamically generated with SQL ANSI.
You will need a kit with two procedures:
Check if a column has only NULL values or not.
Gather all columns with NO NULL values.
With these two procedures you can check each column of a table strcuture and get an output withtout columns with NULL values.
The first procedure is generic:
set delimiter //
create procedure check_field_null(col varchar(64),
schemaname varchar(255),
tablename varchar(255),
out QN int)
BEGIN
SET #sSQL = concat('SELECT #N := COUNT(*) FROM ', schemaname, '.', tablename , ' WHERE (', col, ' <=> NULL);');
prepare stm from #sSQL;
execute stm;
set QN =#N;
deallocate prepare stm;
END
//
The second procedure means that you have the above mentioned method to identify NOT NULLS columns and put all to search.
set delimiter //
create procedure cur_cs_customer(inout allcols varchar(255), in my_schema_name varchar(64), in my_table_name varchar(64))
BEGIN
DECLARE Count_Null int default 0;
DECLARE initial INT DEFAULT 0;
DECLARE MYCOL char(64);
DECLARE ch_done INT DEFAULT 0;
DECLARE cs_cur1 CURSOR
FOR SELECT C.COLUMN_NAME
FROM information_schema.COLUMNS C
WHERE C.TABLE_SCHEMA = my_schema_name
AND C.TABLE_NAME = my_table_name;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET ch_done = true;
open cs_cur1;
read_cs_cur1:
LOOP
FETCH cs_cur1 INTO MYCOL;
IF (ch_done ) THEN
LEAVE read_cs_cur1;
END IF;
IF NOT isnull(MYCOL) THEN
call check_field_null(MYCOL,
my_schema_name,
my_table_name,
Count_Null);
if Count_Null = 0 then
/* Only it inlcudes fields with not null values */
set initial = initial + 1;
if initial = 1 then
SET allcols = MYCOL;
else
SET allcols = concat( cast(allcols as char(255)), ',', MYCOL);
end if;
end if;
END IF;
END LOOP read_cs_cur1;
close cs_cur1;
select allcols;
END
//
After, you may run as follows:
set delimiter ;
call cur_cs_customer(#my_args, 'schema_name', 'users');
select #my_args;
set #stm = concat('SELECT ', #my_args, ' FROM users;');
PREPARE stmt1 FROM #stm;
execute stmt1;
deallocate prepare stmt1;
Further explanation may be checked in an article here! .
I hope this tip may help you.
I wanted to build around 900 views(1 view for 1 table and based on 2 conditions) and i have all the table names in a table in mysql environment. I have created a stored proc . For test purpose i used limit 1.
I get below error when i call the sp.
Actually I re-wrote a mssql sp, did i miss anything here. please help
CALL ca_uim.sp_viewcreation_ontime() Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'NULL' at line 1 0.000 sec
USE `ca_uim`;
DROP procedure IF EXISTS `sp_viewcreation_ontime`;
DELIMITER $$
USE `ca_uim`$$
CREATE DEFINER=`root`#`localhost` PROCEDURE `sp_viewcreation_ontime`()
BEGIN
DECLARE qos varchar(255);
DECLARE pos int;
DECLARE r_table varchar(255);
DECLARE view varchar(255);
DECLARE cview varchar(2048);
DECLARE done int default 0;
DECLARE qos_cursor CURSOR FOR SELECT DISTINCT qos,r_table FROM S_QOS_DATA ORDER BY 2 limit 1;
DECLARE continue handler for not found set done = 1;
OPEN qos_cursor;
-- Perform the first fetch.
FETCH qos_cursor INTO qos, r_table;
-- Check ##FETCH_STATUS to see if there are any more rows to fetch.
WHILE not done
DO
-- Check QOS name for '-' character & replace with '_' if exist
SET pos = LOCATE('-',qos, 1);
IF pos != 0
THEN
SET qos = INSERT(qos, pos, 1, '_');
END IF;
-- Check QOS name for '/' character & replace with '_' if exist
SET pos = LOCATE('/',qos, 1);
IF pos != 0
THEN
SET qos = INSERT(qos, pos, 1, '_');
END IF;
-- Check QOS name for '(' character & replace with '_' if exist
SET pos = LOCATE('(',qos, 1);
IF pos != 0
THEN
SET qos = INSERT(qos, pos, 1, '_');
END IF;
-- Check QOS name for ')' character & replace with '_' if exist
SET pos = LOCATE(')',qos, 1);
IF pos != 0
THEN
SET qos = INSERT(qos, pos, 1, '_');
END IF;
-- Create view
SET view = CONCAT('V_',qos);
SET cview = CONCAT('CREATE VIEW ',view,' AS ',
'SELECT Q.source,Q.target,Q.origin,Q.robot,Q.probe,D.sampletime,D.samplevalue,D.samplestdev,D.samplerate,D.tz_offset ',
'FROM S_QOS_DATA Q JOIN ',r_table,' D ON Q.table_id=D.table_id');
BEGIN
-- Suppress Error message for Views that don't exist
DECLARE CONTINUE HANDLER FOR 1051
set #stmt_str = CONCAT('DROP VIEW ',view);
prepare stmt from #stmt_str;
execute stmt;
deallocate prepare stmt;
END;
BEGIN
-- Create the View, Catch tables that don't have samplestdev & samplerate fields
DECLARE CONTINUE HANDLER FOR 1054
set #stmt_str = cview;
prepare stmt from #stmt_str;
execute stmt;
deallocate prepare stmt;
/* PRINT CONCAT('Created View: ' , view) */
END;
BEGIN
DECLARE CONTINUE HANDLER FOR 1054
SET cview = CONCAT('CREATE VIEW ',view,' AS ',
'SELECT Q.source,Q.target,Q.origin,Q.robot,Q.probe,D.sampletime,D.samplevalue,D.tz_offset ',
'FROM S_QOS_DATA Q JOIN ',r_table,' D ON Q.table_id=D.table_id');
set #stmt_str = cview;
prepare stmt from #stmt_str;
execute stmt;
deallocate prepare stmt;
/* PRINT CONCAT('Created View: ' , view) */
END;
-- PRINT 'qos: ' + #qos + ' ' + #r_table+' '+#view
-- PRINT #cview
-- This is executed as long as the previous fetch succeeds.
FETCH qos_cursor INTO qos, r_table;
END WHILE;
CLOSE qos_cursor;
END$$
DELIMITER ;
Thanks
The following procedure works almost perfeclty with one exception. The first select returns two records. However, in the loop it builds three cases as though it goes around three times. Can't figure it out. Something obvious I'm not seeing.
DECLARE DTE DATE;
DECLARE EXIT_LOOP BOOLEAN;
DECLARE DATECURSOR CURSOR FOR
SELECT DISTINCT DATE
FROM SCHEDULE
WHERE BKFST > 0;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET EXIT_LOOP = TRUE;
OPEN DATECURSOR;
SET #s = '';
DATE_LOOP: LOOP
FETCH DATECURSOR INTO DTE;
SET #s = CONCAT(#s,CONCAT('SUM(CASE WHEN SCHEDULE.DATE =', "'", DTE, "'", ' THEN SCHEDULE.', _TYPE,' END) AS ', "'", DTE ,"', "));
IF EXIT_LOOP THEN
CLOSE DATECURSOR;
LEAVE DATE_LOOP;
END IF;
END LOOP DATE_LOOP;
SET #s = TRIM(trailing ', ' from #s);
SET #q = CONCAT('SELECT MOW_ID, FUNDING, ',#s,' FROM SCHEDULE GROUP BY MOW_ID, FUNDING');
SELECT #q;
PREPARE stmt1 FROM #q;
EXECUTE stmt1;
commit;
DEALLOCATE PREPARE stmt1;
I have two inputs for my stored procedure. One is the 'RoledID' and second one is the 'MenuIDs'. 'MenusIDs' is a list of comma separated menus ids that need to be inserted with RoledID. RoleId is just an INT and we need to put this RoledID against each MenuID. My table 'RolesMenus' contains two columns one for MenuID and one for RoleID.
Now I need to split MenuIDs and insert each MenuID with RoleID.
How can I write a stored procedure for it?
You can build one INSERT query (because statement allows to insert multiple records) and run it with prepared statements, e.g. -
SET #MenuIDs = '1,2,3';
SET #RoledID = 100;
SET #values = REPLACE(#MenuIDs, ',', CONCAT(', ', #RoledID, '),('));
SET #values = CONCAT('(', #values, ', ', #RoledID, ')'); -- This produces a string like this -> (1, 100),(2, 100),(3, 100)
SET #insert = CONCAT('INSERT INTO RolesMenus VALUES', #values); -- Build INSERT statement like this -> INSERT INTO RolesMenus VALUES(1, 100),(2, 100),(3, 100)
-- Execute INSERT statement
PREPARE stmt FROM #insert;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
As you see, it can be done without stored procedure.
Give this a go. It may need some tweaking if the MenuIDs string does not conform to 'menuId,menuId,menuId'.
Also I do not know what data type the menuId column is in your target table (INT?) so you may have to put some numeric checking in too (in case '1,2,3,banana,4,5' is passed in as the MenuIds input parameter).
DELIMITER $$
DROP PROCEDURE IF EXISTS `insert_role_menuids`$$
CREATE PROCEDURE `insert_role_menuids`(IN RoleID INT,IN MenuIDs varchar(500))
BEGIN
declare idx,prev_idx int;
declare v_id varchar(10);
set idx := locate(',',MenuIDs,1);
set prev_idx := 1;
WHILE idx > 0 DO
set v_id := substr(MenuIDs,prev_idx,idx-prev_idx);
insert into RolesMenus (RoleId,MenuId) values (RoleID,v_id);
set prev_idx := idx+1;
set idx := locate(',',MenuIDs,prev_idx);
END WHILE;
set v_id := substr(MenuIDs,prev_idx);
insert into RolesMenus (RoleId,MenuId) values (RoleID,v_id);
END$$
DELIMITER ;
for this solution, you must create a table with the name split_table, it can have a id(autoincrement) if you need it and must have a column where to store the value (I call it valor)
DELIMITER $$
USE `dbaname`$$
DROP PROCEDURE IF EXISTS `Split`$$
CREATE DEFINER=`root`#`localhost` PROCEDURE `Split`(
IN cadena VARCHAR(8000),
IN delimitador VARCHAR(10)
)
BEGIN
TRUNCATE split_table;
SET #posicion = 1;
SET #ldel = LENGTH(delimitador);
SET #valor = SUBSTRING_INDEX(cadena, delimitador, 1);
WHILE #valor <> '' AND #posicion > 0 DO
SET #valor = SUBSTRING_INDEX(cadena, delimitador, 1);
INSERT INTO split_table(valor) VALUES (#valor);
SET #posicion = POSITION(delimitador IN cadena);
SET #largo = LENGTH(cadena);
IF #largo >= #posicion THEN
SET cadena = SUBSTR(cadena, #posicion + #ldel, #largo - #posicion);
SET #valor = SUBSTRING_INDEX(cadena, delimitador, 1);
ELSE
SET #posicion = 0;
END IF;
END WHILE;
END$$
DELIMITER ;
First create procedure
CREATE DEFINER=`root`#`localhost` PROCEDURE `split_str_save_to_tmp_table`(
IN _str TEXT,
IN _table_name VARCHAR(80)
)
BEGIN
#DROP FIRST OLD TABLE
SET #q = CONCAT('DROP TEMPORARY TABLE IF EXISTS ', _table_name);
PREPARE st FROM #q;
EXECUTE st;
#CREATE TABLE
SET #q = CONCAT('CREATE TEMPORARY TABLE ', _table_name, '(id INT UNSIGNED NOT NULL PRIMARY KEY (id) )' );
PREPARE st FROM #q;
EXECUTE st;
SET #ids = REPLACE(_str, ',', '),(');
SET #ids = CONCAT('(', #ids, ')');
#INSERT INTO TABLE
SET #q = CONCAT('INSERT INTO ' , _table_name ,' VALUES');
SET #q = CONCAT(#q, #ids);
PREPARE st FROM #q;
EXECUTE st;
DEALLOCATE PREPARE st;
END
Then call
call split_str_save_to_tmp_table('1,2,3,4,5', 'tmp_split_product');
SELECT * FROM tmp_split_product
AFAIK MySQL does not have a function to split strings. Here is the MySQL manual for string related functions. In the comments section should be some information about workarounds for splitting string with substring-functions but not really usable:
MySQL manual
I'm working on a fairly simple ticket managment system. I want to keep a log for stuff that gets added, deleted, and changed.
I created three triggers, AFTER INSERT, AFTER DELETE, and AFTER UPDATE. The INSERT/DELETE triggers are straightforward, it's theUPDATE trigger I'm having problems with.
I would like to add which columns has changed in the table with their old & new values, i.e. colname changed from X to Y
The trigger I have now "works", except of course that it doesn't insert the actual values I'd like.
How do I get the value from OLD and NEW using the col_name variable?
I'm also not sure if this is the best possible way of doing this ... So if anyone has ideas on that, they're welcome too ... This trigger started out a lot simpler ...
BEGIN
DECLARE num_rows, i int default 1;
DECLARE col_name CHAR(255);
DECLARE updated TEXT;
DECLARE col_names CURSOR FOR
SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'storing'
ORDER BY ordinal_position;
OPEN col_names;
SELECT FOUND_ROWS() INTO num_rows;
SET i = 1;
SET #updated = 'Updated columns: ';
the_loop: LOOP
IF i > num_rows THEN
LEAVE the_loop;
END IF;
FETCH col_names INTO col_name;
/* So, how do I get the proper values? */
/* IF NEW.#col_name != OLD.#col_name THEN */
/*SET #updated = CONCAT(#updated, OLD.#col_name, ' changed into ', NEW.#col_name, ' ');*/
SET #updated = CONCAT(#updated, 'OLD', ' changed into ', 'NEW', ' ');
/* END IF;*/
SET i = i + 1;
END LOOP the_loop;
CLOSE col_names;
INSERT INTO `log` (`storing`, `medewerker`, `actie`, `data`)
VALUES (NEW.`id`, NEW.`medewerker`, "Storing aangepast", #updated);
END
Since usage of prepared statements here is impossible, I would suggest you to call some INSERT statements, e.g. -
IF NEW.column1 <> OLD.column1 THEN
INSERT INTO...
END IF;
IF NEW.column2 <> OLD.column2 THEN
INSERT INTO...
END IF;
...
Or try to copy all fields you need into another table.
In these cases you will avoid using cursor.
Try to use prepared statements
Something like this:
SET #s = CONCAT('SELECT new.', #col_name, ', old.', #col_name, ' FROM ', /*here is the query details like inner joins etc.*/, ' where ', 'NEW.', #col_name, '!= OLD.', #col_name )
PREPARE stmt FROM #s;
EXECUTE stmt;