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

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

Related

How to check if exists a row before insert - PL/SQL

Im new with SQL, and I want to knw how can I add a SQL in this structure to check if the row already exists before insert, Im tried this way, but I think its not the best.
DECLARE
v_project_id NUMBER;
v_group_name VARCHAR2(100);
v_user_id NUMBER;
v_group_id NUMBER;
BEGIN
FOR project IN (
SELECT project_id
FROM JSON_TABLE(:p_request,
'$' COLUMNS(NESTED PATH '$.projects[*]'
COLUMNS(
project_id VARCHAR2(100) PATH '$.project.id'
)
)
)
)
LOOP
v_project_id := project.project_id;
v_group_name := json_value (:p_request,'$.group_name');
v_user_id := json_value (:p_request,'$.user_id');
v_group_id := s_project_group.NEXTVAL;
dbms_output.put_line(v_group_id||' - '||v_group_name||' - '||v_user_id||' - '||v_project_id);
--------- HERE, I WANT TO CHECK IF THE NEW ROW ALREADY EXISTS BEFORE INSERT, IM TRIED THIS WAY, BUT I THINK ITS NOT THE BEST------------
(select case
when NOT exists (select 1 FROM APP_PROJECT_GROUP WHERE GROUP_NAME = :v_group_name AND USER_ID = :v_user_id)
INSERT INTO APP_PROJECT_GROUP (GROUP_ID, GROUP_NAME, PROJECT_ID, USER_ID) VALUES (v_group_id, v_group_name, v_project_id, v_user_i)
);
----------------------------------------------------------------------------------------------------------------------------------------
COMMIT;
END LOOP;
END;
As I commented on your previous question, use a MERGE statement then you can perform all the INSERTs in a single SQL statement (without having to use use cursor loops and repeatedly context-switching from PL/SQL to SQL):
MERGE INTO app_project_group dst
USING (
SELECT group_id,
group_name,
TO_NUMBER(project_id) AS project_id,
user_id
FROM JSON_TABLE(
:p_request,
'$'
COLUMNS(
group_name VARCHAR2(150) PATH '$.group_name',
group_id NUMBER(15) PATH '$.group_id',
user_id NUMBER(15) PATH '$.user_id',
NESTED PATH '$.projects[*]'
COLUMNS (
project_id VARCHAR2(15) PATH '$.project.id'
)
)
)
) src
ON (src.group_name = dst.group_name AND src.user_id = dst.user_id)
WHEN NOT MATCHED THEN
INSERT (group_id, group_name, project_id, user_id)
VALUES (s_project_group.NEXTVAL, src.group_name, src.project_id, src.user_id);
db<>fiddle here

INSERT ALL fails with trigger setting timestamp

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.

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

How to insert foreign key from pre-generated table?

I have 3 tables:
create table customer
(
customer_id integer primary key,
customer_first_name varchar2(50) not null,
customer_surrname varchar2(50) not null,
phone_number varchar2(15) not null,
customer_details varchar2(200) default 'There is no special notes'
);
create table place
(
table_number integer primary key,
table_details varchar2(200) default 'There is no details'
);
create table booking
(
booking_id integer primary key,
date_of_booking date,
number_of_persons number(2) not null,
customer_id integer not null,
foreign key(customer_id) references customer(customer_id),
table_number integer not null,
foreign key(table_number) references place(table_number)
);
I have to generate customer table using this kind of generator:
set SERVEROUTPUT on format wrapped;
set define off;
drop sequence customer_seq;
drop sequence place_seq;
--CUSTOMER TABLE INSERT ROW GENERATOR
create sequence customer_seq START WITH 1 INCREMENT BY 1 NOMAXVALUE;
CREATE OR REPLACE TRIGGER customer_id_trigger
BEFORE INSERT ON customer
FOR EACH ROW
BEGIN
SELECT customer_seq.nextval INTO :new.customer_id FROM dual;
END;
/
DELETE FROM customer;
DECLARE
TYPE TABSTR IS TABLE OF VARCHAR2(250);
first_name TABSTR;
surrname TABSTR;
qname number(5);
phonenum number(15);
details TABSTR;
BEGIN
first_name := TABSTR ('Jhon','Poul','Jesica','Arnold','Max','Teemo','Tim','Mikel','Michael',
'Kristian','Adela','Mari','Anastasia','Robert','Jim','Juana','Adward',
'Jana','Ola','Kristine','Natali','Corey','Chester','Naomi','Chin-Chou');
surrname := TABSTR ('Grey','Brown','Robins','Chiho','Lee','Das','Edwins','Porter','Potter',
'Dali','Jordan','Jordison','Fox','Washington','Bal','Pitney','Komarowski',
'Banks','Albra','Shwiger');
details := TABSTR ('Exellent Customer','Good Customer','Always drunked','Left big tips',
'Bad Customer','Did not pay last bill','New Customer','VIP client');
qname := 100; — CHANGE THIS TO MANAGE HOW MANY ROWS YOU WANT TO BE ADDED
FOR i IN 1..qname LOOP
phonenum := dbms_random.value(111111111,999999999);
INSERT INTO customer VALUES (NULL, first_name(dbms_random.value(1,25)),
surrname(dbms_random.value(1,20)), phonenum, details(dbms_random.value(1,8)));
END LOOP;
DBMS_OUTPUT.put_line('Customers done!');
END;
/
--TABLE INSERT
DELETE FROM place;
create sequence place_seq start with 1 increment by 1;
insert into place values (place_seq.nextval, 'Near the window');
insert into place values (place_seq.nextval, default);
insert into place values (place_seq.nextval, 'Near the door');
insert into place values (place_seq.nextval, 'Near the window');
insert into place values (place_seq.nextval, 'Near the window');
insert into place values (place_seq.nextval, default);
insert into place values (place_seq.nextval, 'Near the door');
insert into place values (place_seq.nextval, 'Big table');
insert into place values (place_seq.nextval, default);
insert into place values (place_seq.nextval, 'Big table');
So the question is how can I insert client_id in "booking" table which have one of the numbers in "customers" table? Because every time I regenerate data in "Customers" table numbers are changing so I should somehow select numbers in an array and then randomly choose one of them from this array. The thing is I don't really know how to select from table to array. Can anybody help?
For PL/SQL version you can use BULK COLLECT and standard sys.odcinumberlist array.
create sequence booking_seq start with 1 increment by 1;
declare
customerIds sys.odcinumberlist;
placeIds sys.odcinumberlist;
number_of_generated_records number := 150; -- number of records to be generated
begin
-- fill the array of customer_id values
select customer_id
bulk collect into customerIds
from customer;
-- fill the array of place numbers
select table_number
bulk collect into placeIds
from place;
for i in 1..number_of_generated_records loop
insert into booking(booking_id,date_of_booking,number_of_persons,customer_id,table_number)
values(
booking_seq.nextval, -- booking_id
trunc(sysdate) + round(dbms_random.value(1,365)), -- date_of_booking
round(dbms_random.value(1,99)), -- number_of_persons
customerIds(round(dbms_random.value(1,customerIds.count))), -- customer_id
placeIds(round(dbms_random.value(1,placeIds.count))) -- table_number
);
end loop;
end;
But, for your case I would prefer pure sql:
insert into booking(booking_id,date_of_booking,number_of_persons,customer_id,table_number)
with
customer_subq as (
select customer_id, row_number() over (order by customer_id) rn from customer
),
place_subq as (
select table_number, row_number() over (order by table_number) rn from place
),
params as (
select 1500 number_of_generated_records,
(select count(1) from customer) customer_count,
(select count(1) from place) place_count
from dual
),
random_numbers as (
select round(dbms_random.value(1,1000)) random_number1,
round(dbms_random.value(1,1000)) random_number2,
round(dbms_random.value(1,1000)) random_number3,
round(dbms_random.value(1,1000)) random_number4
from dual,params
connect by level <= number_of_generated_records
)
select booking_seq.nextval booking_id,
trunc(sysdate) + mod(random_number1,365) date_of_booking,
mod(random_number1,100) number_of_persons,
customer_id,
table_number
from random_numbers,
params,
customer_subq,
place_subq
where mod(random_number1,customer_count) + 1 = customer_subq.rn
and mod(random_number2,place_count) + 1 = place_subq.rn

