INSERT ALL fails with trigger setting timestamp - sql

Let's say we have a table like this:
CREATE TABLE test_table
(
text VARCHAR2(200) NOT NULL,
text2 VARCHAR2(200) NOT NULL,
ts TIMESTAMP
);
And we want to insert some data using INSERT ALL:
INSERT ALL
INTO test_table ( text, text2 ) VALUES ( 'test', 'test2' )
SELECT * FROM dual;
The result is
1 row inserted.
But, when we want to add trigger, to fill ts column with SYSTIMESTAMP
CREATE OR REPLACE TRIGGER test_trigger
BEFORE INSERT ON test_table
FOR EACH ROW
BEGIN
DBMS_OUTPUT.put_line('text=' || :new.text);
DBMS_OUTPUT.put_line('text2=' || :new.text2);
DBMS_OUTPUT.put_line('ts=' || :new.ts);
:new.ts := SYSTIMESTAMP;
END;
/
Running the same script
SET SERVEROUT ON;
INSERT ALL
INTO test_table ( text, text2 ) VALUES ( 'test', 'test2' )
SELECT * FROM dual;
The result is:
text=test
text2=
ts=
INSERT ALL
INTO test_table ( text, text2 ) VALUES ( 'test', 'test2' )
SELECT * FROM dual
Error report -
ORA-01400: cannot insert NULL into ("TEST"."TEST_TABLE"."TEXT2")
Using INSERT works fine
SET SERVEROUT ON;
INSERT INTO test_table ( text, text2 ) VALUES ( 'test', 'test2' )
The result is
text=test
text2=test2
ts=
1 row inserted.
Also this works:
INSERT ALL
INTO test_table ( text, text2, ts) VALUES ( 'test', 'test2', null )
SELECT * FROM dual
When I change ts column type to DATE works fine with this kind of trigger.
I'm using Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production, I've also checked on Oracle 12c but there is no error, so maybe it's some kind of bug in 11g release?

Nothing seems wrong with your code, it may be a bug with version you are using.
That said, what you are trying to achieve is more usually done by following create table statement
CREATE TABLE test_table (
text VARCHAR2(200) NOT NULL,
text2 VARCHAR2(200) NOT NULL,
ts TIMESTAMP not null default systimestamp
);
You will not need trigger for this at all.

Related

How to make an insert with a combination of a select and passed parameters in a stored function

