hsqldb merge with an IDENTITY column - hsqldb

I have the following hsqldb database:
CREATE TABLE t (id INT IDENTITY, code VARCHAR(10), description VARCHAR(100))
INSERT INTO t (code, description) VALUES ('C1', 'dining table'), ('C2', 'deck chair')
I need to add entries, only if they do not already exist in the table.
I would like to do this in one step (instead of a SELECT step to find out if the entry is in the table, and a second step to INSERT it if that's not the case)
hsqldb has the MERGE operation for this.
however, the problem is that I have a id IDENTITY column that hsqldb should automatically take care of setting the value,
and I don't know how to tell this to the MERGE operation.
MERGE INTO t USING (VALUES(3, 'C3', 'conference table'))
AS vals(x,y,z) ON t.code = vals.y
WHEN NOT MATCHED THEN INSERT VALUES vals.x, vals.y, vals.z
This would work, but I had to give the id (3), and I need that it is hsqldb that automatically sets the id (just as in the INSERT operation above). I would need something like this:
MERGE INTO t (code, description) USING (VALUES('C3', 'conference table'))
AS vals(x,y) ON t.code = vals.x
WHEN NOT MATCHED THEN INSERT VALUES vals.x, vals.y
but this query does not work because the MERGE operation does not allow to specify which columns I am working on "(code, description)".
how can I achieve it?
ps: http://hsqldb.org/doc/2.0/guide/dataaccess-chapt.html#dac_merge_statement

See the INSERT documentation.
You can use the keyword DEFAULT instead of a value. This inserts the default value of a column, or if the column is defined as IDENTITY, the next generated value.

I did not try it but potentially this should work.
This is specification
MERGE INTO <target table> [ [ AS ] <merge correlation name> ]
USING <table reference> ON <search condition>
<merge operation specification>
Now focus on <merge operation specification>. This should be expanded into WHEN MATCHED THEN and/or WHEN NOT MATCHED THEN where you put <merge insert specification>
Which should be regular INSERT statement with. To let HSQL to generate identity you could do 2 things (again theoretical):
Ommit PK column
INSERT [ <left paren> <insert column list> <paren right> ]
[ <override clause> ] VALUES <merge insert value list>
So in your case
MERGE INTO t USING (VALUES(3, 'C3', 'conference table'))
AS vals(x,y,z) ON t.code = vals.y
WHEN NOT MATCHED THEN INSERT (code,description) VALUES vals.y, vals.z
Using identity() method
MERGE INTO t USING (VALUES(3, 'C3', 'conference table'))
AS vals(x,y,z) ON t.code = vals.y
WHEN NOT MATCHED THEN INSERT VALUES identity(), vals.y, vals.z
I will try to test this hypothesis, while I need to something similar.

Related

How to use IF ELSE with ON CONFLICT clause in postgresql?

I have an airflow job upserting the columns of my table on daily basis via INSERT ON CONFLICT statement. The table contains a field updated_mode of type enum which is set to automatic when the row is inserted/updated by job or manual if's done manually.
Now, I want my job to update rows only if updated_mode is set to automatic. How can I do that?
Basically, I want to do something like:
Insert into table (data) values (data) on conflict (fields) if updated_mode=automatic set data=excluded.data else do nothing
You need WHERE clause in ON CONFLICT.
INSERT INTO table_data
VALUES (data)
ON CONFLICT (fields)
DO UPDATE SET data=excluded.data
WHERE EXCLUDED.updated_mode='automatic'
Take a look at db fiddle: https://www.db-fiddle.com/f/qwRzRSFaJx4KDMYn2GEXTe/1
You should use the regular WHERE condition. The magic EXCLUDEED RECORD will contain existing confliting record. Something like that :
Insert into table (data) values (data)
on conflict (fields) do update
set data=excluded.data
WHERE updated_mode=automatic
and fields = EXCLUDEED.fields
I assume that fields is the conflicting field and table name is data
https://www.postgresql.org/docs/release/14.0/
Allow column names in the WHERE clause of ON CONFLICT to be table-qualified (Tom Lane)
Now You can more easily reference excluded column names and the original table columns.
setup the table.
create table test101(id bigint primary key, field1 text, update_mode boolean);
insert into test101 values (1,'a', TRUE);
insert into test101 values (2 ,'b', false);
excluded refer to the part you want to insert.
--this one will insert.
insert into test101(id, field1,update_mode)
values(1,'asdf', TRUE)
on conflict (id)
do update set
field1 = excluded.field1
WHERE
excluded.update_mode= test101.update_mode;
--this will not insert
insert into test101(id, field1,update_mode)
values(2,'asdf', TRUE)
on conflict (id)
do update set
field1 = excluded.field1
WHERE
excluded.update_mode= test101.update_mode;

Oracle merge and third party tables

I work with merge operation like in this example:
http://docs.oracle.com/cd/E11882_01/server.112/e26088/statements_9016.htm
How I can make insert into third party table inside "when matched" and "when not matched" clauses?
UPDATE: May be possible set some flags inside this clauses? For using it at next procedure steps - for execute insert ...
Considering you want to benefit from the MERGE clause to actually UPDATE a table, your suggestion about flags is a pretty good idea IMO. Suppose you want to merge TAB_A and TAB_B into TAB_C, you would 1st need to ALTER TABLE TAB_A ADD (FLAG VARCHAR2(1));.
Then the MERGE would consists in setting up the flags:
MERGE INTO tab_a a
USING (tab_b) b
ON (...)
WHEN MATCHED THEN UPDATE SET a.flag = 'U'
WHEN NOT MATCHED THEN INSERT (..., FLAG) VALUES (..., 'I')
;
You can then update TAB_C like:
INSERT INTO tab_c
SELECT ...
FROM tab_a a
WHERE a.flag = 'I';
and the same kind of UPDATE for lines with 'U' flag. Don't forget to reset the flag in TAB_A when you're finished !

Store and reuse value returned by INSERT ... RETURNING

In PostgreSQL, it is possible to put RETURNING at the end of an INSERT statement to return, say, the row's primary key value when that value is automatically set by a SERIAL type.
Question:
How do I store this value in a variable that can be used to insert values into other tables?
Note that I want to insert the generated id into multiple tables. A WITH clause is, as far as I understand, only useful for a single insert. I take it that this will probably have to be done in PHP.
This is really the result of bad design; without a natural key, it is difficult to grab a unique row unless you have a handle on the primary key;
... that can be used to insert values into other tables?
You can even do that in a single SQL statement using a data-modifying CTE:
WITH ins1 AS (
INSERT INTO tbl1(txt)
VALUES ('foo')
RETURNING tbl1_id
)
INSERT INTO tbl2(tbl1_id)
SELECT * FROM ins1
Requires PostgreSQL 9.1 or later.
db<>fiddle here (Postgres 13)
Old sqlfiddle (Postgres 9.6)
Reply to question update
You can also insert into multiple tables in a single query:
WITH ins1 AS (
INSERT INTO tbl1(txt)
VALUES ('bar')
RETURNING tbl1_id
)
, ins2 AS (
INSERT INTO tbl2(tbl1_id)
SELECT tbl1_id FROM ins1
)
INSERT INTO tbl3(tbl1_id)
SELECT tbl1_id FROM ins1;

SQL Insertion without duplication

Is there a specific command for SQL Server in order to INSERT a lot of rows with the condition : if a row already exists in database doesn't duplicate it during insertion ?
Edited
In a sqlbulkcopy, I'd like to avoid exception because a row is already in the table ?
You can use the MERGE command for this. Example Usage.
CREATE TABLE #A(
[id] [int] NOT NULL PRIMARY KEY CLUSTERED,
[C] [varchar](200) NOT NULL)
MERGE #A AS target
USING (SELECT 3, 'C') AS source (id, C)
ON (target.id = source.id)
/*Uncomment for Upsert Semantics
WHEN MATCHED THEN
UPDATE SET C = source.C */
WHEN NOT MATCHED THEN
INSERT (id, C)
VALUES (source.id, source.C);
Edit Though you say in your edit this is for a bulk copy? You could also investigate the "Ignore Duplicate Keys" option on your index.
How to do it in T-SQL is discussed here (article is a bit old though)