compare i and i+1 element oracle

I have table
CREATE table table_x
(
id NUMBER, -- ID
NUMBER_history NUMBER, -- consecutive number
start_date date, -- start_date of next id=end_date of previous
end_date date -- should be >=start_date
);
INSERT INTO table_x VALUES (14 , 1, '13-NOV-92' ,'14-NOV-92' );
INSERT INTO table_x VALUES (15 , 2, '14-NOV-92' ,'16-NOV-92' );
INSERT INTO table_x VALUES (19 , 3, '16-NOV-92' ,'18-NOV-92' );
INSERT INTO table_x VALUES (11 , 4, '18-NOV-92' ,'14-NOV-93' );
INSERT INTO table_x VALUES (17 , 5, '15-NOV-93' ,'16-NOV-93' );
INSERT INTO table_x VALUES (151 , 6, '16-NOV-93' ,'17-NOV-93' );
INSERT INTO table_x VALUES (139 , 7, '17-NOV-93' ,'18-NOV-93' );
INSERT INTO table_x VALUES (121 , 8, '19-NOV-93' ,'20-DEC-93' );
INSERT INTO table_x VALUES (822 , 9, '20-DEC-93' ,'20-DEC-93' );
I want write query where I can find where start_date of next row > end_date of previous. they must be equal.
I try to do something like that using NUMBER_history as counter. C-way where I organize cycle by variable i and compare i and i+1 (NUMBER_history and NUMBER_history+1)
select * INTO row_tblx from table_x where NUMBER_history=NUMBER_history and end_date<(select start_date from table_x where NUMBER_history=NUMBER_history+1);
but I must organize loop by n_counter from 1 to last NUMBER_history value and fetch data into multiple row.
How can I do it?
I try
set serveroutput on
DECLARE
CURSOR cur IS
SELECT * FROM table_x;
TYPE row_tblx_type
IS
TABLE OF cur%ROWTYPE;
row_tblx row_tblx_type;
rowx cur%ROWTYPE;
nh_count NUMBER;
BEGIN
FOR NUMBER_history IN cur LOOP
select * INTO row_tblx from table_x where NUMBER_history=NUMBER_history and end_date<(select start_date from table_x where NUMBER_history=NUMBER_history+1);
DBMS_OUTPUT.PUT_LINE (row_tblx(NUMBER_history).id);
END LOOP;
END;
/
How can I do it using for or another loop, multiple records or table of records , cursor, an table row as counter (NUMBER_history)?
How can I do it without cursor?
You don't need PL/SQL or a loop for that:
select *
from (
select id, number_history, start_date, end_date,
lead(start_date) over (order by number_history) as next_start
from table_x
) t
where next_start > end_date;
For example, here's a solution that does not require PL/SQL or LEAD/LAG function
select a.*, b.*
from table_x a
join table_x b
on a.number_history+1 = b.number_history
where b.start_date > a.end_date