Related
I'm running into an error in my stored procedure, and after numerous YT videos and forums, I still have no clue where I'm going wrong. Given what I'm trying to do, it all seems to look correct.
Here's the deal. I take in some information to buy some stock, I use an IF to make sure that I have enough money to make the purchase, I then insert the purchase information into my TRADES table and update the cash balance in ACCOUNTS to reflect the spending of $$.
I can't even test to see if it works correctly because it won't run. The only error I'm getting is at INSERT INTO, in which it says error: INTO (into) is not valid input at this position
I have done ALL of my insert statements the exact same way, and have no idea why this particular syntax is incorrect? Any help would be greatly appreciated! Below are two approaches, both with errors.
CREATE PROCEDURE `BUY` (TID INT,ID INT, CASH INT, T_NAME VARCHAR(4) ,
TCOUNT INT, TBUYDATE DATE, TBUYPRICE INT )
BEGIN
IF (ACCOUNT.CASH_BALANCE >= (TCOUNT * TBUYPRICE),
INSERT INTO TRADES (TRADE_ID, ACCOUNT_ID, TRADE_NAME, TRADE_COUNT, TRADE_BUYDATE, TRADE_BUYPRICE)
VALUES (TID, ID, T_NAME, TCOUNT, TBUYDATE, TBUYPRICE)
AND UPDATE ACCOUNT.CASH_BALANCE
WHERE ACCOUNT.ACCOUNT_ID = ID
SET ACCOUNT.CASH_BALANCE = (ACCOUNT.CASH_BALANCE - (TCOUNT * TBUYPRICE)),
NULL
)
END
I have also tried the following, however I get an error on END missing subclause or other elements before end
CREATE PROCEDURE `BUY` (TID INT,ID INT, CASH INT, T_NAME VARCHAR(4) , TCOUNT
INT, TBUYDATE DATE, TBUYPRICE INT )
BEGIN
IF (ACCOUNT.CASH_BALANCE >= (TCOUNT * TBUYPRICE))
THEN
INSERT INTO TRADES (TRADE_ID, ACCOUNT_ID, TRADE_NAME, TRADE_COUNT,
TRADE_BUYDATE, TRADE_BUYPRICE)
VALUES (TID, ID, T_NAME, TCOUNT, TBUYDATE, TBUYPRICE);
UPDATE ACCOUNT.CASH_BALANCE
SET ACCOUNT.CASH_BALANCE = (ACCOUNT.CASH_BALANCE - (TCOUNT * TBUYPRICE))
WHERE ACCOUNT.ACCOUNT_ID = ID;
ELSE #noinsert
END
There are multiple errors/corrections:
The Delimiter command was not used, so he gets confused on the end of statement and the end of the procedure definition
The account table needs to be selected in an exists statement
I've used a local variable l_cash instead of repeating TCOUNT * TBUYPRICE (Not an error).
The ELSE statement was not necessary and an END IF; was missing.
Update statement corrected.
Here is the corrected code:
DELIMITER $$
CREATE PROCEDURE `BUY` (TID INT,ID INT, CASH INT, T_NAME VARCHAR(4) , TCOUNT
INT, TBUYDATE DATE, TBUYPRICE INT)
BEGIN
DECLARE l_cash INT DEFAULT 0;
SET l_cash = TCOUNT * TBUYPRICE;
IF EXISTS(SELECT 1 FROM Account WHERE ACCOUNT_ID = ID AND CASH_BALANCE >= l_cash) THEN
INSERT INTO TRADES (TRADE_ID, ACCOUNT_ID, TRADE_NAME, TRADE_COUNT,
TRADE_BUYDATE, TRADE_BUYPRICE)
VALUES (TID, ID, T_NAME, TCOUNT, TBUYDATE, TBUYPRICE);
UPDATE ACCOUNT
SET CASH_BALANCE = (CASH_BALANCE - l_cash)
WHERE ACCOUNT_ID = ID;
END IF;
END$$
DELIMITER ;
So, basically I have this sql stored procedure:
drop procedure if exists registrar_cuenta;
delimiter //
create procedure registrar_cuenta (in p_email varchar(255), in p_passwordHash varchar(40), in p_salt varchar(40))
begin
if (exists(select 1 from usuarios where email = p_email and registrado = 0)) then
update usuarios set passwordHash = p_passwordHash, salt = p_salt, fechaRegistro = now(), registrado = 1 where email = p_email and registrado = 0;
else
insert into usuarios (email, passwordHash, salt, fechaRegistro, registrado) values (p_email, p_passwordHash, p_salt, now(), 1);
end if;
end
//
delimiter ;
Which runs great, BUT I want to change this piece of code:
else
insert into usuarios (email, passwordHash, salt, fechaRegistro, registrado) values (p_email, p_passwordHash, p_salt, now(), 1);
end if;
For something like this:
insert into usuarios (email, passwordHash, salt, fechaRegistro, registrado) values (p_email, p_passwordHash, p_salt, now(), 1);
if (inserted_rows == 0) then
alter table usuarios auto_increment = auto_increment - 1;
end if;
The thing is that I have an unique field (email) which can produce a duplicate entry error, if so, then the auto_increment value will increase anyways and I want to avoid that.
Is there any way I could archieve this task?
Sounds like you want to use the ROW_COUNT function.
http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_row-count
MySQL Solution:
You can use alter table ... as in the example below:
alter table usuarios
auto_increment = ( SELECT ( AUTO_INCREMENT - 1 )
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='usuarios' and TABLE_SCHEMA=DATABASE()
);
But, this practice is discouraged. If you define a column with int unsigned max value you can store is 4294967295 and if it is bigint unsigned max value is 18446744073709551615. If the database engine can insert 100,000 records per second, calculate your self, how many hours ( days / months / years ) would it take to cross the max value. Hence, you can omit the auto incremented value that is wasted.
I need a constraint for a mysql-table. The table has the fields 'id', 'ref', 'from' and 'to'. The constraint schould guarantee that there are no datasets with the same 'ref' and a time overlapping (fields 'from' and 'to').
In sql: The following statement should always return '0'.
select count(*)
from `TABLE` d1 inner join `TABLE` d2 on
d1.`ref` = d2.`ref` and d1.`id` <> d2.`id` and
d1.`to` >= d2.`from` and d1.`from`<=d2.`to`
Is there a way to handle this with constrains?
Now I have the following triggers. Thanks for your help!
DELIMITER $$
USE `devtestplandb`$$
CREATE
TRIGGER `db`.`trig1`
BEFORE INSERT ON `db`.`TABLE`
FOR EACH ROW
BEGIN
SET #CNT = (
select count(*)
from `TABLE` d
where d.`ref` = NEW.`ref` and
d.`to` >= NEW.`from` and
d.`from` <= NEW.`to`);
IF #CNT != 0 THEN
CALL error_001();
END IF;
END$$
CREATE
TRIGGER `db`.`trig2`
BEFORE UPDATE ON `db`.`TABLE`
FOR EACH ROW
BEGIN
SET #CNT = (
select count(*)
from `TABLE` d
where d.`ref` = NEW.`ref` and
d.`ID` <> NEW.`ID` and
d.`to` >= NEW.`from` and
d.`from` <= NEW.`to`);
IF #CNT != 0 THEN
CALL error_002();
END IF;
END$$
"Is there a way to handle this with constrains?"
Yes, SQL Standard 2011 supports this kind of scenarios in readable declarative manner:
unique constraint definition
<without overlap specification> ::=
<application time period name> WITHOUT OVERLAPS
In your example:
CREATE TABLE tab (
id INT AUTO_INCREMENT PRIMARY KEY,
ref VARCHAR(100),
from_date DATE,
end_date DATE,
PERIOD FOR ref_period(from_date, end_date),
UNIQUE (ref, ref_period WITHOUT OVERLAPS)
);
And sample inserts:
INSERT INTO tab(ref, from_date, end_date) VALUES ('a', '2020-01-01','2020-03-01');
-- OK
INSERT INTO tab(ref, from_date, end_date) VALUES ('a', '2020-03-01','2020-05-01');
-- OK
INSERT INTO tab(ref, from_date, end_date) VALUES ('a', '2020-04-01','2020-07-01')
-- Duplicate entry 'a-2020-07-01-2020-04-01' for key 'ref
SELECT * FROM tab;
db<>fiddle demo - Maria DB 10.5
This is quite a strange problem, so first I will post the procedure:
DELIMITER $$
USE `blahblahblah`$$
DROP PROCEDURE IF EXISTS `duplicateTradeIn`$$
CREATE DEFINER=`blahblahblah` PROCEDURE `duplicateTradeIn`(duplicate_claim INT(12))
BEGIN
DECLARE valuation INT(12) DEFAULT 0;
DECLARE claim INT(12) DEFAULT 0;
DECLARE serialNumber VARCHAR(255);
DECLARE duplicates INT(12);
DECLARE i INT(12) DEFAULT 1;
DECLARE claimID INT(12);
SET #claimID = duplicate_claim;
SET #duplicates = (SELECT COUNT(*) FROM `duplicates`);
SET #i = 1;
WHILE #i <= #duplicates DO
SET #serialNumber = (SELECT `serial` FROM `duplicates` WHERE id = #i);
INSERT INTO valuations (`pID`,`boughtproduct`,`valuationType`,`valuationAmount`,`working`,`accessories`,`age`,`brand`,`qty`,`created`,`valuationStatus`)
SELECT `pID`,`boughtproduct`,`valuationType`,`valuationAmount`,`working`,`accessories`,`age`,`brand`,`qty`,`created`,`valuationStatus`
FROM valuations WHERE vID = (SELECT vID FROM claims WHERE cID = #claimID);
SET #valuation = (SELECT LAST_INSERT_ID());
INSERT INTO claims (`vID`, `pID`, `email`, `title`, `firstname`, `lastname`, `customerType`, `company`, `position`, `address1`,`address2`,`town`,`county`,`postCode`,`telephone`,`mobile`,`emailBusiness`,`emailConsumer`,`contactPost`,`contactTelephone`,`contactMobile`,`contactEmail`,`invoiceNum`,`invoiceDate`,`invoiceInc`,`reseller`,`dateOfOrder`,`heardAbout`,`salesPerson`,`method`,`cleanseCert`,`blanccoCert`,`created`,`modified`,`noInvEmailDated`,`lateRejEmailSent`,`invAddressEmailSent`,`revalueEmailSent`,`signed`,`dated`,`sessionID`,`received`,`receiptSent`,`processedDate`,`validatedDate`,`rejectedDate`,`rejectReason`,`notes`,`claimStatus`,`origin`,`invoiceexported`)
SELECT #valuation, `pID`, `email`, `title`, `firstname`, `lastname`, `customerType`, `company`, `position`, `address1`,`address2`,`town`,`county`,`postCode`,`telephone`,`mobile`,`emailBusiness`,`emailConsumer`,`contactPost`,`contactTelephone`,`contactMobile`,`contactEmail`,`invoiceNum`,`invoiceDate`,`invoiceInc`,`reseller`,`dateOfOrder`,`heardAbout`,`salesPerson`,`method`,`cleanseCert`,`blanccoCert`,`created`,`modified`,`noInvEmailDated`,`lateRejEmailSent`,`invAddressEmailSent`,`revalueEmailSent`,`signed`,`dated`,`sessionID`,`received`,`receiptSent`,`processedDate`,`validatedDate`,`rejectedDate`,`rejectReason`,`notes`,`claimStatus`,`origin`,`invoiceexported`
FROM claims WHERE cID = #claimID;
SET #claim = (SELECT LAST_INSERT_ID());
INSERT INTO documents (`claimid`,`serial`,`filename`,`filenameOrig`,`filenameTemp`,`filetype`,`filesize`)
SELECT #claim,`serial`,`filename`,`filenameOrig`,`filenameTemp`,`filetype`,`filesize`
FROM documents WHERE claimid = #claimID;
INSERT INTO redemptions (`cID`,`pID`,`prID`,`bundleNo`,`serialNum`,`price`,`cashback`,`created`,`modified`,`claimStatus`)
SELECT #claim,`pID`,`prID`,`bundleNo`,#serialNumber,`price`,`cashback`,`created`,`modified`,`claimStatus`
FROM redemptions WHERE cID = #claimID;
INSERT INTO tradeins (`vID`,`cID`,`valuationType`,`valuationAmount`,`working`,`accessories`,`age`,`created`,`brand`,`claimStatus`)
SELECT #valuation,#claim,`valuationType`,`valuationAmount`,`working`,`accessories`,`age`,`created`,`brand`,`claimStatus`
FROM tradeins WHERE cID = #claimID;
SET #i = (#i + 1);
END WHILE;
END$$
DELIMITER ;
EDIT: Sorry!
The problem is when it tries to insert into documents (So refer to the documents table insert). If I rename the column (claimid) in the WHERE clause and INSERT to cID (by altering the table and query) it works, but otherwise it refuses to insert the new rows when it is named claimid not cID. If you have any ideas or insight into this it would be much appreciated.
To try and clarify:
Documents table with field and query as cID not claimid works.
Documents table with field and query as claimid does not insert rows.
All of the other queries seem to work fine and it only occurred to me to try cID in the documents table as that was the only difference.
Again sorry for the vagueness before and I hope the question is now clearer.
Thanks!
Just to clear this up. It is as I stated and maybe a bug in mySQL? If there is a table with a field where the ID name differs. In this example cID and claimid then it will not recognise the second declared field claimid and not perform the query.
In order for this to work I had to revise the documents table from claimID to cID.
I'm stuck here.
I've got a Procedure that I want to run X* times in a row. (*X is couple of thousands times)
The procedure based on input data does this:
1. Looks for an actions.id, if not found LEAVEs.
2. Looks for users.id, if not found, creates one and uses LAST_INSERT_ID();
3-5. Looks for summaries.id (3 types, total, daily and monthly), if not found, creates one and uses it's ID.
6. Once all required ids are collected, INSERTs new row into actions and either updates the summaries rows in a transaction, so if any fails - it does a ROLLBACK - no harm done.
7. Depending on the outcome SELECTs message.
CREATE PROCEDURE NEW_ACTION(
IN a_date TIMESTAMP,
IN u_name VARCHAR(255),
IN a_name VARCHAR(255),
IN a_chars INT,
IN url VARCHAR(255),
IN ip VARCHAR(15))
lbl_proc: BEGIN
DECLARE a_id, u_id, us_id, usd_id, usm_id, a_day, a_month, error INT;
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET error = 1;
SET error = 0;
SET a_day = DATE_FORMAT(SUBSTRING(a_date ,1,10), '%Y%m%d');
SET a_month = SUBSTRING(a_day, 1, 6);
/* 1. RETREIVING action.id */
SET a_id = (SELECT `id` FROM `actions` WHERE `name` = a_name);
IF a_id IS NULL THEN
SELECT 'error';
LEAVE lbl_proc;
END IF;
/* 2. RETREIVING users.id */
SET u_id = (SELECT `id` FROM `users` WHERE `name` = u_name);
IF u_id IS NULL THEN
INSERT INTO `users` (name) VALUES (u_name);
SET u_id = (SELECT LAST_INSERT_ID());
END IF;
/* 3. RETREIVING user_summaries.id */
SET us_id = (SELECT `id` FROM `users_summaries` WHERE `user_id` = u_id AND `action_id` = a_id);
IF us_id IS NULL THEN
INSERT INTO `users_summaries` (user_id, action_id) VALUES (u_id, a_id);
SET us_id = (SELECT LAST_INSERT_ID());
END IF;
/* 4. RETREIVING user_summaries_days.id */
SET usd_id = (SELECT `id` FROM `users_summaries_days` WHERE `day` = a_day AND `user_id` = u_id AND `action_id` = a_id);
IF usd_id IS NULL THEN
INSERT INTO `users_summaries_days` (day, user_id, action_id) VALUES (a_day, u_id, a_id);
SET usd_id = (SELECT LAST_INSERT_ID());
END IF;
/* 5. RETREIVING user_summaries_months.id */
SET usm_id = (SELECT `id` FROM `users_summaries_months` WHERE `month` = a_month AND `user_id` = u_id AND `action_id` = a_id);
IF usm_id IS NULL THEN
INSERT INTO `users_summaries_months` (month, user_id, action_id) VALUES (a_month, u_id, a_id);
SET usm_id = (SELECT LAST_INSERT_ID());
END IF;
/* 6. SAVING action AND UPDATING summaries */
SET autocommit = 0;
START TRANSACTION;
INSERT INTO `users_actions` (`date`, `user_id`, `action_id`, `chars`, `url`, `ip`) VALUES (a_date, u_id, a_id, a_chars, url, ip);
UPDATE `users_summaries` SET qty = qty + 1, chars = chars + a_chars WHERE id = us_id;
UPDATE `users_summaries_days` SET qty = qty + 1, chars = chars + a_chars WHERE id = usd_id;
UPDATE `users_summaries_months` SET qty = qty + 1, chars = chars + a_chars WHERE id = usm_id;
IF error = 1 THEN
SELECT 'error';
ROLLBACK;
LEAVE lbl_proc;
ELSE
SELECT 'success';
COMMIT;
END IF;
END;
Now, I've got raw data that I want to feed into this procedure. There's currently about 3000 rows.
I tried all the solutions I knew:
A. # mysql -uuser -ppass DB < calls.sql - Using php I've basically created a list of calls like this:
CALL NEW_ACTION('2010-11-01 13:23:00', 'username1', 'actionname1', '100', 'http://example.com/', '0.0.0.0');
CALL NEW_ACTION('2010-11-01 13:23:00', 'username2', 'actionname1', '100', 'http://example.com/', '0.0.0.0');
CALL NEW_ACTION('2010-11-01 13:23:00', 'username1', 'actionname2', '100', 'http://example.com/', '0.0.0.0');
...
This fails always (tried few times) at row 452 where it found two summary IDs (step 3).
I thought this could be due to the fact that earlier (rows 375-376) there are calls for the same user for the same action.
As if mysql didn't update tables in time, so the summary row created in CALL from line 375 isn't yet visible when line 376 gets executed - therefore creating another summary line.
Tought I'd try delaying calls...
B. Using mysql's SLEEP(duration).
This didn't change anything. Execution stops at the very same CALL again.
I'm out of ideas now.
Suggestions and help hugely appreciated.
NOTE: action names and user names repeat.
PS. Bear in mind this is one of my first procedures ever written.
PS2. Running mysql 5.1.52-community-log 64bit (Windows 7U), PHP 5.3.2 and Apache 2.2.17
EDIT
I've removed PHP related part of question to a separate question here.
EDIT2
Ok, I've deleted the first 200 calls from the .sql file. For some reason it went fine past the previous line that was stopping execution. Now it stopped at row 1618.
This would mean, that at one point a newly INSERTed summary row is no visible for a moment, therefore when it happens that one of the following iterations want to SELECT it, it's not yet accessible for them. Is that a MySQL bug?
EDIT3
Now there's another interesting thing I noticed. I investigated where two users_summaries get created. This happens (not always, but if, then it is) when there are two CALLs referring to the same user and action in close proximity. They could be next to each other or separated by 1 or 2 different calls.
If I move one of them (within .sql file) like 50-100 rows lower (executed earlier) than it's fine. I even managed to make the .sql file work as a whole. But this still doesn't really solve the problem. With 3000 rows it's not that bad, but if I had 100000, I'm lost. I can't rely on manual tweaks to .sql file.
This isn't really a solution, but a workaround.
Just to clarify, summary tables had id column as PRIMARY KEY with AUTO_INCREMENT option and indexes on both user_id and action_id column.
My investigation showed that although my procedure was looking for an entry that existed using WHERE user_id = u_id AND action_id = a_id in certain situations it didn't find it causing new row being inserted with the same user_id and action_id values - something I did not want.
Debugging the procedure showed that the summary row I was looking for, although not accessible with WHERE user_id = u_id AND action_id = a_id condition, was properly returned when calling it's id - PRIMARY KEY.
With this find I decided to change format of id column, from UNASIGNED INT with AUTO_INCEREMENT to a CHAR(32) which consisted of:
<user_id>|<action_id>
This meant that I knew exactly what the id of the row I wanted is even before it existed. This solved the problem really. It also enabled me to use INSERT ... ON DUPLICATE KEY UPDATE ... construct.
Below my updated procedure:
CREATE PROCEDURE `NEW_ACTION`(
IN a_date TIMESTAMP,
IN u_name VARCHAR(255),
IN a_name VARCHAR(255),
IN a_chars INT,
IN url VARCHAR(255),
IN ip VARCHAR(15))
SQL SECURITY INVOKER
lbl_proc: BEGIN
DECLARE a_id, u_id, a_day, a_month, error INT;
DECLARE us_id, usd_id, usm_id CHAR(48);
DECLARE sep CHAR(1);
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET error = 1;
SET sep = '|';
SET error = 0;
SET a_day = DATE_FORMAT(SUBSTRING(a_date ,1,10), '%Y%m%d');
SET a_month = SUBSTRING(a_day, 1, 6);
/* RETREIVING action.id */
SET a_id = (SELECT `id` FROM `game_actions` WHERE `name` = a_name);
IF a_id IS NULL THEN
SELECT 'error';
LEAVE lbl_proc;
END IF;
/* RETREIVING users.id */
SET u_id = (SELECT `id` FROM `game_users` WHERE `name` = u_name);
IF u_id IS NULL THEN
INSERT INTO `game_users` (name) VALUES (u_name);
SET u_id = LAST_INSERT_ID();
END IF;
/* SETTING summaries ids */
SET us_id = CONCAT(u_id, sep, a_id);
SET usd_id = CONCAT(a_day, sep, u_id, sep, a_id);
SET usm_id = CONCAT(a_month, sep, u_id, sep, a_id);
/* SAVING action AND UPDATING summaries */
SET autocommit = 0;
START TRANSACTION;
INSERT INTO `game_users_actions` (`date`, `user_id`, `action_id`, `chars`, `url`, `ip`)
VALUES (a_date, u_id, a_id, a_chars, url, ip);
INSERT INTO `game_users_summaries` (`id`, `user_id`, `action_id`, `qty`, `chars`)
VALUES (us_id, u_id, a_id, 1, a_chars)
ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars;
INSERT INTO `game_users_summaries_days` (`id`, `day`, `user_id`, `action_id`, `qty`, `chars`)
VALUES (usd_id, a_day, u_id, a_id, 1, a_chars)
ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars;
INSERT INTO `game_users_summaries_months` (`id`, `month`, `user_id`, `action_id`, `qty`, `chars`)
VALUES (usm_id, a_month, u_id, a_id, 1, a_chars)
ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars;
IF error = 1 THEN
SELECT 'error';
ROLLBACK;
LEAVE lbl_proc;
ELSE
SELECT 'success';
COMMIT;
END IF;
END
Anyway, I still think there's some kind of a bug in MySQL, but I consider problem solved.