Splitting and inserting a string in PL/SQL - sql

I have a table called LOG that has a column named MESSAGE, which is a VARCHAR2(4000).
Since I'm planning a migration and the column MESSAGE in the new database is a VARCHAR2(2000), I want to iterate over all MESSAGE rows with length > 2000 and substring the text after 2000 characters and insert everything of the text that comes after 2000 characters into a new row.
I also have to update the original rows to have 2000 chars.
How can I do this? It's been really a long time since I worked with PL/SQL, I would really appreciate your help.

It can also easily be done with a connect by as it demonstrates in this example where it should split after every 5th character:
select substr(test.test, (level-1)*5, 5)
from (select 'THIS IS A LONG MESSAGE ACTUALLY' test from dual) test
connect by substr(test.test, (level-1)*5, 5) IS NOT NULL
in this scenario you wouldn´t even have to bother about anything at it would automaticly seperate the values no matter if they are longer than 2000 or not.
select substr(l.message, (level-1)*2000, 2000) message
from log l
substr(l.message, (level-1)*2000, 2000) IS NOT NULL
This could be your final select.

One method is:
select . . ., substr(l.message, 1, 2000) as message
from log l
union all
select . . ., substr(l.message, 2001, 2000) as message
from log l
where lenght(l.message) > 2000;

Your problem can be solved using a PLSQL block as below. Please try to implement the logic mentioned inline and check if this works.
Code:
declare
---getting each message of length 4000 characters
cursor cur is
select message
from log;
var number;
var1 varchar2(2000);
cntr number:=0;
begin
--Loop for each message
for i in cur
loop
--getting length of message
var:= length(i.message);
for len in 1..(var / 2000)
loop
--setting the offset to pick 2000 chracters
if cntr = 0 then
cntr := cntr +1;
else
cntr := cntr + 2000;
end if;
--selecting 2000 characters from message
var1:=substr(i.message,cntr,2000);
---inserting 2000 charcters to table
insert into table_log(col1)
values(var1 );
commit;
end loop;
end loop;
end;
Demo:
SQL> create table log(message varchar2(4000));
SQL> select message from log ;
MESSAGE
--------------------------------------------------------------------------------
KHAGDKAGDHAGDKHAGD
ADSJHA:DAH:DHHAHDH
.
.
.
SQL> select length(message) from log ;
LENGTH(MESSAGE)
---------------
3989
SQL> create table table_log(col1 varchar2(2000));
SQL> select col1 from table_log ;
COL1
--------------------------------------------------------------------------------
SQL> /
PL/SQL procedure successfully completed.
--- Two rows created to destination table with size approx 2000 characters each
SQL> select length(col1) from table_log ;
LENGTH(COL1)
------------
2000
1989
In Simple SQL you can do it as
insert into table_log(col1)
select SUBSTR(l.MESSAGE, 1, 2000) AS MESSAGE
FROM LOG l
UNION ALL
select SUBSTR(l.MESSAGE, 2001, 2000) AS MESSAGE
FROM LOG l
WHERE length(l.MESSAGE) > 2000;

You can split rows during copying to new table. For this you should use INSERT ALL WHEN. For each WHEN clause whose condition evaluates to true, the database executes the corresponding INTO clause list.
create table src_test_table(long_msg varchar2(20));
create table dest_test_table(long_msg varchar2(10));
insert into src_test_table values(lpad('1',20,'1'));
insert into src_test_table values(lpad('2',20,'2'));
insert into src_test_table values(lpad('3',20,'3'));
insert into src_test_table values(lpad('4',10,'4'));
insert into src_test_table values(lpad('5',10,'5'));
insert all
when length(long_msg) <= 10 then
into dest_test_table values(long_msg)
when length(long_msg) > 10 then
into dest_test_table values(substr(long_msg,1,10))
when length(long_msg) > 10 then
into dest_test_table values(substr(long_msg,11))
select long_msg from src_test_table;
And results;
select long_msg,length(long_msg) from dest_test_table;

Related

How to generate SQL Code to update 9k rows with values for one column

