Syntax error with IF EXISTS UPDATE ELSE INSERT (NODE JS) - mysql

I am trying to write an SQL query that will update an entry if it exists and insert a new one if it does not exist. The UPDATE ON DUPLICATE KEY option doesn't work because I am not querying by the primary key. The SQL statement I am referring to is in the sql.query() function below. I have also added the error message.
Asset.create = (newAsset, result) => {
sql.query(
`if exists(SELECT * from asset WHERE
AssetId="${newAsset.AssetId}" AND
AccountID="${newAsset.AccountId}") BEGIN UPDATE asset set
Amount="${newAsset.Amount}" where AssetId="${newAsset.AssetId}"
AND AccountID="${newAsset.AccountId}" End else begin INSERT
INTO asset SET ? end`,
newAsset,
(err, res) => {
if (err) {
console.log("error", err);
result(err, null);
return;
}
result(null, { id: res.insertId, ...newAsset });
}
);
};
Error message:
sqlMessage: 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 'if exists(SELECT * from asset WHERE AssetId="bitcoin" AND AccountID="2c341fed-cf' at line 1,

In MySQL, compound statement syntax such as BEGIN ... END and IF ... THEN ... END IF is supported only in stored routines. You can't use such statements when executing them directly from clients.
https://dev.mysql.com/doc/refman/8.0/en/sql-compound-statements.html says:
This section describes the syntax for the BEGIN ... END compound statement and other statements that can be used in the body of stored programs: Stored procedures and functions, triggers, and events.
In the example you show, you seem to be trying to update a row, and if the row does not exist, then insert it.
One solution if you have a primary key or unique key on the table is to use INSERT ON DUPLICATE KEY UPDATE:
INSERT INTO asset (AssetId, AccountId, Amount) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE Amount = VALUES(Amount);
I'm supposing for the above example that AssetId and AccountId are the primary key.
An alternative is to try the UPDATE first, and in the callback, check result.affectedRows. If this is zero, then try the INSERT.

