MYSQL Procedures - INSERT INTO Return value - mysql

I have the MYSQL stored procedure defined as shown below:
DROP PROCEDURE IF EXISTS TEST_LOCATION;
CREATE PROCEDURE `TEST_LOCATION`()
BEGIN
DECLARE VCount tinyint(1) DEFAULT 1;
INSERT INTO my_counts_model (type,code,model_code,count)
VALUES ( 1,1,456,1)
ON DUPLICATE KEY UPDATE count = count + 1;
END;
The procedure updates the count if the type,code,model_code has the value in table, otherwise inserts a new record. I mean the key is on type,code,model_code.
I want to get whether it is a new insert or the count update for further operations. I am trying to check for the return value of the INSERT INTO, but couldn't find syntax or any solution.

http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_row-count
"ROW_COUNT(): For INSERT ... ON DUPLICATE KEY UPDATE statements, the affected-rows value is 1 if the row is inserted as a new row and 2 if an existing row is updated."

You also can do something like this -
add new 'flag' field to the table and set 1 on INSERT or 2 on UPDATE, e.g.
INSERT INTO my_counts_model (type, code, model_code, count, flag)
VALUES ( 1, 1, 456, 1, 1)
ON DUPLICATE KEY UPDATE count = count + 1, flag = 2;
In this case the information will be stored in the table.

Related

Insert row from a transactional database into a star schema through stored procedure

I need to transform data from transactional database into a star schema. I already created all dimension tables and one fact table (cinjenica) as shown on ERD picture here: left(star-schema) right(transactional database table)
I also created Unique index ON dimension tables so to limit maximum theoretical row count for example in dimenzija_platforma i have UNIQUE index on (widows, linux, mac) atributes, and those atributes can have values 1 or non-one (binary value) which yields 2^3=8 max theoretical rows. Same is done for other dimension tables that will be derived from transactional database table with 29k rows.
Table cinjenica(fact table) contains compound PK/FK and all foreign keys have ON UPDATE CASCADE.
Now to input the data inside my star schema i created following stored procedure:
CREATE DEFINER=`root`#`localhost` PROCEDURE `new_row_to_star_scheme`()
BEGIN
DECLARE i INTEGER;
DECLARE total_row_count INTEGER;
select appid from sppi.steam;
SELECT FOUND_ROWS() into total_row_count;
SET i = 0;
ponavljanje: LOOP
IF i > total_row_count THEN
LEAVE ponavljanje;
END IF;
INSERT INTO dimenzija_datum(ID,datum)
SELECT last_insert_id(),s.release_date
FROM steam as s LIMIT i,1
ON DUPLICATE KEY UPDATE ID=last_insert_id(ID);
SET #row_datum = last_insert_id();
INSERT INTO dimenzija_vlasnik(ID,developer,publisher)
SELECT last_insert_id(),s.developer, s.publisher
FROM steam as s LIMIT i,1
ON DUPLICATE KEY UPDATE ID=last_insert_id(ID);
SET #row_vlasnik = last_insert_id();
INSERT INTO dimenzija_platforme(ID,tekstualno)
SELECT last_insert_id(),s.platforms
FROM steam as s LIMIT i,1
ON DUPLICATE KEY UPDATE ID=last_insert_id(ID);
SET #row_platforme = last_insert_id();
INSERT INTO dimenzija_kategorija(ID,zanr_tagovi,tip_tagovi)
SELECT last_insert_id(),s.genres, s.categories
FROM steam as s LIMIT i,1
ON DUPLICATE KEY UPDATE ID=last_insert_id(ID);
SET #row_kategorija = last_insert_id();
INSERT INTO cinjenica(fk_datum,fk_vlasnik,fk_platforma,fk_kategorija,naziv,positive_rating,negative_rating,avg_playtime,median_playtime,cijena,owners)
SELECT #row_datum, #row_vlasnik, #row_platforme, #row_kategorija, s.name, s.positive_ratings, s.negative_ratings, s.average_playtime, s.median_playtime, s.price, s.owners
FROM steam as s LIMIT i,1
ON DUPLICATE KEY UPDATE ID=last_insert_id(ID);
SET i = i+1;
END LOOP;
END
All other data that isn't inserted into dimensions from shown procedure is created with BEFORE INSERT triggers on each dimension table.
With this stored procedure I can populate my star schema until any of dimension tables encounters duplicate row after that I get Error code 1054 Unknown column 'ID' in 'field list'. After each INSERT INTO if there is duplicate data - ON DUPLICATE KEY UPDATE should update primary key of dimension table(foreign key for fact table) and trigger ON UPDATE CASCADE so in theory it should work. How should I correct my procedure to populate my star schema ?

