How to get stored variable value from mysql transaction - mysql

I have written a transaction for mySQL InnoDB engine.
It has an insert in a table with auto generate key, than another insert using that auto generate key which I got using LAST_INSERT_ID().
Now after this second insert I have several inserts which need a foreign key for auto generated key from last table in which I have inserted.
So what I did was made a variable and used it is all of them.
Now, I need that auto generated key value to be returned in my Java program so I can use it.
How do I do it?
My transaction is fairly large so here is what I am trying to do.
start transaction;
insert into a(value) values(123);
insert into b(aid,value) values((select LAST_INSERT_ID()),345);
SET #KEY = ( select LAST_INSERT_ID() ) ;
insert into c(val,fk) values(1,#KEY);
insert into c(val,fk) values(2,#KEY);
insert into c(val,fk) values(3,#KEY);
.....
insert into c(val,fk) values(10,#KEY);
Now I need the #KEY variable value to be returned back in my program.
Its Java program I am using J connector for MySQL (If it matters).

MySQL variables are session-scoped you can do the following anywhere you want as long as you're using the same connection :
SELECT #KEY;
For more information, the manual is your friend : https://dev.mysql.com/doc/refman/5.0/en/user-variables.html

Related

Getting id generated in a trigger for further requests

I have a table with two columns:
caseId, referring to a foreign table column
caseEventId, int, unique for a given caseId, which I want to auto-increment for the same caseId.
I know that the auto-increment option based on another column is not available in mySql with InnoDb:
MySQL Auto Increment Based on Foreign Key
MySQL second auto increment field based on foreign key
So I generate caseEventId into a trigger. My table:
CREATE TABLE IF NOT EXISTS mydb.caseEvent (
`caseId` CHAR(20) NOT NULL,
`caseEventId` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`caseId`, `caseEventId`),
# Foreign key definition, not important here.
ENGINE = InnoDB;
And my trigger:
CREATE DEFINER=`root`#`%` TRIGGER `mydb`.`caseEvent_BEFORE_INSERT` BEFORE INSERT ON `caseEvent` FOR EACH ROW
BEGIN
SELECT COALESCE((SELECT MAX(caseEventId) + 1 FROM caseEvent WHERE caseId = NEW.caseId),0)
INTO #newCaseEventId;
SET NEW.`caseEventId` = #newCaseEventId;
END
With this, I get my caseEventId which auto-increments.
However I need to re-use this new caseEventId in further calls within my INSERT transaction, so I place this id into #newCaseEventId within the trigger, and use it in following instructions:
START TRANSACTION;
INSERT INTO mydb.caseEvent (caseId) VALUES ('fziNw6muQ20VGYwYPW1b');
SELECT #newCaseEventId;
# Do stuff based on #newCaseEventId
COMMIT;
This seems to work just fine but... what about concurrency, using connection pools etc...?
Is this #newCaseEventId variable going to be shared with all clients using the same connection, can I run into problems when my client server launches two concurrent transactions? This is using mysql under nodejs.
Is this safe, or is there a safer way to go about this? Thanks.
Edit 2020/09/24
FYI I have dropped this approach altogether. I was trying to use the db in a way it isn't meant to be used.
Basically I have dropped caseEventId, and any index which is supposed to increment nicely based on a given column value.
I rely instead on properly written queries on the read side, when I retrieve data, to recreate my caseEventId field...
That is no problem, the user defined variables a per client.
That means every user has its own use defined varoables
User-defined variables are session specific. A user variable defined by one client cannot be seen or used by other clients. (Exception: A user with access to the Performance Schema user_variables_by_thread table can see all user variables for all sessions.) All variables for a given client session are automatically freed when that client exits.
see manul

Prevent Auto Increment Skip On Duplicate Key Update

I have a table with
(ID INT auto_incrment primary key,
tag VARCHAR unique)
I want to insert multiple tags at one. Like this:
INSERT INTO tags (tag) VALUES ("java"), ("php"), ("phyton");
If I would execute this, and "java" is already in the table, I'm getting an error. It doesn't add "php" and "python".
If I do it like this :
INSERT INTO tags (tag) VALUES ("java"), ("php"), ("phyton")
ON DUPLICATE KEY UPDATE tag = VALUES(tag)
it gets added without an error, but it skips 2 values at the ID field.
Example: I have Java with ID = 1 and I run the query. Then PHP will be 3 and Phyton 4. Is there a way to execute this query without skipping the IDs?
I don't want big spaces between them. I also tried INSERT IGNORE.
Thank you!
See "SQL #1" in http://mysql.rjweb.org/doc.php/staging_table#normalization . It is more complex but avoids 'burning' ids. It has the potential drawback of needing the tags in another table. A snippet from that link:
# This should not be in the main transaction, and it should be
# done with autocommit = ON
# In fact, it could lead to strange errors if this were part
# of the main transaction and it ROLLBACKed.
INSERT IGNORE INTO Hosts (host_name)
SELECT DISTINCT s.host_name
FROM Staging AS s
LEFT JOIN Hosts AS n ON n.host_name = s.host_name
WHERE n.host_id IS NULL;
By isolating this as its own transaction, we get it finished in a hurry, thereby minimizing blocking. By saying IGNORE, we don't care if other threads are 'simultaneously' inserting the same host_names. (If you don't have another thread doing such INSERTs, you can toss the IGNORE.)
(Then it goes on to talk about IODKU.)
INNODB engine Its main feature is to support ACID type transactions.
What it usually does that I point out is not a "problem", is that the engine will "reserve" the id before knowing if it is a duplicate or not.
This is a solution, but it depends on your table, if we are talking about a very large one you should do some tests first because the AUTO_INCREMENT function helps you to follow the ordering of the id.
I'll give you some examples:
INSERT INTO tags (java,php,python) VALUES ("val1"), ("val2"), ("val3")
ON DUPLICATE KEY UPDATE java = VALUES(java), id = LAST_INSERT_ID(id);
SELECT LAST_INSERT_ID();
ALTER TABLE tags AUTO_INCREMENT = 1;
Note: I added LAST_INSERT_ID () to you because every time you insert or update it always gives you an inserted or reserved id.
Each time INSERT INTO is called, AUTO_INCREMNT must be followed.

In MySQL, how can I use the autoincrement value in another column at the time of the INSERT?

I am looking to have the automagically set autoincrement included in another column during the insert. For example in a table where ID is the autoincrement and Name is the other column, I'd like to do something like
`INSERT INTO Names (Name) VALUES (CONCAT("I am number ",ID));`
Currently, I do the INSERT without Name, then I have to immediately after do an UPDATE using $mysqli->insert_id.
I don't want to query the table in advance because, as small a time as it may be, another record could get inserted between getting the next autoincrement value and the insertion. A trigger could work, but it seems like overkill. I just want to know if I can reference the autoincrement within the insertion.
Many thanks!
The problem is not as easy as it seems. In a BEFORE INSERT trigger, the autoincrement value hasn't been generated yet (NEW.autoinc_column is 0), and in an AFTER INSERT trigger, it is not possible anymore to change the values to be inserted.
With MyISAM tables, you could check the table definition for the next AUTO_INCREMENT value:
DELIMITER //
CREATE TRIGGER inserName BEFORE INSERT ON name FOR EACH ROW
BEGIN
DECLARE next_ai INT;
SELECT auto_increment INTO next_ai
FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = 'name';
SET NEW.name = CONCAT("I am number ", next_ai);
END //
DELIMITER ;
I believe this could work too with InnoDB tables if innodb_autoinc_lock_mode = 0 (not the case by default), but I am not sure (because of possible concurrency issues).
But if the value to concat is always the same, you probably had better using a view such as:
CREATE VIEW names_v AS SELECT id, CONCAT("I am number ", id) AS name FROM names;
I've also recently been facing this problem and although it might not be the best solution, what I did could be a viable alternative for your case. In my case it was sufficient. You could use a AFTER INSERT trigger to update the field with the value refering to the pseudo-variable NEW. This will probably give you a bit more flexibility.
I needed to fill a field with a string that was computed using the value from the auto increment column.
The trigger reads the NEW pseudo-variable, computes the necessary values and executes the UPDATE.
It still does require two high level write-acesses to the database, but all are done consecutively and without the need for further interaction with the client application (effectively, one single insert statement being sent from the client application, being followed by the implicit update).

How to increment the Identity column

I am using identity columns as a primary key in my tables.
In some situations I need to work with primary keys before inserting a new row.
For example, in Oracle I use : select <sequence_name>.nextval into <variable> from dual
and I was sure that no one will insert any row with the same ID while my sp was executing.
As for SQL Server I can read the current identity value and it's increment, but there is no way to increment it without inserting a row.
Updated: The question is - how can I accomplish my task to work with ID (as identity column) in SQL Server before inserting a row and be sure that it will be unique at the end of my stored procedure.
Updated:I have a table with HierarchyId column.The way to form the first level of hierarchy,in my case, is to insert the hierarchyId column, according to indentity column. That is how I'v done it now:
begin transaction
insert into [dbo].[Group](GroupTypeId,CompanyOwnerId,GroupHierarchyId)
values(#GroupTypeId,#HeaderCompanyId,null)
update [dbo].[Group]
set GroupHierarcyId=hierarchyid::GetRoot().GetDescendant(cast ('/'+cast(#NewGroupId as varchar)+'/' as hierarchyid),null)
where GroupId=scope_identity()
commit
You can put an exclusive lock on the table, get the maximum ID, add 1 to it. That will be your next ID. Insert your data, the unlock the table.
HOWEVER,
I cannot fathom why you would want to work with a value before it is created. Can yo post a bit more information on that?
If you need a key that would be unique across databases and database servers, then the GUID's (Global Unique Identifier) certainly fulfills this need.
If you want to generate a new GUID server the you can simply use the NEWID() function
SELECT NEWID()

Last Inserted Row info without Auto-Incrementing PK

I've got a number of tables that "share" a single auto-incrementing primary key - this is accomplished via a trigger on insert which looks like this:
FOR EACH ROW
BEGIN
INSERT INTO master (time) VALUES (NOW());
SET NEW.id = LAST_INSERT_ID();
END
This produces the PK for the just inserted row. This does, however, create the problem that I can't seem to figure out what that ID was. last_insert_id obviously returns nothing as the above statement wasn't executed on what's considered "the current connection".
Is there a way to access the most recently inserted row on a connection without an auto-incrementing primary key?
Update: As a temporary(?) measure I've removed the trigger and now generate the ID by making the insert to master within my model. Just seems like it would be nicer if I could somehow return the value that the trigger set.
The doc does say, "For stored functions and triggers that change the [LAST_INSERT_ID] value, the value is restored when the function or trigger ends, so following statements do not see a changed value."
Try a stored procedure, which can do your two INSERTS and return the assigned ID.
Or, give up on doing things the "Oracle way", drink the MySql Kool-Aid, and just use an auto-incrementing id on the table.