How to insert 3.5k ints to array in Oracle DB - sql

I have custom type:
create or replace type integer_varray as varray (4000) of int;
Then table which uses this array:
create table plan_capacities
(
id int generated by default as identity not null constraint plan_capacities_pkey primary key,
line_id int references lines (id) on delete cascade,
model_id int references models (id) on delete cascade,
plan_id int references plans (id) on delete cascade,
capacity integer_varray
);
And then some data I would like to insert in. The problem is that in Oracle I can't use more than 1000 items (I have 3 500 items) in the array "constructor" so simple statement
INSERT INTO plan_capacities ("model_id", "line_id", "plan_id", "capacity") VALUES (1,1,1,integer_varray(1,2,3.....35000))
Is not possible to use. (data are some capacities and the have to be in the specific order).
Data that should be inserted into array are in a string I have to put into script. -> {1,10,11,10,20,0,0,0,1,10 ....}
How can I insert that load of data?
I tried to insert them into temp table and then filling array with them - this works but that sql script has 3500 rows (to create just one records to plan_capacities) which is awful and big.

You can use your array as a table to insert its values into a table with a single SQL statement; for example:
declare
vArray integer_varray;
begin
-- some code to populate vArray
insert into someTable(col)
select column_value from table(vArray);
end;
If you can populate your array with a query, you don't need the array, simply using your query as a data source for the insert statement; for example:
insert into someTable(col)
select something
from someOtherTable
If you need a way to create a set of numbers, say 1, 2, ... 3500, this is a commonly used way:
select level
from dual
connect by level <= 3500
About a way to build a set of numbers from a string, this is a quite usual way:
SQL> create or replace type integer_varray as varray (4000) of int
2 /
Type created.
SQL> create table someTable(n number);
Table created.
SQL> declare
2 vString varchar2(32000) := '1,10,11,10,20,0,0,0,1,10';
3 vArray integer_varray;
4 begin
5 insert into someTable(n)
6 select to_number(regexp_substr(vString, '[^,]+', 1, level))
7 from dual
8 connect by instr(vString, ',', 1, level - 1) > 0;
9 end;
10 /
PL/SQL procedure successfully completed.
SQL> select * from someTable;
N
----------
1
10
11
10
20
0
0
0
1
10
10 rows selected.
SQL>

So my final solution is following:
create or replace type integer_varray as varray (4000) of int;
/
create or replace type varchar_varray as varray (10) of varchar(32767);
/
declare
data_to_be_stored varchar_varray := varchar_varray(
'0,0,0,0,.....',
'0,0,0,0,0,0....',
'0,0,0,0,0,0....'
);
array_to_store integer_varray := integer_varray();
begin
for i in 1 .. data_to_be_stored.COUNT loop
for j in (select to_number(trim(regexp_substr(data_to_be_stored(i), '[^,]+', 1, LEVEL))) value
from dual
connect by LEVEL <= regexp_count(data_to_be_stored(i), ',') + 1
) loop
array_to_store.extend;
array_to_store(array_to_store.count) := j.value;
end loop;
end loop;
insert into table_with_that_array (array) values (array_to_store);
end;
\
I had to you use varchar_varraybecause my data/strings are bigger than max capacity of varchar2so I split it into multiple strings in array.

Related

Use column name as key for PL/SQL associative array when updating another column