How to make unique incrementing counter in MySQL

How to assign unique auto incrementing values to a certain column? Kind of like AUTO_INCREMENT does but it should be NULL at the time of insertion and assigned at some later point.
I have a table that gets regular data inserts and a few workers that process that data and set processed_at datetime field when they're done. Now I want incrementally select new processed rows since the last call. If I naively use where processed_at > #last_update_time I'm afraid there might be a situation where some records are processed at the same second and I miss some rows.
update: Can I just do
begin;
select #max := max(foo) from table1;
update table1 set foo = #max + 1 where id = 'bar' limit 1;
commit;
if foo column is indexed?
You can use a trigger to implement that.
CREATE TABLE my_increment (value INT, table_name TEXT);
INSERT INTO my_increment VALUES (0, 'your_table_name');
CREATE TRIGGER pk AFTER UPDATE ON your_table_name
BEGIN
UPDATE my_increment
SET value = value + 1
WHERE table_name = 'your_table_name';
UPDATE your_table_name
SET ID2 = (
SELECT value
FROM my_increment
WHERE table_name = 'your_table_name')
WHERE ROWID = new.ROWID;
END;
But bear in mind that this trigger will work on every execution of the Update query.
You can also do it manually:
Create the table to store increment value:
CREATE TABLE my_increment (value INT, table_name TEXT);
INSERT INTO my_increment VALUES (0, 'your_table_name');
Then when you want to update the table, get the last value from this table and insert value+1 to your column needed to be incremented.

Mysql trigger add number to new value on duplicate key

I'm trying to import data to a new table, but turns out some values from two different columns are duplicate, but it seems to not be working. This is what my trigger looks like:
DELIMITER //
CREATE TRIGGER insert_specificationattributeoption_child AFTER INSERT ON import_specificationattributeoption FOR EACH ROW
BEGIN
INSERT INTO t_virtuemart_customs (virtuemart_custom_id, custom_parent_id, custom_title, show_title, field_type, custom_params, created_on, created_by, ordering, modified_on, modified_by)
VALUES (NEW.option_id, NEW.specification_attribute_id, NEW.option_name, lower(NEW.option_name), 'S', 0, current_time(), 633, NEW.display_order, current_time(), 633)
ON DUPLICATE KEY UPDATE NEW.option_id = NEW.option_id + 5000;
END //
With the trigger I don't get the duplicate key error, but I don't see any key being over 5000. What am I doing wrong there? Would it be better to "find" the highest id and adding the new id to it instead?
Edit: Basically what I want to do is, if I'm inserting a primary key that already exists, change the value I want to insert.
I am not clear what you are trying to do. The trigger must be detecting the duplicate key because no duplicate error is being shown but UPDATE NEW.option_id = NEW.option_id + 5000 has no effect on the table. If you want to amend the table then the syntax would be UPDATE option_id = NEW.option_id + 5000 -note this also amends the next auto_increment value to NEW.option_id + 5000 + 1.

Insert only when auto-increment id is not equal 6(for example)?