I need to update one field for 9000 rows with random information on it.
Keep in mind that the table in question does not have Primary Key.
What's the best option to do it?
Thanks in advance.
Create table to hold 9000 random numbers for demonstration purposes:
CREATE TABLE brianl.random
(
VALUE NUMBER
, id INTEGER
);
Insert 9000 rows into the table
Since this is for demonstration purposes, I decided to use a PL/SQL loop as it is easy to understand even if you are not familiar with PL/SQL. I could just as easily used a Common Table Expression (CTE), but if you don't understand the PL/SQL, you won't understand the CTE
DECLARE
l_cnt INTEGER;
BEGIN
FOR i IN 1 .. 9000
LOOP
INSERT INTO random (id)
VALUES (i);
END LOOP;
COMMIT;
SELECT COUNT (*) c
INTO l_cnt
FROM random;
DBMS_OUTPUT.put_line ('-- '
|| TO_CHAR (SYSDATE, 'YYYY.MM.DD HH24:MI:SS')
|| ' count is: '
|| l_cnt);
END;
-- 2016.11.11 10:28:42 count is: 9000
Update 9000 records with random values with a single update statement
Now that we have a table of 9000 values, we can update 9000 values as requested`.
update random set value = dbms_random.value;
commit;
-- You can see those 9000 random values with a select from the table we
created for this purpose
select * from random;
Alternatives to PL/SQL for creating table records
And finally, some people have commented that the PL/SQL is bad practice for creating the values (I disagree, to me it is straightforward, and because it is procedural, easy to understand for the beginner), Here is a CTE that performs the same function. Don't forget to commit after it has finished executing.
INSERT INTO random (id)
WITH iset (num)
AS (SELECT 1
FROM DUAL
UNION ALL
SELECT num + 1
FROM iset
WHERE num < 9000)
SELECT *
FROM iset;
Another method people sometimes use is to select from all_objects. I personally don't care for this method as I believe that tables should only be used for their stated purpose. This is also defective if you need to go above the number of objects in all_objects, which on the system I am using is 82,009,
INSERT INTO random (id)
SELECT ROWNUM
FROM all_objects
WHERE ROWNUM <= 9000;
So, on the Oracle system I am creating these demonstrations on, the following will only insert 82009 rows into the table, not the requested 100000.
INSERT INTO random (id)
SELECT ROWNUM
FROM all_objects
WHERE ROWNUM <= 100000;

How to fill some column with constant string followed by variable number

I need to fill a table column (in Oracle database) with string values that have variable part, e. g. AB0001, AB0002,...,AB0112...,AB9999, where AB is constant string part, 0001 -9999 is variable number part. i've tried the following solution in SQL for a table with 2 columns:
create table tbl1
(seq1 number(8),
string1 varchar(32));
declare
tst number(8) :=0;
begin
for cntr in 1..100
loop
tst := cntr;
insert into TBL1 values (someseq.nextval, concat('AB',tst));
end loop;
end;
But in this case I get STRING1 filled with values AB1,AB2,...,AB10,.. which is not exactly what I need.
How should I modify my script to insert values like AB0001,...,AB0010?
Either pad the number with zeros, or format it with leading zeros:
insert into TBL1
values (someseq.nextval, concat('AB', to_char(tst, 'FM0000'));
The 'FM' format modifier prevents a space being added (to allow for a minus sign).
For your specific example you don't need a PL/SQL block; you could use a hierarchical query to generate the data for the rows:
insert into tbl1(seq1, string1)
select someseq.nextval, concat('AB', to_char(level, 'FM0000'))
from dual
connect by level <= 100;
use the lpad function
select lpad(1, 4, '0') from dual
--> '0001'
try this one
INSER INTO table_name(code)
VALUES(CONCAT('AB', LPAD('99', 4, '0'));
or You can Update on the basis of PK after insertion
UPDATE table_name SET code = CONCAT('AB', LPAD(PK_Column_Name, 4, '0') ;
or You Can Use Triggers
CREATE TRIGGER trgI_Terms_UpdateTermOrder
ON DB.table_name
AFTER INSERT
AS
BEGIN
UPDATE t
SET code = CONCAT('AB', LPAD(Id, 4, '0')
FROM DB.table_name t INNER JOIN inserted i ON t.Id = I.Id
END;
GO

How to transpose a table from a wide format to narrow, using the values as a filter?

I get a table X (with 1 row):
COL_XA COL_VG COL_LF COL_EQ COL_PP COL_QM ...
1 0 0 0 1 1
Each column COL_x can have only values 0 or 1.
I want to transform this table into this form Y:
NAME
"COL_XA"
"COL_PP"
"COL_QM"
...
This table should print only those columns from table X that the first (and only) row has value 1.
This question is related to any other question about transposition, with the difference that I don't want the actual values, but the column names, which are not known in advance.
I could use Excel or PL/SQL to create a list of strings of the form
MIN(CASE WHEN t.COL_XA = 1 THEN 'COL_XA' ELSE null END) as NAME, but this solution is inefficient (EXECUTE IMMEDIATE) and difficult to maintain. And the string passed to EXECUTE IMMEDIATE is limited to 32700 characters, which can be easily exceeded in production, where the table X can have well over 500 fields.
To completly automate the query you must be able to read the column names of the actual cursor. In PL/SQL this is possible using DBMS_SQL (other way would be in JDBC). Based on this OTN thread here a basic table function.
The importent parts are
1) dbms_sql.parse the query given as a text string and dbms_sql.execute it
2) dbms_sql.describe_columns to get the list of the column names returned from the query on table x
3) dbms_sql.fetch_rows to fetch the first row
4) loop the columns and checking the dbms_sql.column_value if equals to 1 output column_name (with PIPE)
create or replace type str_tblType as table of varchar2(30);
/
create or replace function get_col_name_on_one return str_tblType
PIPELINED
as
l_theCursor integer default dbms_sql.open_cursor;
l_columnValue varchar2(2000);
l_columnOutput varchar2(4000);
l_status integer;
l_colCnt number default 0;
l_colDesc dbms_sql.DESC_TAB;
begin
dbms_sql.parse( l_theCursor, 'SELECT * FROM X', dbms_sql.native );
for i in 1 .. 1000 loop
begin
dbms_sql.define_column( l_theCursor, i,
l_columnValue, 2000 );
l_colCnt := i;
exception
when others then
if ( sqlcode = -1007 ) then exit;
else
raise;
end if;
end;
end loop;
dbms_sql.define_column( l_theCursor, 1, l_columnValue, 2000 );
l_status := dbms_sql.execute(l_theCursor);
dbms_sql.describe_columns(l_theCursor,l_colCnt, l_colDesc);
if dbms_sql.fetch_rows(l_theCursor) > 0 then
for lColCnt in 1..l_colCnt
loop
dbms_sql.column_value( l_theCursor, lColCnt, l_columnValue );
--DBMS_OUTPUT.PUT_LINE( l_columnValue);
IF (l_columnValue = '1') THEN
DBMS_OUTPUT.PUT_LINE(Upper(l_colDesc(lColCnt).col_name));
pipe row(Upper(l_colDesc(lColCnt).col_name));
END IF;
end loop;
end if;
return;
end;
/
select * from table(get_col_name_on_one);
COLUMN_LOOOOOOOOOOOOOONG_100
COLUMN_LOOOOOOOOOOOOOONG_200
COLUMN_LOOOOOOOOOOOOOONG_300
COLUMN_LOOOOOOOOOOOOOONG_400
COLUMN_LOOOOOOOOOOOOOONG_500
COLUMN_LOOOOOOOOOOOOOONG_600
COLUMN_LOOOOOOOOOOOOOONG_700
COLUMN_LOOOOOOOOOOOOOONG_800
COLUMN_LOOOOOOOOOOOOOONG_900
COLUMN_LOOOOOOOOOOOOOONG_1000
You should not get in troubles with wide tables using this solution, I tested with a 1000 column tables with long column names.
Here is solution but I have to break it in two parts
First you extract all the column names of table. I have used LISTAGG to collect column names separated by ,
I will use the output of first query in second query.
select listagg(column_name,',') WITHIN GROUP (ORDER BY column_name )
from user_tab_cols where upper(table_name)='X'
The output of above query will be like COL_XA,COL_VG,COL_LF,COL_EQ,COL_PP,COL_QM ... and so on.
Copy above output and use in below query replacing
select NAME from X
unpivot ( bit for NAME in (<outputvaluesfromfirstquery>))
where bit=1
I am trying to merge above two, but I have option for pivot xml but not for unpivot xml.
You can do this with a bunch of union alls:
select 'COL_XA' as name from table t where col_xa = 1 union all
select 'COL_VG' as name from table t where col_vg = 1 union all
. . .
EDIT:
If you have only one row, then you do not need:
MIN(CASE WHEN t.COL_XA = 1 THEN 'COL_XA' ELSE null END) as NAME
You can simply use:
(CASE WHEN t.COL_XA = 1 THEN 'COL_XA' END)
The MIN() isn't needed for one row and the ELSE null is redundant.

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;

Using SEQUENCE(Oracle) in WHERE Clause [duplicate]

The following Oracle SQL code generates the error "ORA-02287: sequence number not allowed here":
INSERT INTO Customer (CustomerID,Name) VALUES (Customer_Seq.nextval,'AAA');
SELECT * FROM Customer where CustomerID=Customer_Seq.currval;
The error occurs on the second line (SELECT statement). I don't really understand the problem, because this does work:
INSERT INTO Customer (CustomerID,Name) VALUES (Customer_Seq.nextval,'AAA');
SELECT Customer_Seq.currval from dual;
You have posted some sample code, so it is not clear what you are trying to achieve. If you want to know the assigned value, say for passing to some other procedure you could do something like this:
SQL> var dno number
SQL> insert into dept (deptno, dname, loc)
2 values (deptno_seq.nextval, 'IT', 'LONDON')
3 returning deptno into :dno
4 /
1 row created.
SQL> select * from dept
2 where deptno = :dno
3 /
DEPTNO DNAME LOC
---------- -------------- -------------
55 IT LONDON
SQL>
Edit
We can use the RETURNING clause to get the values of any column, including those which have been set with default values or by trigger code.
You don't say what version of Oracle you are using. There have in the past been limitations on where sequences can be used in PL/SQL - mostly if not all gone in 11G. Also, there are restrictions in SQL - see this list.
In this case you may need to write:
SELECT Customer_Seq.currval INTO v_id FROM DUAL;
SELECT * FROM Customer where CustomerID=v_id;
(Edited after comments).
This doesn't really directly answer your question, but maybe what you want to do can be resolved using a the INSERT's RETURNING clause?
DECLARE
-- ...
last_rowid rowid;
-- ...
BEGIN
-- ...
INSERT INTO Customer (CustomerID,Name) VALUES (Customer_Seq.nextval,'AAA') RETURNING rowid INTO last_rowid;
SELECT * FROM Customer where rowid = last_rowid;
-- ...
END;
/
You may not use a sequence in a WHERE clause - it does look natural in your context, but Oracle does not allow the reference in a comparison expression.
[Edit]
This would be a PL/SQL implementation:
declare
v_custID number;
cursor custCur is
select customerid, name from customer
where customerid = v_custID;
begin
select customer_seq.nextval into v_custID from dual;
insert into customer (customerid, name) values (v_custID, 'AAA');
commit;
for custRow in custCur loop
dbms_output.put_line(custRow.customerID||' '|| custRow.name);
end loop;
end;
You have not created any
sequence
First create any sequence its cycle and cache. This is some basic example
Create Sequence seqtest1
Start With 0 -- This Is Hirarchy Starts With 0
Increment by 1 --Increments by 1
Minvalue 0 --With Minimum value 0
Maxvalue 5 --Maximum Value 5. So The Cycle Of Creation Is Between 0-5
Nocycle -- No Cycle Means After 0-5 the Insertion Stopes
Nocache --The cache Option Specifies How Many Sequence Values Will Be Stored In Memory For Faster Access
You cannot do Where Clause on Sequence in SQL beacuse you cannot filter a sequence . Use procedures like #APC said