How to write oracle insert script with one field as CLOB? - oracle

I want to create an insert script which will be used only to insert one record into one table.
It has 5 columns and one of them is of type CLOB.
Whenever I try, it says can not insert string is so long . larger than 4000.
I need an insert statement with clob as one field.
INSERT INTO tbltablename
(id,
NAME,
description,
accountnumber,
fathername)
VALUES (1,
N'Name',
clob'some very long string here, greater than 4000 characters',
23,
'John') ;

Keep in mind that SQL strings can not be larger than 4000 bytes, while Pl/SQL can have strings as large as 32767 bytes. see below for an example of inserting a large string via an anonymous block which I believe will do everything you need it to do.
note I changed the varchar2(32000) to CLOB
set serveroutput ON
CREATE TABLE testclob
(
id NUMBER,
c CLOB,
d VARCHAR2(4000)
);
DECLARE
reallybigtextstring CLOB := '123';
i INT;
BEGIN
WHILE Length(reallybigtextstring) <= 60000 LOOP
reallybigtextstring := reallybigtextstring
|| '000000000000000000000000000000000';
END LOOP;
INSERT INTO testclob
(id,
c,
d)
VALUES (0,
reallybigtextstring,
'done');
dbms_output.Put_line('I have finished inputting your clob: '
|| Length(reallybigtextstring));
END;
/
SELECT *
FROM testclob;
"I have finished inputting your clob: 60030"

I solved my problem with a solution that is simpler than the most voted answer.
You must divide your big clob string into multiple strings, each one with less than 4000 chars, convert each one using the to_clob method, and concatenate them with the || operator.
Here is an example of the final insert statement:
INSERT INTO tbltablename
(id,
name,
big_clob_description)
VALUES (1,
N'A Name',
to_clob('string with less than 4000 chars')
|| to_clob('rest of string here, with less than 4000 chars')
) ;
My insert code was generated by a script, and it wasn't difficult to break the strings.

You can use the to_clob function too.
INSERT INTO tbltablename
(id,
NAME,
description,
accountnumber,
fathername)
VALUES (1,
N'Name',
to_clob('clob''some very long string here, greater than 4000 characters'),
23,
'John') ;
You can find more information here: https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions182.htm.
Regards.

Related

Writing a Version Number Function in PL/SQL

