Oracle merge constants into single table - oracle

In Oracle, given a simple data table:
create table data (
id VARCHAR2(255),
key VARCHAR2(255),
value VARCHAR2(511));
suppose I want to "insert or update" a value. I have something like:
merge into data using dual on
(id='someid' and key='testKey')
when matched then
update set value = 'someValue'
when not matched then
insert (id, key, value) values ('someid', 'testKey', 'someValue');
Is there a better way than this? This command seems to have the following drawbacks:
Every literal needs to be typed twice (or added twice via parameter setting)
The "using dual" syntax seems hacky
If this is the best way, is there any way around having to set each parameter twice in JDBC?

I don't consider using dual to be a hack. To get rid of binding/typing twice, I would do something like:
merge into data
using (
select
'someid' id,
'testKey' key,
'someValue' value
from
dual
) val on (
data.id=val.id
and data.key=val.key
)
when matched then
update set data.value = val.value
when not matched then
insert (id, key, value) values (val.id, val.key, val.value);

I would hide the MERGE inside a PL/SQL API and then call that via JDBC:
data_pkg.merge_data ('someid', 'testKey', 'someValue');
As an alternative to MERGE, the API could do:
begin
insert into data (...) values (...);
exception
when dup_val_on_index then
update data
set ...
where ...;
end;

I prefer to try the update before the insert to save having to check for an exception.
update data set ...=... where ...=...;
if sql%notfound then
insert into data (...) values (...);
end if;
Even now we have the merge statement, I still tend to do single-row updates this way - just seems more a more natural syntax. Of course, merge really comes into its own when dealing with larger data sets.

Related

Mybatis, insert in Oracle with sequence id

