I have this strange problem. I have a table containing 2 numbers, but a simple calculation makes a precision fault:
mysql> create table tblX as select '2.7' as H, '2100' as H2;
Query OK, 1 row affected (0.32 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> select *, h-h2/1000 from tblX;
+-----+------+--------------------+
| H | H2 | h-h2/1000 |
+-----+------+--------------------+
| 2.7 | 2100 | 0.6000000000000001 |
+-----+------+--------------------+
1 row in set (0.00 sec)
This example recreates a problem I have in a setup, where formulas, field and their values are extracted in a way where some fields have to be varchars but others are used in calculations as above. So the formula can not be changed. Another example could be a formula like: if(code='c' and h-h2/1000>0.6,h,0)
where the fields are created by a pivot operation.
My assumption is, that implicit conversion of varchar to decimal causes this problem. My question is: Are there any server settings that could change this implicit conversion. Or are there any other solution for this.
EDIT:
If I modify the columns like this:
alter table tblX MODIFY column h decimal(20,4), MODIFY h2 decimal(20,4);
The result will be correct. But I do not know the names of the columns in advance as they are created on the fly in temporary tables. It would be nice if it was possible to create a script that modify all columns that is only containing numeric values.
I am not aware about any server settings. But you can do explicit conversion in the sql as well.
Try something like this :
select *, CAST(h-h2/1000 AS DECIMAL(10,2)) from tblX;
Refer MySQL documentation. https://dev.mysql.com/doc/refman/5.5/en/fixed-point-types.html
The problem is, that varchars in a formula apparently implicit converts to floats and not decimals. Changing the formulas is not an option in this case. But modifying column datatype is an option.
The solution is to make a stored procedure that loops thru the relevant columns and modify them to a decimal datatype.
The table that contains the incorrect datatypes is named tmp.
I made a temporary table called tmpPar containing all column names that take part in a formula as numeric values. Then I set up a cursor and loop thru all those columns. Cursor loop looks like this:
DECLARE a varchar(255);
DECLARE cur1 CURSOR FOR SELECT CName FROM tmpPar;
...
Open cur1;
simple_loop: LOOP
fetch cur1 into a;
IF no_more_rows=1 THEN
LEAVE simple_loop;
END IF;
set #sql = null;
set #sql = concat('alter table tmp MODIFY column ' , a , ' decimal(20,4)');
PREPARE stmt FROM #sql;
EXECUTE stmt;
END LOOP simple_loop;
Close cur1;
Related
I'm trying to write a MySQL stored proceedure that loops through all existing tables in my database and creates a copy/clone of each table. I'm using a cursor to loop through the table names then create a new table like this:
DELIMITER //
CREATE PROCEDURE CopyTables()
BEGIN
DECLARE finished INT DEFAULT 0;
DECLARE tableName VARCHAR(100);
DECLARE copyTableName VARCHAR(100);
DECLARE curTables
CURSOR FOR
SELECT table_name FROM INFORMATION_SCHEMA.TABLES;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
OPEN curTables;
create_loop: LOOP
FETCH curTables INTO tableName;
IF finished THEN LEAVE create_loop; END IF;
SELECT concat('Processing table ', tableName);
SET copyTableName = CONCAT('copy_',tableName);
SELECT concat('Creating table ', copyTableName);
CREATE TABLE copyTableName LIKE tableName;
END LOOP;
CLOSE curTables;
END //
DELIMITER;
But I get an error when calling the stored procedure:
> call CopyTables()
[2020-12-08 18:16:03] 1 row retrieved starting from 1 in 77 ms (execution: 15 ms, fetching: 62 ms)
[2020-12-08 18:16:03] [S1000] Attempt to close streaming result set com.mysql.cj.protocol.a.result.ResultsetRowsStreaming#7a714591 that was not registered. Only one streaming result set may be open and in use per-connection. Ensure that you have called .close() on any active result sets before attempting more queries.
Is the result set exception effectively complaining because I'm creating new tables which is effectively messing with the cursor/select? I've got additional table changes on both the original and copied table to perform, like adding new columns, creating triggers, modifying constraints.
The list of table names is not static, and this should be able to run on whatever database I need it.
Can you suggest another way to achieve this without the cursor perhaps?
The problem is that the procedure is returning multiple result sets, but your Java client is not handling that correctly.
Refer to How do you get multiple resultset from a single CallableStatement?
Another problem with your procedure is that you aren't creating tables the way you think you are.
This statement:
CREATE TABLE copyTableName LIKE tableName;
will only create a table named literally copyTableName that is like another table that is literally tableName. It will NOT use the values of variables by those names.
To do what you want, you need to use a prepared statement:
SET #sql = CONCAT('CREATE TABLE `', copyTableName, '` LIKE `', tableName, '`');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
This way the value of your variables is concatenated into an SQL statement.
Note that PREPARE only accepts a user-defined session variable, the type with the # sigil. It doesn't work with local variables you create in your procedure with DECLARE. Read https://dev.mysql.com/doc/refman/8.0/en/prepare.html and https://dev.mysql.com/doc/refman/8.0/en/user-variables.html
I am having difficult getting a procedure to update a table in the way I require. I am using phpmyadmin on my local computer. In phpmyadmin I can put the following code into the SQL tab and one row will be updated:
SET `adjCost` = 22.05 WHERE `Name` LIKE CONCAT('magic', '%') AND `idKey` = '2016fulham02345';
As expected and wanted, IF the name begins with magic AND the idKey is '2016fulham02345' THEN the adjCost is updated to 22.05.
There will be between 2 and 50 rows with the same idKey. The Name will never be repeated in a set with the same idKey.
I created a procedure with the following parameters:
IN idK VARCHAR 255 Charset
IN aName VARCHAR 255 Charset
IN cost FLOAT 5,2
BEGIN
UPDATE `raceresults` SET `adjCost` = cost WHERE `Name` LIKE CONCAT(aName, '%') AND `idKey` = idK;
END
When I run this procedure it updates ALL adjCost where the idKey = idk and (seems) to ignore the name parameter.
I have tried concatenating the name string first:
BEGIN
SELECT CONCAT(aName, '%') INTO #str;
UPDATE `raceresults` SET `adjCost` = cost WHERE `Name` = #str AND `idKey` = idK;
END
but to no avail.
I looked through w3schools, stackoverflow and google and have not been able to find the answer.
My question is:
How can I correct my procedure to get it to work as I would like?
UPDATE: as requested.
CREATE DEFINER=`root`#`localhost` PROCEDURE `importAltUpdateAjdCost`(IN `idK` VARCHAR(255), IN `aName` VARCHAR(255), IN `cost` FLOAT(5,2))
NO SQL
BEGIN
UPDATE `costingPP`
SET `adjCost` = cost
WHERE
`Name` LIKE CONCAT(aName, '%')
AND
`idKey` = idK;
END
To get this, I selected export on my list of procedures on phpmyadmin.
I'm not entirely sure what or how you did, but here's what I did and it instantly worked. Since you didn't specify MySQL version, I used 5.7.
EDIT: Now as I went back to see your procedure creation statement I realised that NO SQL was introduced in MySQL 8.0. Since your procedure clearly is SQL then please remove the NO SQL and re-create the procedure.
I'm leaving my MySQL 5.7 sample here for reference:
1) Created a simple table:
mysql> CREATE TABLE raceresults (
-> idKey VARCHAR(255),
-> Name VARCHAR(255),
-> adjCost FLOAT(5,2)
-> );
Query OK, 0 rows affected (0.06 sec)
2) Here we insert a sample data row:
mysql> INSERT INTO raceresults VALUES ('2016fulham02345', 'magicFlyingHorse', 0.00);
Query OK, 1 row affected (0.01 sec)
3) To create a (STORED) PROCEDURE we have to temporarily set a different delimiter, so query parser wouldn't terminate procedure creation on default semi-colon, as it's used inside the procedure. After delimiter's change we create the procedure and set the delimiter back to semi-colon
mysql> DELIMITER //
mysql> CREATE PROCEDURE update_test(IN idK VARCHAR(255), IN aName VARCHAR(255), IN cost FLOAT(5,2))
-> BEGIN
-> UPDATE `raceresults` SET `adjCost` = cost WHERE `Name` LIKE CONCAT(aName, '%') AND `idKey` = idK;
-> END//
mysql> DELIMITER ;
Query OK, 0 rows affected (0.00 sec)
4) Now let's see how it all works. Before and after the procedure call I'm selecting the rows from database. You can see the cost column value changing:
mysql> SELECT * FROM raceresults;
+-----------------+------------------+---------+
| idKey | Name | adjCost |
+-----------------+------------------+---------+
| 2016fulham02345 | magicFlyingHorse | 0.00 |
+-----------------+------------------+---------+
1 row in set (0.00 sec)
mysql> CALL update_test('2016fulham02345', 'magic', 1.23);
Query OK, 1 row affected (0.02 sec)
mysql> SELECT * FROM raceresults;
+-----------------+------------------+---------+
| idKey | Name | adjCost |
+-----------------+------------------+---------+
| 2016fulham02345 | magicFlyingHorse | 1.23 |
+-----------------+------------------+---------+
1 row in set (0.00 sec)
And now one piece of advise too:
If possible, use only lower case table, column, indexes, functions, procedures, etc... names, while always writing all SQL commands in uppercase (which you did). This is kind of a de facto standard and makes life easier both for you and others reading your code.
I have the following query I want to execute in my stored procedure WITHOUT PREPARING the query, since this gives me problems with OUT to pass back parameters.
DELIMITER //
CREATE PROCEDURE Test (
IN CID BIGINT(20),
IN IDs LONGTEXT
)
BEGIN
#EXECUTE UNDERNEATH QUERY
SELECT * FROM CONCAT('Part1_OfTableName', CID); #CID IS CustomerID
END //
DELIMITER ;
However, this fails and I don't know how to fix the problem.
(Note that in the example I have no spaces in my table name, however in my situation I might have a space in my table name though)
PREPARE should have no bearing on your ability to successfully set OUT parameters of your procedure
SET DELIMITER //
CREATE PROCEDURE test(IN cid INT, IN ids TEXT, OUT out_int INT)
BEGIN
SET #sql = CONCAT('SELECT * FROM `table_', cid, '`', CASE WHEN ids IS NULL THEN '' ELSE CONCAT(' WHERE id IN( ', ids, ')') END);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET out_int = 1;
END//
SET DELIMITER ;
Sample usage:
mysql> CALL test(1, '2,3', #out_int);
+------+
| id |
+------+
| 2 |
| 3 |
+------+
2 rows in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT #out_int;
+----------+
| #out_int |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
If you need to return results from a stored procedure using sql statement that must be prepared, you can use an intermediate temp table.
BEGIN
CREATE TEMPORARY TABLE `myresults` blah blah....;
//construct and prepare select you would've used, but start it with an insert like so...
// INSERT INTO `myresults` SELECT ....
// Execute the prepared query
SELECT * FROM `myresults`;
DROP TEMPORARY TABLE `myresults`;
END
...at least I am pretty sure this technique used to work; I've been working more in MSSQL the last couple years.
Something to note:
Temporary tables are connection/session specific, so while safe from a global perspective using a generic name like myresults can be problematic if queries executed earlier on the connection/session (or by a procedure calling this one) use the same name; in practice/paranoia, I tended to use a different guid (in each procedure using this technique) as a prefix for any temporary tables generated within it.
I need to store select statement results (multiple rows and Columns) in a variable to use later, Is there a way to do it in mySql only. I want to use it in stored procedure.
I know that single result of a select statement can easily be stored in a variable, but is there any to store multiple rows and columns?
If there is. Then What is that and its Ok.
If not then how a scenario like following is solvable in mySql? I have written following ideal/desired code supposing there exists a dataTbale.
Declare dt DataTable;
set dt = select column_name,table_name from information_schema.columns
where table_schema='emp';
DECLARE i INT; DECLARE c INT; set i=0; set c=dt.Rows.Count;
WHILE i < c
#q = concat ('select ', dt.Rows[i][0],' from ',dt.Rows[i][1]);
prepare s1 from #q;
execute s1;deallocate prepare s1;
END WHILE;
Each Row and its two cells of dt are used in while. Is there any facility of two-D array or some other?
What is being tried to do? Above code in while is intended to display all values from all columns (1 by one) from all tables of a database
What is question? Title and 1st Statement of Question.
Instead of storing all the results at once, use a cursor: http://dev.mysql.com/doc/refman/5.0/en/cursors.html
Then loop while reading the cursor.
Environment: MySQL 5.1, Linux
I have a stored procedure that computes several values from a single input value. The values are returned as OUT parameters. I would like to call this procedure for every row set and have the values appear as columns in the result set. The values have a complex relationship such that distinct functions for each value is not easily constructed.
The question: How can I get OUT parameters to show up as columns in a table?
Here's what I have so far:
DELIMITER $_$
DROP PROCEDURE IF EXISTS in_out;
CREATE PROCEDURE in_out (
IN s TEXT,
OUT op TEXT,
OUT opn INT,
OUT opd TEXT,
OUT len INT
)
BEGIN
SET op = 'del';
SET opn = 1;
SET opd = substr(s,4);
SET len = LENGTH(SUBSTR(s,4));
END
$_$
DELIMITER ;
Then:
mysql> call in_out('delACT',#op,#opn,#opd,#len);
Query OK, 0 rows affected (0.00 sec)
mysql> select #op,#opn,#opd,#len;
+------+------+------+------+
| #op | #opn | #opd | #len |
+------+------+------+------+
| snv | 1 | ACT | 3 |
+------+------+------+------+
1 row in set (0.00 sec)
So far so good, but I can't figure out how to call this procedure for every row and return the results in the result set. I want is something like this:
dream> select mycol,in_out(mycol) from mytable
+---------+------+------+------+------+
| mycol | #op | #opn | #opd | #len |
+---------+------+------+------+------+
| delACT | del | 1 | ACT | 3 |
+---------+------+------+------+------+
Thanks!
You confuse the stored procedures and stored functions:
stored function will be return a value, the results can be used in
expressions (like COS() and other mysql built-in functions).
stored procedure need use CALL , is an independent operation, can not
be used in expressions.
If you want to "select mycol,in_out(mycol) from mytable",you must:
CREATE FUNCTION in_out( ...
This appears to be a trick question: one can't create table relations out of function/procedure results in MySQL. I ended up refactoring into separate functions (as suggested by MichaĆ). I had been hoping for a MySQL equivalent to PostgreSQL's table functions (http://goo.gl/77QVE).
I'd recommend to prepare data in stored procedure for each possible value in:
select distinct mycol
from mytable
where <... condition that you would use anyway in final result ...>
where mycol is your parameter for stored procedure save it to temporary table and than join to this values.
-- way the temp table may look in your sp
create temporary table tmptable (
mycol text
op text,
opn int,
opd text,
len int
)
after that use join:
select m.mycol, t.op, t.opn, t.opd, t.len
from mytable m
join tmptable t on m.mycol = t.mycol
where <... condition that you would use anyway in final result ...>
Bit different question, are you absolutely sure that there is no different way to process your final result than using a stored procedure?