I want to write a function that will give me the next version number for a table. The table stores the existing version on each record. For example,
I have the cat table
cats
seqid 1
name Mr Smith
version number 1.2b.3.4
How can I write a program that will be able to increment these values based on various conditions?
This is my first attempt
if v_username is not null
then v_new_nbr = substr(v_cur_nbr, 1,7)||to_number(substr(v_cur_nbr, 8,1))+1
should be 1.2b.3.5
substr(v_cur_nbr, 1,7)||to_number(substr(v_cur_nbr, 8,1))+1
This hurls ORA-01722: invalid number. The reason is a subtle one. It seems Oracle applies the concatenation operator before the additions, so effectively you're adding one to the string '1.2b.3.4'.
One solution is using a TO_CHAR function to bracket the addition with the second substring before concatenating the result with the first substring:
substr(v_cur_nbr, 1,7) || to_char(to_number(substr(v_cur_nbr, 8,1))+1)
Working demo on db<>fiddle.
Incidentally, a key like this is a bad piece of data modelling. Smart keys are dumb. They always lead to horrible SQL (as you're finding) and risk data corruption. A proper model would have separate columns for each element of the version number. We can use virtual columns to concatenate the version number for display circumstances.
create table cats(
seqid number
,name varchar2(32)
,major_ver_no1 number
,major_ver_no2 number
,variant varchar2(1)
,minor_ver_no1 number
,minor_ver_no2 number
,v_cur_nbr varchar2(16) generated always as (to_char(major_ver_no1,'FM9') ||'.'||
to_char(major_ver_no2,'FM9') ||'.'||
variant ||'.'||
to_char(minor_ver_no1,'FM9') ||'.'||
to_char(minor_ver_no2,'FM9') ) );
So the set-up is a bit of a nause but incrementing the version numbers is a piece of cake.
update cats
set major_ver_no1 = major_ver_no1 +1
, major_ver_no2 = 0
, variant = 'a';
There's a db<>fiddle for that too.
Try searching mask for TO_NUMBER to be able to get the decimal number, this small example might help:
CREATE TABLE tmp_table (version varchar2(100));
INSERT INTO tmp_table(version) VALUES ('1.2b.3.4');
DECLARE
mainVersion NUMBER;
subVersion NUMBER;
currentVersion VARCHAR2(100);
BEGIN
SELECT version INTO currentVersion FROM tmp_table;
mainVersion := TO_NUMBER(SUBSTR(currentVersion,1,3),'9.9') + 0.1;
subVersion := TO_NUMBER(SUBSTR(currentVersion,6,3),'9.9') + 1.1;
UPDATE tmp_table SET version = (mainVersion||'b.'||subVersion);
END;

execute SQL query stored in a table

I have a table having one of the columns that stores SQL query returning ids or it stores comma separated ids.
create table to store query or ids(separated by ,)
create table test1
(
name varchar(20) primary key,
stmt_or_value varchar(500),
type varchar(50)
);
insert into test1 (name, stmt_or_value, type)
values ('first', 'select id from data where id = 1;','SQL_QUERY')
insert into test1 (name, stmt_or_value, type)
values ('second', '1,2,3,4','VALUE')
data table is as follows
create table data
(
id number,
subject varchar(500)
);
insert into data (id, subject) values (1, 'test subject1');
insert into data (id, subject) values (2, 'test subject2');
insert into data (id, subject) values (3, 'test subject2');
I am not able to formulate query that will return values after either executing stored sql or parsing stored ids based on the value of name.
select id, subject
from data
where id in( EXECUTE IMMEDIATE stmt_or_value
where type='SQL_QUERY'
and name = 'first') or
( parse and return ids
from stmt_or_value
where type='VALUE'
and name = 'second')
Could you please help me in this.
Parsing comma separated value is done, I basically need help in below first part of the query:
( EXECUTE IMMEDIATE stmt_or_value
where type='SQL_QUERY'
and name = 'first')
This seems a very peculiar requirement, and one which will be difficult to solve in a robust fashion. STMT_OR_VALUE is the embodiment of the One Column Two Usages anti-pattern. Furthermore, resolving STMT_OR_VALUE requires flow control logic and the use of dynamic SQL. Consequently it cannot be a pure SQL solution: you need to use PL/SQL to assemble and execute the dynamic query.
Here is a proof of concept for a solution. I have opted for a function which you can call from SQL. It depends on one assumption: every query string you insert into TEST1.STMT_OR_VALUE has a projection of a single numeric column and every value string is a CSV of numeric data only. With this proviso it is simple to construct a function which either executes a dynamic query or tokenizes the string into a series of numbers; both of which are bulk collected into a nested table:
create or replace function get_ids (p_name in test1.name%type)
return sys.odcinumberlist
is
l_rec test1%rowtype;
return_value sys.odcinumberlist;
begin
select * into l_rec
from test1
where name = p_name;
if l_rec.type = 'SQL_QUERY' then
-- execute a query
execute immediate l_rec.stmt_or_value
bulk collect into return_value;
else
-- tokenize a string
select xmltab.tkn
bulk collect into return_value
from ( select l_rec.stmt_or_value from dual) t
, xmltable( 'for $text in ora:tokenize($in, ",") return $text'
passing stmt_or_value as "in"
columns tkn number path '.'
) xmltab;
end if;
return return_value;
end;
/
Note there is more than one way of executing a dynamic SQL statement and a multiplicity of ways to tokenize a CSV into a series of numbers. My decisions are arbitrary: feel free to substitute your preferred methods here.
This function can be invoked with a table() call:
select *
from data
where id in ( select * from table(get_ids('first'))) -- execute query
or id in ( select * from table(get_ids('second'))) -- get string of values
/
The big benefit of this approach is it encapsulates the logic around the evaluation of STMT_OR_VALUE and hides use of Dynamic SQL. Consequently it is easy to employ it in any SQL statement whilst retaining readability, or to add further mechanisms for generating a set of IDs.
However, this solution is brittle. It will only work if the values in the test1 table obey the rules. That is, not only must they be convertible to a stream of single numbers but the SQL statements must be valid and executable by EXECUTE IMMEDIATE. For instance, the trailing semi-colon in the question's sample data is invalid and would cause EXECUTE IMMEDIATE to hurl. Dynamic SQL is hard not least because it converts compilation errors into runtime errors.
Following is the set up data used for this example:
create table test1
(
test_id number primary key,
stmt_or_value varchar(500),
test_type varchar(50)
);
insert into test1 (test_id, stmt_or_value, test_type)
values (1, 'select id from data where id = 1','SQL_QUERY');
insert into test1 (test_id, stmt_or_value, test_type)
values (2, '1,2,3,4','VALUE');
insert into test1 (test_id, stmt_or_value, test_type)
values (3, 'select id from data where id = 5','SQL_QUERY');
insert into test1 (test_id, stmt_or_value, test_type)
values (4, '3,4,5,6','VALUE');
select * from test1;
TEST_ID STMT_OR_VALUE TEST_TYPE
1 select id from data where id = 1 SQL_QUERY
2 1,2,3,4 VALUE
3 select id from data where id = 5 SQL_QUERY
4 3,4,5,6 VALUE
create table data
(
id number,
subject varchar(500)
);
insert into data (id, subject) values (1, 'test subject1');
insert into data (id, subject) values (2, 'test subject2');
insert into data (id, subject) values (3, 'test subject3');
insert into data (id, subject) values (4, 'test subject4');
insert into data (id, subject) values (5, 'test subject5');
select * from data;
ID SUBJECT
1 test subject1
2 test subject2
3 test subject3
4 test subject4
5 test subject5
Below is the solution:
declare
sql_stmt clob; --to store the dynamic sql
type o_rec_typ is record(id data.id%type, subject data.subject%type);
type o_tab_typ is table of o_rec_typ;
o_tab o_tab_typ; --to store the output records
begin
--The below SELECT query generates the required dynamic SQL
with stmts as (
select (listagg(stmt_or_value, ' union all ') within group(order by stmt_or_value))||' union all ' s
from test1 t
where test_type = 'SQL_QUERY')
select
q'{select id, subject
from data
where id in (}'||
nullif(s,' union all ')||q'{
select distinct to_number(regexp_substr(s, '[^,]+', 1, l)) id
from (
select level l,
s
from (select listagg(stmt_or_value,',') within group(order by stmt_or_value) s
from test1
where test_type = 'VALUE') inp
connect by level <= length (regexp_replace(s, '[^,]+')) + 1))}' stmt into sql_stmt
from stmts; -- Create the dynamic SQL and store it into the clob variable
--execute the statement, fetch and display the output
execute immediate sql_stmt bulk collect into o_tab;
for i in o_tab.first..o_tab.last
loop
dbms_output.put_line('id: '||o_tab(i).id||' subject: '||o_tab(i).subject);
end loop;
end;
Output:
id: 1 subject: test subject1
id: 2 subject: test subject2
id: 3 subject: test subject3
id: 4 subject: test subject4
id: 5 subject: test subject5
Learnings:
Avoid using key words for table and column names.
Design application tables effectively to serve current and reasonable future requirements.
The above SQL will work. Still it is wise to consider reviewing the table design because, the complexity of the code will keep increasing with changes in requirements in future.
Learned how to convert comma separated values into records. "https://asktom.oracle.com/pls/apex/f?p=100:11:::NO::P11_QUESTION_ID:9538583800346706523"
declare
my_sql varchar2(1000);
v_num number;
v_num1 number;
begin
select stmt_or_value into my_sql from test1 where ttype='SQL_QUERY';
execute immediate my_sql into v_num;
select id into v_num1 from data where id=v_num;
dbms_output.put_line(v_num1);
end;
Answer for part 1.Please check.