I have a table with 3 fields: Id(PK,AI), Name(varchar(36)), LName(varchar(36)).
I have to insert name and last name, Id inserts automatically because of it's constraints,
Is There a way to Jump id auto increment value when it reaches 6?
for instance do this 7 times:
Insert Into table(Name, LName) Values ('name1', 'lname1') "And jump id to 7 if it is going to be 6"
It may sound stupid to do this but I have the doubt.
Also Jump and do not record id 6.
record only, 1-5, 7,8,9 and so on
What I want to achieve starts from a Union:
Select * From TableNames
Union All
Select * From TableNames_general
In the TableNames_general I assign it's first value so that when the user sees the table for the first time it will be displayed the record I inserted.
The problem comes when the user inserts a new record, if the Id of the inserted record is the same as the one I have inserted it will be duplicated, that is why I want to achieve when the users inserts one record and if the last insert id already exists just jump that record. this is because I must have different ids due to its relationship among child tables.
Identity column generate values for you, And its best left this way, You have the ability to insert specific values in Identity column but its best left alone and let it generate values for you.
Imagine you have inserted a value explicitly in an identity column and then later on Identity column generates the same value for you, you will end up with duplicates.
If you want to have your input in that column then why bother with identity column anyway ??
Well this is not the best practice but you can jump to a specific number by doing as follows:
MS SQL SERVER 2005 and Later
-- Create test table
CREATE TABLE ID_TEST(ID INT IDENTITY(1,1), VALUE INT)
GO
-- Insert values
INSERT INTO ID_TEST (VALUE) VALUES
(1),(2),(3)
GO
-- Set idnentity insert on to insert values explicitly in identity column
SET IDENTITY_INSERT ID_TEST ON;
INSERT INTO ID_TEST (ID, VALUE) VALUES
(6, 6),(8,8),(9,9)
GO
-- Set identity insert off
SET IDENTITY_INSERT ID_TEST OFF;
GO
-- 1st reseed the value of identity column to any smallest value in your table
-- below I reseeded it to 0
DBCC CHECKIDENT ('ID_TEST', RESEED, 0);
-- execute the same commad without any seed value it will reset it to the
-- next highest idnetity value
DBCC CHECKIDENT ('ID_TEST', RESEED);
GO
-- final insert
INSERT INTO ID_TEST (VALUE) VALUES
(10)
GO
-- now select data from table and see the gap
SELECT * FROM ID_TEST
If you query the database to get the last inserted ID, then you can check if you need to increment it, by using a parameter in the query to set the correct ID.
If you use MSSQL, you can do the following:
Before you insert check for the current ID, if it's 5, then do the following:
Set IDENTITY_INSERT to ON
Insert your data with ID = 7
Set IDENTITY_INSERT to OFF
Also you might get away with the following scenario:
check for current ID
if it's 5, run DBCC CHECKIDENT (Table, reseed, 6), it will reseed the table and in this case your next identity will be 7
If you're checking for current identity just after INSERT, you can use SELECT ##IDENTITY or SELECT SCOPE_IDENTITY() for better results (as rcdmk pointed out in comments)
Otherwise you can just use select: SELECT MAX(Id) FROM Table
There's no direct way to influence the AUTO_INCREMENT to "skip" a particular value, or values on a particular condition.
I think you'd have to handle this in an AFTER INSERT trigger. An AFTER INSERT trigger can't update the values of the row that was just inserted, and I don't think it can make any modifications to the table affected by the statement that fired the trigger.
A BEFORE INSERT trigger won't work either, because the value assigned to an AUTO_INCREMENT column is not available in a BEFORE INSERT trigger.
I don't believe there's a way to get SQL Server IDENTITY to "skip" a particular value either.
UPDATE
If you need "unique" id values between two tables, there's a rather ugly workaround with MySQL: roll your own auto_increment behavior using triggers and a separate table. Rather than defining your tables with AUTO_INCREMENT attribute, use a BEFORE INSERT trigger to obtain a value.
If an id value is supplied, and it's larger than the current maximum value from the auto_increment column in the dummy auto_increment_seq table, we'd need to either update that row, or insert a new one.
As a rough outline:
CREATE TABLE auto_increment_seq
(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT) ENGINE=MyISAM;
DELIMITER $$
CREATE TRIGGER TableNames_bi
BEFORE INSERT ON TableNames
FOR EACH ROW
BEGIN
DECLARE li_new_id INT UNSIGNED;
IF ( NEW.id = 0 OR NEW.id IS NULL ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NULL);
SELECT LAST_INSERT_ID() INTO li_new_id;
SET NEW.id = li_new_id;
ELSE
SELECT MAX(id) INTO li_max_seq FROM auto_increment_seq;
IF ( NEW.id > li_max_seq ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NEW.id);
END IF;
END IF;
END$$
CREATE TRIGGER TableNames_ai
AFTER INSERT ON TableNames
FOR EACH ROW BEGIN
DECLARE li_max_seq INT UNSIGNED;
SELECT MAX(id) INTO li_max_seq FROM auto_increment_seq;
IF ( NEW.id > li_max_seq ) THEN
INSERT INTO auto_increment_seq (id) VALUES (NEW.id);
END IF;
END;
DELIMITER ;
The id column in the table could be defined something like this:
TableNames
( id INT UNSIGNED NOT NULL DEFAULT 0 PRIMARY KEY
COMMENT 'populated from auto_increment_seq.id'
, ...
You could create an identical trigger for the other table as well, so the two tables are effectively sharing the same auto_increment sequence. (With less efficiency and concurrency than an Oracle SEQUENCE object would provide.)
IMPORTANT NOTES
This doesn't really insure that the id values between the tables are actually kept unique. That would really require a query of the other table to see if the id value exists or not; and if running with InnoDB engine, in the context of some transaction isolation levels, we might be querying a stale (as in, consistent from the point in time at the start of the transaction) version of the other table.
And absent some additional (concurrency killing) locking, the approach outline above is subject to a small window of opportunity for a "race" condition with concurrent inserts... the SELECT MAX() from the dummy seq table, followed by the INSERT, allows a small window for another transaction to also run a SELECT MAX(), and return the same value. The best we can hope for (I think) is for an error to be thrown due to a duplicate key exception.
This approach requires the dummy "seq" table to use the MyISAM engine, so we can get an Oracle-like AUTONOMOUS TRANSACTION behavior; if inserts to the real tables are performed in the context of a REPEATABLE READ or SERIALIZABLE transaction isolation level, reads of the MAX(id) from the seq table would be consistent from the snapshot at the beginning of the transaction, we wouldn't get the newly inserted (or updated) values.
We'd also really need to consider the edge case of an UPDATE of row changing the id value; to handle that case, we'd need BEFORE/AFTER UPDATE triggers as well.

finding value of column after ON DUPLICATE KEY UPDATE

Consider this statement:
INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE d=d+1;
I need the value of d.
Is it possible to obtain it without performing a further SELECT?
There is a unique index on a,b,c. Would this index be used for better performance? This table will have a large number of rows.
Assuming you will be running both queries using the same connection, You can use the LAST_INSERT_ID(expr) function to set the current value of d, and check the value of LAST_INSERT_ID() together with the ROW_COUNT() function to find out if the record was inserted or updated:
INSERT INTO table (a,b,c)
VALUES (1,2,3)
ON DUPLICATE KEY UPDATE d = LAST_INSERT_ID(d + 1);
SELECT IF(ROW_COUNT() = 2, LAST_INSERT_ID(), 0 /*default value*/) ;
You can also use session variables:
INSERT INTO table (a,b,c)
VALUES (1,2,3)
ON DUPLICATE KEY UPDATE d = #tmp := (d + 1);
SELECT IF(ROW_COUNT() = 2, #tmp, 0 /*default value*/) ;