I have two tables, e.g.:
CREATE TABLE table_1
(
one_column INTEGER,
two_column INTEGER,
three_column INTEGER
);
CREATE TABLE table_2
(
id SERIAL,
column_1 INTEGER,
column_2 INTEGER,
column_3 INTEGER,
name TEXT,
step INTEGER
);
I have a stored function which receives a number of parameters. Within the function, I need to INSERT a row into a table using a (dynamic?) combination of the results from a SELECT and two of the function parameters. Currently I've got something similar to the following pseudo-code...
CREATE OR REPLACE FUNCTION function_p (
p_id INTEGER,
p_name TEXT DEFAULT '',
p_step INTEGER DEFAULT NULL
)
RETURNS VOID AS $$
BEGIN
INSERT INTO table_2 (
column_1,
column_2,
column_3,
p_name,
p_step
)
SELECT
one_column,
two_column,
three_column
FROM table_1
WHERE id = p_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Should the INSERT be more like this?
INSERT INTO table_2 (
column_1,
column_2,
column_3,
name,
step
)
(SELECT
one_column,
two_column,
three_column
FROM table_1
WHERE id = p_id),
p_name,
p_step;
Use format to create the insert statement. Also, you problably want a procedure, e.g.
CREATE OR REPLACE PROCEDURE function_p (
p_name TEXT DEFAULT '',
p_step INT DEFAULT NULL
)
LANGUAGE plpgsql AS $$
BEGIN
EXECUTE format('
INSERT INTO table_2 (column_1,column_2,column_3,name,step)
SELECT one_column, two_column,three_column,%L,%s
FROM table_1',p_name,p_step);
END;
$$
Keep in mind that the columns number (and types) used in the INSERT statement has to match with those coming from the SELECT.
Demo: db<>fiddle

How can I disable entering the same values in two fields at the same time on a table in Oracle?

Let´s say I create this table:
CREATE TABLE MYTABLE (
id INT NOT NULL AUTO_INCREMENT,
Field1 VARCHAR(30),
Field2 NUMBER(10),
);
Then I will insert this values:
INSERT INTO MYTABLE VALUES(null, 'Value', 10);
What I want is to be able to do both of these inserts:
INSERT INTO MYTABLE VALUES(null, 'Value', 5);
/* This works as there isn´t a row with both Field1='Value' and Field2=5 at the same time */
INSERT INTO MYTABLE VALUES(null, 'Something', 10);
/* This works as there isn´t a row with both Field1='Something' and Field2=10 at the same time */
But I don´t want to be able to do this (repeat both the Field1 and Field2 values together):
INSERT INTO MYTABLE VALUES(null, 'Value', 10);
/* This doesn´t work as there is a row with both Field1='Value' and Field2=10 at the same time */
How can I achieve this behaviour in Oracle? I thought about using ASSERTIONS but they are not yet implemented in Oracle.
I don´t want to be able to do this (repeat both the Field1 and Field2 values together)
You can use a COMPOUND trigger:
CREATE OR REPLACE TRIGGER mytable__not_repeat_f1_and_f2
FOR UPDATE OR INSERT ON MyTable
COMPOUND TRIGGER
TYPE MyTable_Fields_Type IS RECORD(
rid ROWID,
field1 MyTable.Field1%TYPE,
field2 MyTable.Field2%TYPE
);
TYPE MyTable_Fields_Table_Type IS TABLE OF MyTable_Fields_Type;
fields MyTable_Fields_Table_Type := MyTable_Fields_Table_Type();
AFTER EACH ROW IS
BEGIN
fields.EXTEND;
fields(fields.COUNT) := MyTable_Fields_Type(
:NEW.ROWID,
:NEW.Field1,
:NEW.Field2
);
END AFTER EACH ROW;
AFTER STATEMENT IS
num_field1 PLS_INTEGER;
num_field2 PLS_INTEGER;
BEGIN
FOR i IN 1 .. fields.COUNT LOOP
SELECT COUNT( CASE WHEN Field1 = fields(i).Field1 THEN 1 END ),
COUNT( CASE WHEN Field2 = fields(i).Field2 THEN 1 END )
INTO num_field1,
num_field2
FROM MyTable
WHERE ROWID != fields(i).RID;
IF num_field1 > 0 AND num_field2 > 0 THEN
RAISE_APPLICATION_ERROR( -20000, 'Cannot have duplicate Field1 and Field2' );
END IF;
END LOOP;
END AFTER STATEMENT;
END;
/
Then, for the table:
CREATE TABLE MYTABLE (
id INT
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
Field1 VARCHAR2(30),
Field2 NUMBER(10)
);
If you do:
INSERT INTO MyTable ( Field1, Field2 )
SELECT 'a', 1 FROM DUAL UNION ALL
SELECT 'b', 2 FROM DUAL UNION ALL
SELECT 'c', 3 FROM DUAL;
That works but then trying to do:
INSERT INTO MyTable ( Field1, Field2 ) VALUES ( 'b', 3 );
Would raise the exception:
ORA-20000: Cannot have duplicate Field1 and Field2
ORA-06512: at "SCHEMA_NAME.MYTABLE__NOT_REPEAT_F1_AND_F2", line 33
ORA-04088: error during execution of trigger 'SCHEMA_NAME.MYTABLE__NOT_REPEAT_F1_AND_F2'
But:
INSERT INTO MyTable ( Field1, Field2 ) VALUES ( 'b', 4 );
Would work since this doesn't repeat a Field1 and a Field2 value together.
db<>fiddle here
If you want each column to be unique, you can just use unique constraints:
CREATE TABLE MYTABLE (
id INT NOT NULL AUTO_INCREMENT,
Field1 VARCHAR(30) UNIQUE,
Field2 NUMBER(10) UNIQUE
);
Create a compound unique index containing both columns so that the combination can't be inserted more than once, whether it be at the same time or at different times.
I don't think there's any way to do this reliably. If I understand correctly, you want to reject an inserted row if Field1 and Field2 both already exist in the table, but not necessarily in the same row.
So if your table looks like:
Field1 Field2
------ ------
Value 5
Something 10
I should be able to insert ('Apple', 7), right? Then when i try to insert ('Value', 7), it fails because there are rows with both those values already.
But I could do them in the reverse order: insert ('Value', 7) and then ('Apple', 7)
So what happens if I do this?
WITH mydata AS (
SELECT 'Apple' AS field1, 7 AS field2 FROM dual UNION ALL
SELECT 'Value', 7 FROM dual
INSERT INTO mytable
SELECT null, field1, field2 FROM mydata
Does that succeed or fail? There's no way to know. You are not guaranteed that the database actions happen in the order in which you think they're going to happen.
You'll run into the same problem if two sessions try to insert these two rows, then commit.

Checking whether a string contains any numeric values

I have the following table:
create table students
(
stuName varchar2(100),
cgpa number
);
My goal is to create a PL/SQL trigger that would fire if anyone tries to enter a name that contains any numeric values. My attempt:
create or replace trigger invalid_name
before insert
on students
for each row
declare
vName varchar2(100);
begin
vName := :new.stuName;
if upper(vName) like upper(vName) then
vName := initcap(vName);
end if;
exception
when value_error then
dbms_output.put_line('ERROR: Name contains numeric value(s).');
end;
I thought if the upper function were to act on a string containing any numeric value in it, it would throw an exception. But that's not happening and insert action is being executed.
I'd suggest using a constraint rather than a trigger.
create table foo (
name varchar2(100) NOT NULL
constraint name_non_numeric check ( not regexp_like( name, '[0-9]' ) )
);
Table created.
insert into foo ( name ) values ( 'Andy' );
1 row created.
> insert into foo ( name ) values ( 'Logan 5' );
insert into foo ( name ) values ( 'Logan 5' )
*
ERROR at line 1:
ORA-02290: check constraint (NAMESPACE.NAME_NON_NUMERIC) violated
If you don't want to replace, but check and raise an error, you can use this trick and raise an error if the result is not null:
SELECT LENGTH(TRIM(TRANSLATE('123b', ' +-.0123456789',' '))) FROM dual;
Result: 1
SELECT LENGTH(TRIM(TRANSLATE('a123b', ' +-.0123456789',' '))) FROM dual;
Result: 2
SELECT LENGTH(TRIM(TRANSLATE('1256.54', ' +-.0123456789',' '))) FROM dual;
Result: null
SELECT LENGTH(TRIM(TRANSLATE ('-56', ' +-.0123456789',' '))) FROM dual;
Result: null

PL/SQL: ORA-12704: character set mismatch when insert varchar2 to nvarchar2

I try to populate a table TBL in a loop. And get ORA-12704: character set mismatch on a subquery.
Here is a query I use:
BEGIN
FOR i IN (SELECT t.Stuff FROM STUFF_TABLE t ORDER BY t.Name ASC)
LOOP
INSERT INTO TBL(StuffId, StuffName)
VALUES(
i.Stuff,
(SELECT TempStuffName FROM
(SELECT COALESCE(st.StuffName, i.Stuff) as TempStuffName FROM STUFFDEFINITION st WHERE st.Stuff = i.Stuff ORDER BY st.Version DESC)
WHERE ROWNUM = 1)
);
END LOOP;
END;
The columns types are the following:
STUFF_TABLE.Stuff nvarchar2(30)
TBL.StuffId nvarchar2(30)
TBL.StuffName nvarchar2(50)
STUFFDEFINITION.Stuff varchar2(255)
STUFFDEFINITION.StuffName varchar2(255)
The issue, as I understand it, is in type casting namely from varchar2(255) to nvarchar2(50).
I tried to use CAST, Translate functions, but it didn't help. The ORA-12704: character set mismatch still occurs.
Is there a way to populate TBL in a loop as I try it to do?
Below is the test data to reproduce issue I talk about:
CREATE TABLE STUFF_TABLE
(
Stuff nvarchar2(30),
Name nvarchar2(50)
);
CREATE TABLE TBL
(
StuffId nvarchar2(30),
StuffName nvarchar2(50)
);
CREATE TABLE STUFFDEFINITION
(
Stuff varchar2(255),
StuffName varchar2(255),
Version number(19)
)
INSERT INTO STUFF_TABLE(Stuff, Name) VALUES('First', 'Name1');
INSERT INTO STUFF_TABLE(Stuff, Name) VALUES('Second', 'Name2');
INSERT INTO STUFF_TABLE(Stuff, Name) VALUES('Third', 'Name3');
INSERT INTO STUFFDEFINITION(Stuff, StuffName, Version) VALUES('First', 'First Stuff', 1);
INSERT INTO STUFFDEFINITION(Stuff, StuffName, Version) VALUES('First', 'First Stuff', 2);
INSERT INTO STUFFDEFINITION(Stuff, StuffName, Version) VALUES('Second', 'Second Stuff', 1);
INSERT INTO STUFFDEFINITION(Stuff, StuffName, Version) VALUES('Third', 'Third Stuff', 1);
From your statement its clear that you are joining a varchar2 column with nvarchar2 and inserting a varchar2 column to a nvarchar2 column. You need to do a conversion first. Try this:
BEGIN
FOR i IN (SELECT t.Stuff FROM STUFF_TABLE t ORDER BY t.Name ASC)
LOOP
INSERT INTO TBL(StuffId, StuffName)
VALUES(
i.Stuff,
(SELECT to_nchar(TempStuffName)
FROM
(SELECT COALESCE(to_nchar(st.StuffName), i.Stuff) as TempStuffName
FROM STUFFDEFINITION st
WHERE to_nchar(st.Stuff) = i.Stuff
ORDER BY st.Version DESC)
WHERE ROWNUM = 1)
);
END LOOP;
END;
You shouldn't need to use PL/SQL:
INSERT INTO TBL ( StuffId, StuffName )
SELECT TO_NCHAR( t.stuff ),
TO_NCHAR(
COALESCE(
MAX( d.StuffName ) KEEP ( DENSE_RANK LAST ORDER BY d.version ),
t.stuff
)
)
FROM StuffTable t
LEFT OUTER JOIN StuffDefinition d
ON ( t.stuff = TO_NCHAR( d.stuff ) )
GROUP BY t.stuff;
Oracle says this about that error: 'A string expression in the VALUES clause of an INSERT statement does not have the same character set as the column into which the value would be inserted.'
Please select from nls_database_parameters and tell me what the values of these are:
NLS_CHARACTERSET
NLS_NCHAR_CHARACTERSET
NLS_RDBMS_VERSION
NLS_LENGTH_SEMANTICS

Oracle SQL random value from string set

In Oracle SQL 11g I am trying to fill a table with procedure. For some columns I need to take data randomly from predefined set of strings. How do I define such set and take data from it by random order?
You could use a cte and dbms_random.value. Something like:
with strings as (
select 'string1' as s from dual union all
select 'string2' as s from dual union all
select 'string3' as s from dual union all
select 'string4' as s from dual
)
select <col1>,
(select s
from (select s from strings order by dbms_random.value) s
where rownum = 1
) as RandomString
from dual;
Can you give this a try,it is working.
1.Insert the list of strings in a table (strings).
2.Create a function(RANDOM) to generate random number.
3.Create a procedure(PROC_STRING) to pick a string name from (STRINGS) table using the random number generated from function(RANDOM) and then insert into (NEW_TABLE)
PROGRAM:
--Table with list of string names
Create table strings (string_id number,string_name varchar2(2000) );
--Table to store new string names in random order
Create table new_table (string_id number,string_name varchar2(2000) );
--Function to generate random numbers
create or replace function random(p_number in number)
return number
is
a number;
begin
select dbms_random.value(1,10) into a
from dual;
a := floor(a);
return a;
end;
/
delete from strings;
delete from new_table;
insert into strings values(1,'abc');
insert into strings values(2,'def');
insert into strings values(3,'ghi');
insert into strings values(4,'abc 1');
insert into strings values(5,'def 1');
insert into strings values(6,'ghi 1');
insert into strings values(7,'abc 2');
insert into strings values(8,'def 2');
insert into strings values(9,'ghi 2');
insert into strings values(10,'xyz 3');
--Procedure to pick string names randomly from strings table and insert into new_table
create or replace procedure proc_string(p_no in number)
as
s_id number;
s_name varchar2(2000);
begin
select random(1) into s_id from dual;
select string_name into s_name from strings where string_id = s_id;
insert into New_table values(s_id,s_name);
dbms_output.put_line('insert successfully completed');
commit;
Exception when others
then dbms_output.put_line('ERROR:' || SQLCODE || ' ' || SQLERRM);
end;
/
commit;
EXECUTION:
--After executing the procedure for 3 times
SQL> exec proc_string(1);
insert successfully completed
PL/SQL procedure successfully completed.
-- Random string names got inserted into newtable
SQL> select * from new_table;
STRING_ID STRING_NAME
5 def 1
3 ghi
1 abc
Let me know if you questions.