Can't use oracle associative array in object (for custom aggregate function)

Background : My purpose is to write an aggregate function in oracle to make a string contains number of occurrence of each element. For example "Jake:2-Tom:3-Jim:5" should means 2 times occurrence for Jake, 3 times for Tom and 5 times for Jim. So for writing a custom aggregate function I should write an object implements ODCIAggregate routines. And also a Map like data structure for counting each element occurrences. Only Map like data structure in oracle is associative array.
Problem : Unfortunately I can't know any approach to use associative arrays in object. I tried these approaches:
1 – Create a generic type for associative array and use it in object. Oracle doesn't let creating generic associative array types.
CREATE TYPE STR_MAP IS TABLE OF NUMBER INDEX BY VARCHAR2(100);
This get following error :
PLS-00355: use of pl/sql table not allowed in this context
2 – Create map like type in a package and use it in object. Oracle lets creating an associative array in a package, but doesn’t let using an 'in package type' in object. I checked all issues about grant execute on package or make a synonym for 'in package type'. But there is no way for use 'in package type' in object declaration.
P.S. 1 :
Of course we can do it for one column by nested group by. But I prefer to do it for many columns with only agg-func. It is very useful agg-func and I wonder why nobody wrote something like this before. For many columns we have limited number of distinct values, and with such an agg-func we can simply summarize all of them. For example if we had such a agg-func named ocur_count(), we can simply analyze an collection of transactions like this :
select ocur_count(trans_type), ocur_count(trans_state), ocur_count(response_code), ocur_count(something_status) from transaction;
You can use listagg and a simple group by with a count to get what you need (Note the listagg output is limited in size to 4k chars). Here I'm counting occurrences of first names, using ',' as the separator between names and ':' as the separator for count:
SQL> create table person_test
(
person_id number,
first_name varchar2(50),
last_name varchar2(50)
)
Table created.
SQL> insert into person_test values (1, 'Joe', 'Blow')
1 row created.
SQL> insert into person_test values (2, 'Joe', 'Smith')
1 row created.
SQL> insert into person_test values (3, 'Joe', 'Jones')
1 row created.
SQL> insert into person_test values (4, 'Frank', 'Rizzo')
1 row created.
SQL> insert into person_test values (4, 'Frank', 'Jones')
1 row created.
SQL> insert into person_test values (5, 'Betty', 'Boop')
1 row created.
SQL> commit
Commit complete.
SQL> -- get list of first names and counts into single string
SQL> --
SQL> -- NOTE: Beware of size limitations of listagg (4k chars if
SQL> -- used as a SQL statement I believe)
SQL> --
SQL> select listagg(person_count, ',')
within group(order by person_count) as person_agg
from (
select first_name || ':' || count(1) as person_count
from person_test
group by first_name
order by first_name
)
PERSON_AGG
--------------------------------------------------------------------------------
Betty:1,Frank:2,Joe:3
1 row selected.
NOTE: If you do run into a problem with string concatenation too long (exceeds listagg size limit), you can choose to return a CLOB using xmlagg:
-- this returns a CLOB
select rtrim(xmlagg(xmlelement(e,person_count,',').extract('//text()') order by person_count).GetClobVal(),',')
from (
select first_name || ':' || count(1) as person_count
from person_test
group by first_name
order by first_name
);
Hope that helps
EDIT:
If you want counts for multiple columns (firstname and lastname in this example), you can do:
select
typ,
listagg(cnt, ',') within group(order by cnt)
as name_agg
from (
-- FN=FirstName, LN=LastName
select 'FN' as typ, first_name || ':' || count(1) as cnt
from person_test
group by first_name
union all
select 'LN' as typ, last_name || ':' || count(1) as cnt
from person_test
group by last_name
)
group by typ;
Output:
"FN" "Betty:1,Frank:2,Joe:3"
"LN" "Blow:1,Boop:1,Jones:2,Rizzo:1,Smith:1"
I'd also note that you probably can create a custom aggregate function to do this, I just prefer to stick with built in functionality of SQL first if it can solve my problem.