I have tried with this:
<insert id="insertPersonalizacionUsuario" useGeneratedKeys="true" keyProperty="param1.id" keyColumn="id">
INSERT INTO dsk_prop_personali (idpersonalizacion, idusuario, valor, centro)
VALUES (#{param1.idPersonalizacion}, #{param1.idUsuario}, #{param1.valor}, #{param2})
And with this:
<insert id="insertPersonalizacionUsuario" useGeneratedKeys="true" keyProperty="param1.id" keyColumn="id">
<selectKey keyProperty="id" resultType="int">
SELECT id.nextVal from dual
</selectKey>
INSERT INTO dsk_prop_personali (id, idpersonalizacion, idusuario, valor, centro)
VALUES (#{id}, #{param1.idPersonalizacion}, #{param1.idUsuario}, #{param1.valor}, #{param2})
But not working. Thanks
You must add the order attribute with BEFORE value to <selectKey> element. In your case you are using an Oracle database which until version 12c (review your case) it doesn't have auto-generated column types and works with a sequence is not related with your column by the rdbms.
If you take a look the documentation reference there is a section which explains your case:
MyBatis has another way to deal with key generation for databases that
don't support auto-generated column types, or perhaps don't yet
support the JDBC driver support for auto-generated keys.
Here's a simple (silly) example that would generate a random ID
(something you'd likely never do, but this demonstrates the
flexibility and how MyBatis really doesn't mind):
<insert id="insertAuthor">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
insert into Author
(id, username, password, email,bio, favourite_section)
values
(#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
In the example above, the selectKey statement would be run first, the
Author id property would be set, and then the insert statement would
be called. This gives you a similar behavior to an auto-generated key
in your database without complicating your Java code.
So, to be sure the selectKey statement will run first, you would need to use the Order attribute with BEFORE value, the attribute is explained very good after this example in the reference documentation:
order This can be set to BEFORE or AFTER. If set to BEFORE, then it
will select the key first, set the keyProperty and then execute the
insert statement. If set to AFTER, it runs the insert statement and
then the selectKey statement – which is common with databases like
Oracle that may have embedded sequence calls inside of insert
statements.
Therefore, you must match your keyProperty value with the insert param as you have done (keyProperty="id" will be the Param in insert statement:#{id}), and specify the resultType as int so it is a numeric sequence.
Otherwise, you must do your select using the sequence id name, in your case be sure your sequence is called id (because you are using id.NEXTVAL):
SELECT YOUR_SEQ.NEXTVAL FROM DUAL
<insert id="insertAuthor">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select MYSEQUENCE.nextval from dual
</selectKey>
insert into Author
(id, username, password, email,bio, favourite_section)
values
(#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR}

How to update string value by string comparison condition PL/SQL

I want update all values in my tables, but this can kill my database
UPDATE Table_1
SET Value = 'Some string with but changed'
where value = 'Some string without changes';
Can I do this by procedures, and it guarantee that it will not perform in infinty please i need some tips?
Edit
I read about cursors, but how can i use it
Your SQL seems fine and that is the preferred solution. A cursor will normally be far, far slower.
If you cannot create an index and the update above is really that slow, try the following. Considering I don't have the rest of the table definition to work with, I assume your primary key is a single field named ID:
First, create a temporary table with only the matching records:
CREATE TEMPORARY TABLE temp as
SELECT *
FROM Table_1
WHERE value = 'Some string without changes';
Then, update using this temporary table:
UPDATE Table_1 SET
Table_1.Value = 'Some string with but changed'
WHERE EXISTS (
SELECT *
FROM Temp
WHERE Temp.ID = Table_1.ID
);
Another approach if your DB is higher than 11g R1 version. Oracle has provided a beautiful package called DBMS_PARALLEL_EXECUTE which is used for large DMLS or any process which can be split into chunks and can be parallely done.

Inserting an empty row

This is so simple it has probably already been asked, but I couldn't find it (if that's the case I'm sorry for asking).
I would like to insert an empty row on a table so I can pick up its ID (primary key, generated by an insert trigger) through an ExecuteScalar. Data is added to it at a later time in my code.
My question is this: is there a specific insert syntax to create an empty record? or must I go with the regular insert syntax such as "INSERT INTO table (list all the columns) values (null for every column)"?
Thanks for the answer.
UPDATE: In Oracle, ExecuteScalar on INSERT only returns 0. The final answer is a combination of what was posted below. First you need to declare a parameter, and pick up it up with RETURNING.
INSERT INTO TABLENAME (ID) VALUES (DEFAULT) RETURNING ID INTO :parameterName
Check this out link for more info.
You would not have to specify every single column, but you may not be able to create an "empty" record. Check for NOT NULL constraints on the table. If none (not including the Primary Key constraint), then you would only need to supply one column. Like this:
insert into my_table ( some_column )
values ( null );
Do you know about the RETURNING clause? You can return that PK back to your calling application when you do the INSERT.
insert into my_table ( some_column )
values ( 'blah' )
returning my_table_id into <your_variable>;
I would question the approach though. Why create an empty row? That would/could mean there are no constraints on that table, a bad thing if you want good, clean, data.
Basically, in order to insert a row where values for all columns are NULL except primary
key column's value you could execute a simple insert statement:
insert into your_table(PK_col_name)
values(1); -- 1 for instance or null
The before insert trigger, which is responsible for populating primary key column will
override the value in the values clause of the insert statement leaving you with an
empty record except PK value.

DB2 for IBM iSeries: IF EXISTS statement syntax

I am familiar with Sybase which allows queries with format: IF EXISTS () THEN ... ELSE ... END IF (or very close). This a powerful statement that allows: "if exists, then update, else insert".
I am writing queries for DB2 on IBM iSeries box. I have seen the CASE keyword, but I cannot make it work. I always receive the error: "Keyword CASE not expected."
Sample:
IF EXISTS ( SELECT * FROM MYTABLE WHERE KEY = xxx )
THEN UPDATE MYTABLE SET VALUE = zzz WHERE KEY = xxx
ELSE INSERT INTO MYTABLE (KEY, VALUE) VALUES (xxx, zzz)
END IF
Is there a way to do this against DB2 on IBM iSeries? Currently, I run two queries. First a select, then my Java code decides to update/insert. I would rather write a single query as my server is located far away (across the Pacific).
+UPDATE+
DB2 for i, as of version 7.1, now has a MERGE statement which does what you are looking for.
>>-MERGE INTO--+-table-name-+--+--------------------+----------->
'-view-name--' '-correlation-clause-'
>--USING--table-reference--ON--search-condition----------------->
.------------------------------------------------------------------------.
V |
>----WHEN--+-----+--MATCHED--+----------------+--THEN--+-update-operation-+-+----->
'-NOT-' '-AND--condition-' +-delete-operation-+
+-insert-operation-+
'-signal-statement-'
See IBM i 7.1 InfoCenter DB2 MERGE statement reference page
DB/2 on the AS/400 does not have a conditional INSERT / UPDATE statement.
You could drop the SELECT statement by executing an INSERT directly and if it fails execute the UPDATE statement. Flip the order of the statements if your data is more likely to UPDATE than INSERT.
A faster option would be to create a temporary table in QTEMP, INSERT all of the records into the temporary table and then execute a bulk UPDATE ... WHERE EXISTS and INSERT ... WHERE NOT EXISTS at the end to merge all of the records into the final table. The advantage of this method is that you can wrap all of the statements in a batch to minimize round trip communication.
You can perform control-flow logic (IF...THEN...ELSE) in an SQL stored procedure. Here's sample SQL source code:
-- Warning! Untested code ahead.
CREATE PROCEDURE libname.UPSERT_MYTABLE (
IN THEKEY DECIMAL(9,0),
IN NEWVALUE CHAR(10) )
LANGUAGE SQL
MODIFIES SQL DATA
BEGIN
DECLARE FOUND CHAR(1);
-- Set FOUND to 'Y' if the key is found, 'N' if not.
-- (Perhaps there's a more direct way to do it.)
SET FOUND = 'N';
SELECT 'Y' INTO FOUND
FROM SYSIBM.SYSDUMMY1
WHERE EXISTS
(SELECT * FROM MYTABLE WHERE KEY = THEKEY);
IF FOUND = 'Y' THEN
UPDATE MYTABLE
SET VALUE = NEWVALUE
WHERE KEY = THEKEY;
ELSE
INSERT INTO MYTABLE
(KEY, VALUE)
VALUES
(THEKEY, NEWVALUE);
END IF;
END;
Once you create the stored procedure, you call it like you would any other stored procedure on this platform:
CALL UPSERT_MYTABLE( xxx, zzz );
This slightly over complex piece of SQL procedure will solve your problem:
IBM Technote
If you want to do a mass update from another table then have a look at the MERGE statement which is an incredibly powerful statement which lets you insert, update or delete depending on the values from another table.
IBM DB2 Syntax

Insert default value when null is inserted

I have an Oracle database, and a table with several not null columns, all with default values.
I would like to use one insert statement for any data I want to insert, and don't bother to check if the values inserted are nulls or not.
Is there any way to fall back to default column value when null is inserted?
I have this code:
<?php
if (!empty($values['not_null_column_with_default_value'])) {
$insert = "
INSERT INTO schema.my_table
( pk_column, other_column, not_null_column_with_default_value)
VALUES
(:pk_column,:other_column,:not_null_column_with_default_value)
";
} else {
$insert = "
INSERT INTO schema.my_table
( pk_column, other_column)
VALUES
(:pk_column,:other_column)
";
}
So, I have to omit the column entirely, or I will have the error "trying insert null to not null column".
Of course I have multiple nullable columns, so the code create insert statement is very unreadable, ugly, and I just don't like it that way.
I would like to have one statement, something similar to:
INSERT INTO schema.my_table
( pk_column, other_column, not_null_column_with_default_value)
VALUES
(:pk_column,:other_column, NVL(:not_null_column_with_default_value, DEFAULT) );
That of course is a hypothetical query. Do you know any way I would achieve that goal with Oracle DBMS?
EDIT:
Thank you all for your answers. It seams that there is no "standard" way to achieve what I wanted to, so I accepted the IMO best answer: That I should stop being to smart and stick to just omitting the null values via automatically built statements.
Not exactly what I would like to see, but no better choice.
For those who reading it now:
In Oracle 12c there is new feature: DEFAULT ON NULL. For example:
CREATE TABLE tab1 (
col1 NUMBER DEFAULT 5,
col2 NUMBER DEFAULT ON NULL 7,
description VARCHAR2(30)
);
So when you try to INSERT null in col2, this will automatically be 7.
As explained in this AskTom thread, the DEFAULT keyword will only work as a stand-alone expression in a column insert and won't work when mixed with functions or expressions such as NVL.
In other words this is a valid query:
INSERT INTO schema.my_table
( pk_column, other_column, not_null_column_with_default_value)
VALUES
(:pk_column,:other_column, DEFAULT)
You could use a dynamic query with all rows and either a bind variable or the constant DEFAULT if the variable is null. This could be as simple as replacing the string :not_null_column_with_default_value with the string DEFAULT in your $insert.
You could also query the view ALL_TAB_COLUMNS and use nvl(:your_variable, :column_default). The default value is the column DATA_DEFAULT.
I think the cleanest way is to not mention them in your INSERT-statement. You could start writing triggers to fill default values but that's heavy armor for what you're aiming at.
Isn't it possible to restructure your application code a bit? In PHP, you could construct a clean INSERT-statement without messy if's, e.g. like this:
<?php
$insert['column_name1'] = 'column_value1';
$insert['column_name2'] = 'column_value2';
$insert['column_name3'] = '';
$insert['column_name4'] = 'column_value4';
// remove null values
foreach ($insert as $key => $value) {
if (is_null($value) || $value=="") {
unset($insert[$key]);
}
}
// construct insert statement
$statement = "insert into table (". implode(array_keys($insert), ',') .") values (:". implode(array_keys($insert), ',:') .")";
// call oci_parse
$stid = oci_parse($conn, $statement);
// bind parameters
foreach ($insert as $key => $value) {
oci_bind_by_name($stid, ":".$key, $value);
}
// execute!
oci_execute($stid);
?>
The better option for performance is the first one.
Anyway, as I understand, you don't want to repeat the insert column names and values due the difficult to make modifications. Another option you can use is to run an insert with returning clause followed by an update:
INSERT INTO schema.my_table
( pk_column, other_column, not_null_column_with_default_value)
VALUES
(:pk_column,:other_column, :not_null_column_with_default_value)
RETURNING not_null_column_with_default_value
INTO :insered_value
It seems to work with PHP.
After this you can check for null on insered_value bind variable. If it's null you can run the following update:
UPDATE my_table
SET not_null_column_with_default_value = DEFAULT
WHERE pk_column = :pk_column:
I would like to use one insert
statement for any data I want to
insert, and don't bother to check if
the values inserted are nulls or not.
Define your table with a default value for that column. For example:
create table myTable
(
created_date date default sysdate,
...
)
tablespace...
or alter an existing table:
alter table myTable modify(created_date default sysdate);
Now you (or anyone using myTable) don't have to worry about default values, as it should be. Just know that for this example, the column is still nullable, so someone could explicitly insert a null. If this isn't desired, make the column not null as well.
EDIT: Assuming the above is already done, you can use the DEFAULT keyword in your insert statement. I would avoid triggers for this.

Resources