this is no valid sql code
In SQL you would write something like
SELECT
if (exists(SELECT * from asset WHERE AssetId="${newAsset.AssetId}" AND AccountID="${newAsset.AccountId}")
, #sql := 'UPDATE asset set Amount="${newAsset.Amount}" where AssetId="${newAsset.AssetId}" AND AccountID="${newAsset.AccountId}" ',
#sql := 'INSERT INTO asset SET ? ');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
but this is multi query which mus be eanabled
it needs still some work ys you have to many quotes

Related

How do I use IF THEN ELSE to SELECT and INSERT or UPDATE Queries?

I've been trying to use the following query
IF EXISTS (SELECT id FROM users WHERE id = 1)
THEN
(INSERT INTO users (id, username) VALUES (2, user2))
ELSE
(UPDATE users SET username = 'userUpdated')
But I keep getting
/* SQL Error (1064): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'INSERT INTO users (id) VALUES (2)) ELSE (UPDATE users SET id = 11)' at line 1 */
Also tried using the following query
IF EXISTS (SELECT id FROM users WHERE id = 1)
THEN
(SELECT username FROM users WHERE id = 1)
ELSE
(INSERT INTO users (id, username) VALUES (1, 'user'))
But this time I got
/* SQL Error (1064): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'ELSE
(INSERT INTO users (id, username) VALUES (1, 'user'))' at line 4 */
Am I doing or understood something wrong?
The if statement can only be used in programming blocks, such as stored procedures, functions, and triggers.
In any case, MySQL offers simpler syntax for this functionality: insert . . . on duplicate key update.
To use it, id must have a unique index, unique constraint, or be defined as the primary key. Let me assume that a column so-named is already so-defined.
Then:
INSERT INTO users (id, username)
VALUES (2, user2)
ON DUPLICATE KEY UPDATE username = 'userUpdated';
Are you missing the END IF (and semi colons)?
IF EXISTS (SELECT id FROM users WHERE id = 1)
THEN
(SELECT username FROM users WHERE id = 1);
ELSE
(INSERT INTO users (id, username) VALUES (1, 'user'));
END IF;
Try This.
IF EXISTS (SELECT id FROM users WHERE id = 1)
begin
INSERT INTO users (id, username) VALUES (2, 'user2')
end
ELSE
UPDATE users SET username = 'userUpdated'
In MariaDB 10.1 and newer, you can use compound statements outside of stored procedures. This blog post gives a good description of what you can do with it.
Here's an example the blog:
BEGIN NOT ATOMIC
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
stmt1;
....
stmtN;
COMMIT;
END

Creating a MySQL stored procedure to update records

I'm converting all of my existing MSSQL databases and stored procedures am stuck on a new stored procedure where I need to update an existing record. The procedure gets called from a web form once a record has been inserted into the database and en email sent successfully (or at least passed off to the SMTP server)
I've had a working procedure in MSSQL for a long time but am trying to convert it to MySQL. I'm passing in 3 variables - a bit indicating the email got sent, a string indicating which SMTP server has been used to sent the email and a unique record id so I'll know what record to update. I'm also adding the date and time to another field to know when the procedure ran.
I've got the following but keep getting an error "#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 '' at line 7 - yet I don't see anything off at line 7 - at least to my eye.
The code I'm trying to use is:
CREATE PROCEDURE `sp_Test`(
`emailSent_In` BIGINT UNSIGNED,
`emailTransport_In` VARCHAR(100),
`formSecret_In` VARCHAR(32)
)
BEGIN
SET #`query` := CONCAT('UPDATE ',`tbl_JustSayThanks`,'
SET `emailSent` = `emailSent_In`,
`emailTransport` = ',`emailTransport_In`,',
`emailSentDate` = NOW()
WHERE `formSecret` = ', `formSecret_In`, '');
PREPARE `stmt` FROM #`query`;
EXECUTE `stmt`;
#`query` := NULL;
DEALLOCATE PREPARE `stmt`;
END//
DELIMITER ;
Just FYI, I'm using the CONCAT based on a previous answer I received from wchiquito and will be passing in the table name eventually. But, I wanted to get it to work on a simplified level before going there.
The following is wrong:
SET #`query` := CONCAT('UPDATE ',`tbl_JustSayThanks`,'
because you seem to be concatenating your SQL text with the value of tbl_JustSayThanks, but I think you mean to use the identifier itself. This should therefore be:
SET #`query` := CONCAT('UPDATE `tbl_JustSayThanks`',
The following is wrong:
`emailTransport` = ',`emailTransport_In`,',
because the variable is a VARCHAR but you don't quote it as a string literal in your SQL statement. It's easy to get mixed up with the multiple levels of quoting. It should be:
`emailTransport` = ''', `emailTransport_In`, ''',
The following is wrong for the same reason:
WHERE `formSecret` = ', `formSecret_In`, '');
it should be:
WHERE `formSecret` = ''', `formSecret_In`, '''');
This still suffers from SQL injection problems, unless you can guarantee that the input parameters are safe (which is not a good assumption). If you need to concatenate values into your SQL expressions, you should use the QUOTE() function to do escaping:
SET #query = CONCAT('
UPDATE tbl_JustSayThanks
SET emailSent = ', QUOTE(emailSent_In), '
emailTransport = ', QUOTE(emailTransport_In), '
emailSentDate = NOW()
WHERE formSecret = ', QUOTE(formSecret_In));
More comments:
You don't need to delimit every identifier with back-ticks, only those that conflict with SQL reserved words, or contain whitespace or punctuation or international characters. None of your identifiers you show require delimiting.
When you use prepared statements, you should use query parameters with the ? placeholders, intead of concatenating variables into the SQL string. You don't quote parameter placeholders in your SQL query. That way you won't run into hard-to-debug syntax errors like the ones you found.
Here's an example showing the fixes:
CREATE PROCEDURE sp_Test(
emailSent_In BIGINT UNSIGNED,
emailTransport_In VARCHAR(100),
formSecret_In VARCHAR(32)
)
BEGIN
SET #query = '
UPDATE tbl_JustSayThanks
SET emailSent = ?,
emailTransport = ?,
emailSentDate = NOW()
WHERE formSecret = ?';
SET #es = emailSent_In;
SET #et = emailTransport_In;
SET #fs = formSecret_In;
PREPARE stmt FROM #query;
EXECUTE stmt USING #es, #et, #fs;
DEALLOCATE PREPARE stmt;
END//
DELIMITER ;
Final comment:
Your example query has no dynamic syntax elements, only dynamic values. So you don't need to use a prepared statement at all.
This is how I'd really write the procedure:
CREATE PROCEDURE sp_Test(
emailSent_In BIGINT UNSIGNED,
emailTransport_In VARCHAR(100),
formSecret_In VARCHAR(32)
)
BEGIN
UPDATE tbl_JustSayThanks
SET emailSent = emailSent_In,
emailTransport = emailTransport_In,
emailSentDate = NOW()
WHERE formSecret = formSecret_In;
END//
DELIMITER ;
You should also be aware that MySQL stored procedures are greatly inferior to Microsoft SQL Server. MySQL doesn't keep compiled stored procedures, it doesn't support packages, it doesn't have a debugger... I recommend you do not use MySQL stored procedures. Use application code instead.

UNIQUE constraint in MySQL rows

I am performing an INSERT query in my database as such:
$query = "INSERT IGNORE INTO user VALUES ('', 0, $safe_email, '$hashed_password')";
$result = $db->query($query);
The 3rd row in the db table is email which I have set a unique constraint to.
If I try to insert a duplicate email, with the above query notice I have the INSERT IGNORE which won't insert the record if it is a duplicate to one that already exists in the db, however it won't give an error or any kind of indication that a duplicate record is trying be inserted.
I want to show a nice error message if a duplicate is found but with the INSERT IGNORE I am struggling to do this because it won't show an error it will just ignore the query.
So I need something like:
IF (duplicate entry found in db){
echo "User already exists";
}
END IF
Use normal insert query and implement the query in try-catch statement. 'Insert' query will fail if you try to insert same email since it is unique constraint. So you can catch the exception as the 'Insert' query fails.
Example:
try {
"your insert query";
} catch (Exception $e) {
"your insert failure exception"
}
NB: you can catch all the exceptions that occurred during the execution of insert query which will be more helpful
As per documentation on INSERT ... IGNORE:
If you use the IGNORE keyword, errors that occur while executing the INSERT statement are ignored. For example, without IGNORE, a row that duplicates an existing UNIQUE index or PRIMARY KEY value in the table causes a duplicate-key error and the statement is aborted. With IGNORE, the row still is not inserted, but no error occurs. Ignored errors may generate warnings instead, although duplicate-key errors do not.
You can issue a show warnings; or any compatible similar statement from your scripting language or SQL interface. If it returns one or more such warnings, may be one of them can be on such insertion issues. Using them, you can show proper error or consoling message to the end user.
Edit 1:
... but ... how do I throw my own error message instead of the default exception when using INSERT without the IGNORE.
You can define a BEFORE INSERT trigger to identify duplicate data row and throw custom error message when found one.
Example:
delimiter //
drop trigger if exists bi_table_trigger //
CREATE TRIGGER bi_table_trigger BEFORE INSERT ON table
FOR EACH ROW
BEGIN
declare rowCount int default 0;
declare error_message varchar(1024) default '';
SELECT COUNT(1) into rowCount FROM table
WHERE email = NEW.email;
IF ( rowCount > 0 ) THEN -- if( rowCount ) -- too works
set error_message =
concat( error_message, 'User with email \'' );
set error_message =
concat( error_message, NEW.email, '\' ' );
set error_message =
concat( error_message, 'already exists' );
-- throw the error
-- User with email '?' already exists ( example )
signal sqlstate 1062 set message_text = error_message;
END IF;
END;//
delimiter ;

Using result of SQL Query as table name in mysql trigger

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.

Use MySql function variables as table name in the query

I need to have a function that increments the certain ID in a table (like auto_increment)
I have smth like this
DELIMITER $$
DROP FUNCTION IF EXISTS `GetNextID`$$
CREATE FUNCTION `GetNextID`(tblName TEXT, increment INT)
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE NextID INT;
SELECT MAX(concat(tblName, 'ID')) + increment INTO NextID FROM concat('table_', tblName);
## SELECT MAX(articleID) + increment INTO NextID FROM table_article;
RETURN NextID;
END$$
DELIMITER ;
INSERT INTO `table_article` ( articleID, articleAlias ) VALUES ( GetNextID('article', 5), 'TEST' );
So i pass two variables: tblName (without table_ prefix), and the increment number. The commented line - SELECT query inside the function itself - works well, but i want to dynamically pass table name to the function and so get data from a certain col of certain table. What am I doing wrong?
The error is:
#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 '('table_', tblName);
RETURN NextID;
END' at line 6
if i simply try to select max value in such a way
SELECT MAX(articleID) + increment INTO NextID FROM tblName;
The error reports that tblName does not exist. How can i tell MySql that this is actually a var passed to the function, not an exact table name? If it is possible.
you need something like
prepare stmp from concat('SELECT MAX(ID) + ', increment, ' INTO NextID FROM table_', tblName);
execute stmp;
deallocate prepare stmp;