How to implement multi value parameter in spagoBi server

I made a birt report in spagoBI studio with multi_value parameter it works fine in studio.
But when i upload it to server it's execution give a blank page.
pls someone help me.
Has been a couple of years since the queston was asked but I thought I would post my experience with this issue as it may help others out there.
The problem is due to the fact that each value in the parameter string is being wrapped with single quotes so no where condition is met in your report's sql where statement.
So if you are using Postgresql see: https://www.spagoworld.org/jforum/posts/list/382.page. However if like me you are using MySQL then that where all the fun and games begin because MySQL does not have a ready to use regxp_split_to_table function! What worked for me was to use temporary tables and a stored procedure to return the report dataset. I then called the procedure in the report's queryString.
So the following were the steps I took:
create a function to split out each parameter from the multivalue String and remove the single quotes:
CREATE DEFINER=root#localhost FUNCTION SPLIT_STR(
x VARCHAR(255),
delim VARCHAR(12),
pos INT
) RETURNS varchar(255) CHARSET utf8
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos),
LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1),
delim, '')
Create a stored procedure to return the parameters as a resultset that can be matched via your sql's where in ($P{parameter}) statement. Thet trick here is to take the split and cleaned up parameter and insert it into a temporary table that can then be queried in the subsequent select statement that returns the dataset. My stored procedure looks like:
CREATE DEFINER=root#localhost PROCEDURE create_temp_breweries(fullstr varchar(255), startDate date, endDate date, outlet_Type varchar(255))
BEGIN
DECLARE a INT Default 0 ;
DECLARE b INT Default 0 ;
DECLARE str VARCHAR(255);
DECLARE outletStr VARCHAR(255);
drop temporary table if exists temp_table1;
create temporary table temp_breweries(col1 varchar(255));
drop temporary table if exists temp_table2;
create temporary table temp_outletTypes(col2 varchar(255));
loop1: LOOP
SET a=a+1;
SET str= REPLACE(SPLIT_STR(fullstr,",",a),'\'', '');
IF str='' THEN
LEAVE loop1;
END IF;
#Do Inserts into temp table here with str going into the row
insert into temp_table1 values (str);
END LOOP loop1;
loop2: LOOP
SET b=b+1;
SET outletStr= REPLACE(SPLIT_STR(outlet_Type,",",b),'\'', '');
IF outletStr='' THEN
LEAVE loop2;
END IF;
#Do Inserts into temp table here with outletStr going into the row
insert into temp_table2 values (outletStr);
# For testing: insert into mytest (brewery) values (outletStr);
END LOOP loop2;
SELECT [fields]
FROM
[tables]
WHERE
BINARY field IN (SELECT * FROM temp_table1)
AND DATE BETWEEN startDate AND endDate AND
BINARY field2 IN (SELECT * FROM temp_table2);
END
Got this from: MySQL Split Comma Separated String Into Temp Table.
I am sure there must be a better or easier way but this worked like a charm!

How to show star at first two character of a string in oracle query?

Example if an ID is 1213 i want show **13.
If it's a number
select '**' || substr(to_char(id),3)
from my_table
Or, if it's already a character
select '**' || substr(id,3)
from my_table
This concatenates ** onto the beginning of the string, using the Oracle concatenation operator || and removes the first two characters of the id using substr.
Here's a SQL Fiddle to demonstrate.
If you don't want to sacrifice performance too much, to mask first two characters you can use-
SQL> select regexp_replace('1213','(.)2','**') from dual; --if VARCHAR
MASKED
------------
**13
SQL> select regexp_replace(1213,'(.)2','**') from dual; --if NUMBER
MASKED
------------
**13
REGEXP_REPLACE will work alike on NUMBER and VARCHAR so you save some conversion time there.
Consecutively, you can create a Function Based Index on the regexp function operation to optimize the query like (considering you would always want to mask only first two characters of ID) -
CREATE INDEX
mask_id
ON
table_name
(regexp_replace(id,'(.)2','**'));

Resources