I have an Excel table with two columns of data. Column A are codes, column B are the corresponding country names. I turned it into an associative array, ueln_country.
Now the task is to update HORSE table's column COUNTRY_OF_RESIDENCE. Horses have a column UELN where first three letters correspond to the codes in the Excel table.
I have to check if the code exists in the Excel table. If it does, I have to update HORSE.country_of_residence with a CLASSIFICATOR.code where CLASSIFICATOR.name = **the corresponding country in column B** andCLASSIFICATOR.dom_code = 'ISOCODE'`.
First try gets the error
PLS-00201: identifier 'UELN' must be declared
As I understood, it's because I can only use declared variables in PL/SQL statement.
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
ueln_country TBL_UELN_COUNTRY;
begin
ueln_country('008') := 'ALBAANIA';
ueln_country('010') := 'ANTARKTIS';
ueln_country('011') := 'ANTARKTIS';
....
update HORSE
set COUNTRY_OF_RESIDENCE=
when (...dummy_case...) then
(select code from meta.classifcator
where dom_code = 'ISOCODE'
and name = ueln_country(substr(UELN, 1, 3)))
where UELN is not null;
end;
/
Second try.
So because of the first error I tried to somehow declare the variables.
I knew it wouldn't work (ORA-01422: exact fetch returns more than requested number of rows) but made it to show where my idea is going:
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
ueln_country TBL_UELN_COUNTRY;
v_ueln horse.UELN%TYPE;
begin
select UELN into v_ueln from HORSE;
ueln_country('008') := 'ALBAANIA';
ueln_country('010') := 'ANTARKTIS';
ueln_country('011') := 'ANTARKTIS';
....
update HORSE
set COUNTRY_OF_RESIDENCE=
when (...dummy_case...) then
(select code from meta.classifcator
where dom_code = 'ISOCODE'
and name = ueln_country(substr(v_ueln, 1, 3)))
where UELN is not null;
end;
/
So I want pick a value from associative array where the key = substr(specific_horse.UELN, 1, 3).
Searched through Google and Stack for hours and didn't find an answer.
The ugly and very slow working solution was just where I didn't make the associate array and made 400+ cases for every Excel table row in the form like when -key- then select code from meta.classificator where dom_code = 'ISOKOOD' and name = -value-
associative array can not be used in SQL.
If you use expression like array(index) in SQL then, in fact, PL/SQL engine gets value by index and then result is bound into SQL engine before execution of the SQL statement.
More specifically
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
dummy varchar2(30);
begin
test('GBP') := 'UK';
test('USD') := 'USA';
select /*+ qwerty */ test('GBP')
into dummy
from dual;
end;
/
If we check binds for a cursor we see that actual bind value has a type VARCHAR(128) - :B1. test('GBP') in PL/SQL code is passed as bind variable B1.
SQL> column sql_text format a50
SQL> select sbc.datatype_string, sql_text
2 from v$sql s join v$sql_bind_capture sbc
3 on s.sql_id = sbc.sql_id
4 where lower(sql_text) not like '%v$sql%'
5 and lower(sql_fulltext) like 'select %qwerty%';
DATATYPE_STRING SQL_TEXT
--------------- --------------------------------------------------
VARCHAR2(128) SELECT /*+ qwerty */ :B1 FROM DUAL
SQL engine knows nothing about associative array and apparently it cannot pass and index value to array and get an element of the array back.
If you still want to use associative array to look-up some values you can declare package variable and a getter function (you may also want to implement the logic to handle a case when there is no element in array for a given index - otherwise you'll get run-time exception in such case).
create or replace package pkg as
function GetCountry(idx in varchar2) return varchar2;
end pkg;
/
sho err
create or replace package body pkg as
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test pkg.TBL_UELN_COUNTRY;
function GetCountry(idx in varchar2) return varchar2 as
begin return test(idx); end;
-- initializing
begin
test('GBP') := 'UK';
test('USD') := 'USA';
end pkg;
/
sho err
And finally
SQL> set serveroutput on
SQL> declare
2 dummy varchar2(30);
3 begin
4 with t(idx) as (select 'GBP' from dual)
5 select pkg.GetCountry(t.idx)
6 into dummy
7 from t;
8 dbms_output.put_line(dummy);
9 end;
10 /
UK
PL/SQL procedure successfully completed.

How to create a trigger to allow both manual and auto primary key in sql developer with oracle 11g

I am attempting to create a trigger (using SQL Developer with Oracle 11g) that will allow manual insertions onto the primary key, and if a record is created without a specified primary key it will assign one from a sequence. First I tried to use a select statement in the trigger that checks if the id generated by the sequence is already in the table because of manual insertion :
DROP TABLE testing;
DROP SEQUENCE testing_seq;
CREATE TABLE testing (
id_number NUMBER PRIMARY KEY,
test_data VARCHAR(50)
);
CREATE SEQUENCE testing_seq
MINVALUE 1
MAXVALUE 10000
START WITH 1
INCREMENT BY 1
NOORDER
NOCYCLE;
CREATE OR REPLACE TRIGGER auto_testing_id BEFORE
INSERT ON testing
FOR EACH ROW
DECLARE
tmp NUMBER;
seq NUMBER;
BEGIN
IF :NEW.id_number IS NULL THEN
seq := testing_seq.nextval;
SELECT
(SELECT 1 FROM testing WHERE id_number = seq
) INTO tmp FROM dual;
while (tmp = 1)
loop
seq := testing_seq.nextval;
SELECT
(SELECT 1 FROM testing WHERE id_number = seq
) INTO tmp FROM dual;
END loop;
:NEW.id_number := seq;
END IF;
END;
/
INSERT INTO testing VALUES(1,'test1');
INSERT INTO testing (test_data) VALUES('test2');
SELECT * FROM testing;
Table TESTING dropped.
Sequence TESTING_SEQ dropped.
Table TESTING created.
Sequence TESTING_SEQ created.
Trigger AUTO_TESTING_ID compiled
1 row inserted.
1 row inserted.
ID_NUMBER TEST_DATA
---------- --------------------------------------------------
1 test1
2 test2
This works for manually created insertions, but not if I try to insert using a select statement. I believe this is because I am referencing the table being inserted on inside the trigger.
I tried a trigger without the check, but as expected if the trigger created an id that was already in the table it threw a unique constraint error
CREATE OR REPLACE TRIGGER auto_testing_id2 BEFORE
INSERT ON testing
FOR EACH ROW
DECLARE
BEGIN
IF :NEW.id_number is null
then
:NEW.id_number := testing_seq.nextval;
end if;
end;
/
Trigger AUTO_TESTING_ID2 compiled
1 row inserted.
Error starting at line : 59 in command -
INSERT INTO testing (test_data) VALUES('test2')
Error report -
SQL Error: ORA-00001: unique constraint (KATRINA_LEARNING.SYS_C001190313) violated
00001. 00000 - "unique constraint (%s.%s) violated"
*Cause: An UPDATE or INSERT statement attempted to insert a duplicate key.
For Trusted Oracle configured in DBMS MAC mode, you may see
this message if a duplicate entry exists at a different level.
*Action: Either remove the unique restriction or do not insert the key.
ID_NUMBER TEST_DATA
---------- --------------------------------------------------
1 test1
I tried to catch this error (using error name DUP_VAL_ON_INDEX), and then loop it until it found the next number in the sequence that isn't in the table (with and without error catching), but it wouldn't even send up a test error message, and when I added the loop it wouldn't compile...
Can anyone please help me create a trigger that works without using a select statement to see if the sequence nextval is already used?
The example I'm adding here is clunky and performance here would be poor, but I wanted to put out an idea that could perhaps be a starting place.
You mentioned that you don't want to SELECT to check whether NEXTVAL is already used. If you meant that you didn't want to have to perform any SELECT at all, then this answer would be cheating as it does include a SELECT statement. But if you only want to avoid the mutating-table problem, then it could be a possibility.
The approach I'll add here takes a few steps to avoid collision, and runs them anytime a non-NULL value is provided as a key. It is currently set up to fire per-row, but if entire statements will be all-NULL or all-non-NULL then it could possible be changed to a statement- or compound-trigger to improve efficiency a little. In any event, the collision-avoidance is inefficient in this example.
General steps:
- If NULL, just use the NEXTVAL
- If non-NULL, check the LAST_VALUE and CACHE for the SEQUENCE against the provided value.
- If the provided value is within the CACHE (or beyond the cache) and could cause a collision, jump the SEQUENCE beyond the provided value and throw away the values in the cache.
Create the test table/sequence:
CREATE TABLE MY_TABLE (
MY_TABLE_ID NUMBER NOT NULL PRIMARY KEY
);
--Default cache here 20
CREATE SEQUENCE MY_SEQUENCE;
Create an autonomous synchronizer. Please note, this is not at all efficient, and concurrency could be a real problem (A serializing alternative is below).
It assumes a CACHE of at least 1 and may be incompatible with NOCACHE. (Actually the whole situation might be simpler with a NOCACHE SEQUENCE though)
CREATE OR REPLACE PROCEDURE SYNC_MY_SEQUENCE(P_CANDIDATE_MAX_VALUE IN NUMBER)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
V_LAST_NUMBER NUMBER;
V_CACHE_SIZE NUMBER;
V_SEQUENCE_GAP NUMBER;
V_SEQUENCE_DELTA NUMBER;
V_NEXTVAL NUMBER;
BEGIN
SELECT
LAST_NUMBER,
CACHE_SIZE
INTO V_LAST_NUMBER, V_CACHE_SIZE
FROM USER_SEQUENCES
WHERE SEQUENCE_NAME = 'MY_SEQUENCE';
--Only do anything if the provided value could cause a collision.
IF P_CANDIDATE_MAX_VALUE >= (V_LAST_NUMBER - V_CACHE_SIZE)
THEN
-- Get the delta, in case the provided value is way way higher than the SEQUENCE
V_SEQUENCE_DELTA := P_CANDIDATE_MAX_VALUE + V_CACHE_SIZE - V_LAST_NUMBER ;
-- Use the biggest gap to get a safe zone when resetting the SEQUENCE
V_SEQUENCE_GAP := GREATEST(V_SEQUENCE_DELTA, V_CACHE_SIZE);
-- Set the increment so the distance between the last_value and the safe zone can be moved in one jump
EXECUTE IMMEDIATE 'ALTER SEQUENCE MY_SEQUENCE INCREMENT BY '||V_SEQUENCE_GAP;
-- Jump to the safe zone.
V_NEXTVAL := MY_SEQUENCE.NEXTVAL;
-- Reset increment. Note there is a space here that other sessions could get big NEXTVALs from concurrent access
EXECUTE IMMEDIATE 'ALTER SEQUENCE MY_SEQUENCE INCREMENT BY 1';
--Chew through the rest of at least one cache cycle.
FOR CACHE_POINTER IN 1..V_CACHE_SIZE LOOP
V_NEXTVAL := MY_SEQUENCE.NEXTVAL;
END LOOP;
END IF;
COMMIT;
END;
/
EDIT: it would be even more costly, but one might be able to serialize access to manage concurrency with something like the below alternative:
CREATE OR REPLACE PROCEDURE SYNC_MY_SEQUENCE(P_CANDIDATE_MAX_VALUE IN NUMBER)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
V_LAST_NUMBER NUMBER;
V_CACHE_SIZE NUMBER;
V_SEQUENCE_GAP NUMBER;
V_SEQUENCE_DELTA NUMBER;
V_NEXTVAL NUMBER;
V_LOCK_STATUS NUMBER;
V_LOCK_HANDLE VARCHAR2(64);
C_LOCK_KEY CONSTANT VARCHAR2(20) := 'SYNC_MY_SEQUENCE';
BEGIN
DBMS_LOCK.ALLOCATE_UNIQUE (C_LOCK_KEY,V_LOCK_HANDLE,10);
--Serialize access
V_LOCK_STATUS := DBMS_LOCK.REQUEST(
LOCKHANDLE => V_LOCK_HANDLE,
LOCKMODE => DBMS_LOCK.X_MODE,
TIMEOUT => 10,
RELEASE_ON_COMMIT => TRUE);
SELECT
LAST_NUMBER,
CACHE_SIZE
INTO V_LAST_NUMBER, V_CACHE_SIZE
FROM USER_SEQUENCES
WHERE SEQUENCE_NAME = 'MY_SEQUENCE';
IF P_CANDIDATE_MAX_VALUE >= (V_LAST_NUMBER - V_CACHE_SIZE)
THEN
V_SEQUENCE_DELTA := P_CANDIDATE_MAX_VALUE + V_CACHE_SIZE - V_LAST_NUMBER ;
V_SEQUENCE_GAP := GREATEST(V_SEQUENCE_DELTA, V_CACHE_SIZE);
EXECUTE IMMEDIATE 'ALTER SEQUENCE MY_SEQUENCE INCREMENT BY '||V_SEQUENCE_GAP;
V_NEXTVAL := MY_SEQUENCE.NEXTVAL;
EXECUTE IMMEDIATE 'ALTER SEQUENCE MY_SEQUENCE INCREMENT BY 1';
FOR CACHE_POINTER IN 1..V_CACHE_SIZE LOOP
V_NEXTVAL := MY_SEQUENCE.NEXTVAL;
END LOOP;
END IF;
COMMIT;
END;
/
Create the trigger:
CREATE OR REPLACE TRIGGER MAYBE_SET
BEFORE INSERT ON MY_TABLE
FOR EACH ROW
BEGIN
IF :NEW.MY_TABLE_ID IS NULL
THEN
:NEW.MY_TABLE_ID := MY_SEQUENCE.NEXTVAL;
ELSE
SYNC_MY_SEQUENCE(:NEW.MY_TABLE_ID);
END IF;
END;
/
Then test it:
INSERT INTO MY_TABLE SELECT LEVEL FROM DUAL CONNECT BY LEVEL < 5;
SELECT * FROM MY_TABLE ORDER BY 1 ASC;
MY_TABLE_ID
1
2
3
4
It just used the NEXTVAL each time.
Then add a collidable value. Adding this will fire the sproc and do the extra work to push the SEQUENCE into a safe zone.
INSERT INTO MY_TABLE VALUES(5);
SELECT * FROM MY_TABLE ORDER BY 1 ASC;
MY_TABLE_ID
1
2
3
4
5
Then use NULL again:
INSERT INTO MY_TABLE VALUES(NULL);
SELECT * FROM MY_TABLE ORDER BY 1 ASC;
MY_TABLE_ID
1
2
3
4
5
41
The SEQUENCE had a costly operation to get there, but has settled and didn't collide.
If other provided values are below the SEQUENCE visibility, they add freely and don't change the NEXTVAL:
INSERT INTO MY_TABLE VALUES(7);
INSERT INTO MY_TABLE VALUES(19);
INSERT INTO MY_TABLE VALUES(-9999);
INSERT INTO MY_TABLE VALUES(NULL);
SELECT * FROM MY_TABLE ORDER BY 1 ASC;
MY_TABLE_ID
-9999
1
2
3
4
5
7
19
41
42
If the gap is huge, it jumps way out there:
INSERT INTO MY_TABLE VALUES(50000);
INSERT INTO MY_TABLE VALUES(NULL);
SELECT * FROM MY_TABLE ORDER BY 1 ASC;
MY_TABLE_ID
-9999
1
2
3
4
5
7
19
41
42
50000
50022
This could be too costly for your use case, and I haven't tested in in a RAC, but wanted to throw out an idea that can avoid collisions.

Array Input for Stored Procedure

I am a newbie to Oracle and this is my first post for Oracle queries.
Below is the existing query which inserts 1 row for each SP call.
I want to make change in the SP which would accept input as array where SAP system would would send the Array to Stored Procedure.
As you observe in SP, the value of ID is incremented each time with the each update. The SP will take this input of Phone and Text and insert the value of ID in sequence wise.The ID is not passed in the input.
CREATE OR REPLACE PROCEDURE DetailsTable
(
Phoneno IN NUMBER,
Text IN VARCHAR2
)
aS
BEGIN
INSERT INTO PERSON.DETAILS(
ID,
PHONENO,
TEXT,
COUNTRY,
LANG,
--PRIORITY,
SENDER)
VALUES (
DETAILS_seq.nextval ,
p_phoneno,
p_text ,
'RSA',
'EN',
'Nest-Payroll');
commit;
END DetailsTable;
Please guide.
SQL> CREATE OR REPLACE TYPE arraytype AS VARRAY(1000) OF VARCHAR2(100);
2 /
Type created
SQL> CREATE OR REPLACE PROCEDURE test_array (in_array arraytype) IS
2 BEGIN
3 FOR i IN 1..in_array.count LOOP
4 DBMS_OUTPUT.PUT_LINE(in_array(i));
5 END LOOP;
6 END;
7 /
Procedure created
SQL> DECLARE
2 var_array arraytype;
3 BEGIN
4 var_array := arraytype();
5 var_array.EXTEND(10);
6 var_array(1) := '1st sentence in the array';
7 var_array(2) := '2nd sentence in the array';
8 test_array(var_array);
9 END;
10 /
1st sentence in the array
2nd sentence in the array
We can use a Type in SQL but it needs to be declared as a SQL Type:
create or replace type person_t as object
(phoneno number
, text varchar2(100)
);
/
create or replace type person_nt as table of person_t
/
Use it like this:
CREATE OR REPLACE PROCEDURE DetailsTable
(
p_array in person_nt
)
aS
BEGIN
INSERT INTO PERSON.DETAILS(
ID,
PHONENO,
TEXT,
COUNTRY,
LANG,
--PRIORITY,
SENDER)
select DETAILS_seq.nextval ,
t.phoneno,
t.text ,
'RSA',
'EN',
'Nest-Payroll'
from table (p_array)t;
commit;
END DetailsTable;
/

In SAP HANA how can I generate a range of numbers, eg from 1 to 10?

In SAP HANA I wish to have a view which has a range of number from 1 to 10, or 1 to n where n is any number. So when I select from the view I can select n records to get the first n records from the range.
I was able to create a table with 1000 rows with a ID that increment's by using this stored procedure. Is there an easier way?
DROP PROCEDURE "DEMO_PROC";
CREATE PROCEDURE "DEMO_PROC"(
IN ID INTEGER )
LANGUAGE SQLSCRIPT AS
/*********BEGIN PROCEDURE SCRIPT ************/
BEGIN
DECLARE
START_ID INTEGER;
DROP TABLE TEST_TABLE;
CREATE COLUMN TABLE "TEST_TABLE" (ID INTEGER, NAME VARCHAR(10));
START_ID := 0;
WHILE START_ID < 1000 DO
START_ID := START_ID + 1;
INSERT INTO "TEST_TABLE" VALUES(:START_ID, '');
END WHILE;
END;
CALL "DEMO_PROC"(1);
SELECT * FROM "TEST_TABLE";
Using a generator is the prefered way:
INSERT INTO "TEST_TABLE" SELECT GENERATED_PERIOD_START as ID, '' as NAME from SERIES_GENERATE_INTEGER(1,1,1001);
is much easier and faster.
I think for loop is easier than while.
FOR START_ID IN 1..1000 DO
INSERT INTO "TEST_TABLE" VALUES(START_ID,'');
END FOR;

PLSQL - Searching for record in a Nested Table that was Bulk Collected

I used bulk collect to fetch records into a nested table. I want to search for a record with exists method but it's not working out. I then found out the exists method uses index and does not look for the values. Do I need to go across each record and search for a match? Is there a shorter way to do it because I am going to use the same logic for large set of records?
I read in websites that bulk collect doesn't work properly with an associative array when using a varchar as a key so I used nested tables instead. Also, I don't want to read each record and store it in a hashmap as it degrades performance.
Create table sales(
name varchar2(100)
)
insert into sales(name) values('Test');
insert into sales(name) values('alpha');
insert into sales(name) values(null);
declare
type sales_tab is table of varchar2(1000);
t_sal sales_tab;
begin
select name bulk collect into t_sal from sales;
if(t_sal.exists('Test')) THEN
dbms_output.put_line('Test exists');
END IF;
dbms_output.put_line(t_sal.count);
end;
exists() function tells you if a particular element with integer or varchar2(for associative arrays index by varchar2 collections ) index of a collection exists. It does not test for membership. To be able to check if a collection contains an element with specific value member of condition can be used:
SQL> declare
2 type sales_tab is table of varchar2(1000);
3 t_sal sales_tab;
4 begin
5 select name
6 bulk collect into t_sal
7 from sales;
8
9 if('Test' member of t_sal) THEN
10 dbms_output.put_line('Test exists');
11 END IF;
12
13 dbms_output.put_line(t_sal.count);
14 end;
15 /
Test exists
3
PL/SQL procedure successfully completed