Does DB2 have an "insert or update" statement?

From my code (Java) I want to ensure that a row exists in the database (DB2) after my code is executed.
My code now does a select and if no result is returned it does an insert. I really don't like this code since it exposes me to concurrency issues when running in a multi-threaded environment.
What I would like to do is to put this logic in DB2 instead of in my Java code.
Does DB2 have an insert-or-update statement? Or anything like it that I can use?
For example:
insertupdate into mytable values ('myid')
Another way of doing it would probably be to always do the insert and catch "SQL-code -803 primary key already exists", but I would like to avoid that if possible.
Yes, DB2 has the MERGE statement, which will do an UPSERT (update or insert).
MERGE INTO target_table USING source_table ON match-condition
{WHEN [NOT] MATCHED
THEN [UPDATE SET ...|DELETE|INSERT VALUES ....|SIGNAL ...]}
[ELSE IGNORE]
See:
http://publib.boulder.ibm.com/infocenter/db2luw/v9/index.jsp?topic=/com.ibm.db2.udb.admin.doc/doc/r0010873.htm
https://www.ibm.com/support/knowledgecenter/en/SS6NHC/com.ibm.swg.im.dashdb.sql.ref.doc/doc/r0010873.html
https://www.ibm.com/developerworks/community/blogs/SQLTips4DB2LUW/entry/merge?lang=en
I found this thread because I really needed a one-liner for DB2 INSERT OR UPDATE.
The following syntax seems to work, without requiring a separate temp table.
It works by using VALUES() to create a table structure . The SELECT * seems surplus IMHO but without it I get syntax errors.
MERGE INTO mytable AS mt USING (
SELECT * FROM TABLE (
VALUES
(123, 'text')
)
) AS vt(id, val) ON (mt.id = vt.id)
WHEN MATCHED THEN
UPDATE SET val = vt.val
WHEN NOT MATCHED THEN
INSERT (id, val) VALUES (vt.id, vt.val)
;
if you have to insert more than one row, the VALUES part can be repeated without having to duplicate the rest.
VALUES
(123, 'text'),
(456, 'more')
The result is a single statement that can INSERT OR UPDATE one or many rows presumably as an atomic operation.
This response is to hopefully fully answer the query MrSimpleMind had in use-update-and-insert-in-same-query and to provide a working simple example of the DB2 MERGE statement with a scenario of inserting AND updating in one go (record with ID 2 is updated and record ID 3 inserted).
CREATE TABLE STAGE.TEST_TAB ( ID INTEGER, DATE DATE, STATUS VARCHAR(10) );
COMMIT;
INSERT INTO TEST_TAB VALUES (1, '2013-04-14', NULL), (2, '2013-04-15', NULL); COMMIT;
MERGE INTO TEST_TAB T USING (
SELECT
3 NEW_ID,
CURRENT_DATE NEW_DATE,
'NEW' NEW_STATUS
FROM
SYSIBM.DUAL
UNION ALL
SELECT
2 NEW_ID,
NULL NEW_DATE,
'OLD' NEW_STATUS
FROM
SYSIBM.DUAL
) AS S
ON
S.NEW_ID = T.ID
WHEN MATCHED THEN
UPDATE SET
(T.STATUS) = (S.NEW_STATUS)
WHEN NOT MATCHED THEN
INSERT
(T.ID, T.DATE, T.STATUS) VALUES (S.NEW_ID, S.NEW_DATE, S.NEW_STATUS);
COMMIT;
Another way is to execute this 2 queries. It's simpler than create a MERGE statement:
update TABLE_NAME set FIELD_NAME=xxxxx where MyID=XXX;
INSERT INTO TABLE_NAME (MyField1,MyField2) values (xxx,xxxxx)
WHERE NOT EXISTS(select 1 from TABLE_NAME where MyId=xxxx);
The first query just updateS the field you need, if the MyId exists.
The second insertS the row into db if MyId does not exist.
The result is that only one of the queries is executed in your db.
I started with hibernate project where hibernate allows you to saveOrUpdate().
I converted that project into JDBC project the problem was with save and update.
I wanted to save and update at the same time using JDBC.
So, I did some research and I came accross ON DUPLICATE KEY UPDATE :
String sql="Insert into tblstudent (firstName,lastName,gender) values (?,?,?)
ON DUPLICATE KEY UPDATE
firstName= VALUES(firstName),
lastName= VALUES(lastName),
gender= VALUES(gender)";
The issue with the above code was that it updated primary key twice which is true as
per mysql documentation:
The affected rows is just a return code. 1 row means you inserted, 2 means you updated, 0 means nothing happend.
I introduced id and increment it to 1. Now I was incrementing the value of id and not mysql.
String sql="Insert into tblstudent (id,firstName,lastName,gender) values (?,?,?)
ON DUPLICATE KEY UPDATE
id=id+1,
firstName= VALUES(firstName),
lastName= VALUES(lastName),
gender= VALUES(gender)";
The above code worked for me for both insert and update.
Hope it works for you as well.