How to subtract two columns of Longvarchar in oracle - oracle

I have TABLE1 with columns COL1,COL2 defined as LongVarChar in Oracle.
Every time user loads a record to TABLE1, COL1 will have the old value and COL2 will have the old value + new value. i.e.
For ex:
COL1 COL2
Bat
Bat Bat Ball
Bat Ball Bat Ball Wicket
Bat Ball Wicket Bat Ball Wicket Stump
To get the only new value, i am planning to delete contents of COL2 from COL1 contents..I think this is a pretty bad idea..
I tried the usual "-" operator and it says "Expected Numeric but received CLOB"
But does any one any better idea or how to accomplish this?
Thanks so much

Assuming you mean Varchar2, and (as in your example), each old value is separated by an additional space, then something like this should work:
SELECT SUBSTR(col2, NVL(LENGTH(col1)+2,0)) "New Value" FROM TABLE1
eg:
select substr('Bat', nvl(length(null)+2,0)) from dual;
results in 'Bat'
select substr('Bat Ball', nvl(length('Bat')+2,0)) from dual;
results in 'Ball'
But if you're actually talking about a CLOB datatype, then substitute:
dbms_lob.substr( colX, 4000, 1)
instead of colX (where X is 1 or 2)
[This will give you the first 4000 bytes of the CLOB - in order to get more, you'd have to use PL/SQL]

Related

Getting the last character in an Oracle CLOB column

I am having an issue with getting the last character from an Oracle CLOB column. The same SQL that works on an VARCHAR column doesn't work on a CLOB column and can't figure out why or how to make it work.
(this works in getting the last character from a VARCHAR)
select substr(COLUMN_NAME, length(COLUMN_NAME), 1) from TABLE_NAME
(when applied to a CLOB column, it doesn't return the last character.
select substr(CLOB_COLUMN, length(CLOB_COLUMN), 1) from TABLE_NAME
(Updates below)
Per Alex suggestion, I added the dump/cast and get "Typ=1 Len=1: 10" for that column. (I have no idea what that is)
Here is the SQL I was running:
set linesize 32000
select
length(COLUMN_NAME) as clob_length,
substr(trim(COLUMN_NAME), length(trim(COLUMN_NAME)) -50 ) as last_50,
SUBSTR(COLUMN_NAME, -1) as last_char,
dump(cast(substr(COLUMN_NAME, -1) as varchar2(1)))
from TABLENAME
I added the SQL I used and the results for one of my rows are:
CLOB_LENGTH: 1227
LAST_50: shall be permitted to be continued in service
LAST_CHAR:
VARCHAR2(1): Typ=1 Len=1: 10
(I am not clear as to what the last value means)
Works for my simple test case:
SQL> create table test (col clob);
Table created.
SQL> insert into test (col) values ('This is a cloB');
1 row created.
SQL> select substr(col, length(col), 1) from test;
SUBSTR(COL,LENGTH(COL),1)
--------------------------------------------------------------------------------
B
Though, it is simpler to use substr(col, -1) (take the last character):
SQL> select substr(col, -1) from test;
SUBSTR(COL,-1)
--------------------------------------------------------------------------------
B
SQL>
I added the dump/cast and get "Typ=1 Len=1: 10" for that column
As suspected from your image, your CLOB values end with new lines (character 10, which is what the dump output shows), and probably other whitespace, or perhaps line feed (character 13) if the original data came from, say, Word. That's why the 'last 50' isn't showing 50 'normal' characters - there are multiple blank lines taking up some of those positions.
If you want to ignore all of those trailing characters you can change your trim to include more characters, and apply that to both substr calls; something like:
substr(rtrim(clob_column, chr(32)||chr(9)||chr(10)||chr(13)), -1)
which will remove any combination of spaces, tabs, new lines and line feeds.
db<>fiddle showing the 50 character version now appears as a single line, and the last character is null, '.', 'n', 'e' etc.

Oracle query looking for particular string format

Am working with an Oracle 11g db in which I need to run a query that returns all rows with values in a specific format.
I.e., I need to find all rows with a value like 'abc1234'. The actual values aren't important....what I need is to find all rows with the first 3 characters being alpha and the subsequent 4 characters all being numeric.
Any input would be much appreciated.
Might not be exact since I don't have an Oracle server handy to test but something like this should get you started in the right direction:
SELECT * FROM your_table WHERE REGEXP_LIKE(your_column, '([a-z]\3[0123456789]\4', 'i')
This will check all rows in your table where the specified column has any alphabet character A to Z three times followed by 4 numbers and return that list. The 'i' at the end just says ignore case on the alphabet part. You can choose to not have that option if you so wish.
Here are a couple links as well with more information on the regexp_like syntax:
Oracle SQL - REGEXP_LIKE contains characters other than a-z or A-Z
https://docs.oracle.com/cd/B28359_01/server.111/b28286/conditions007.htm#SQLRF00501
Try it this will work definitely:
Let's take a sample example:
For example, create a sample table
CREATE TABLE bob (
empno VARCHAR2(10) NOT NULL,
ename VARCHAR2(15) NOT NULL,
ssn VARCHAR2(10) NOT NULL);
Insert data into that table
Insert into bob ( empno,ename,ssn) values ('abC0123458','zXe5378023','0pl4783202');
Insert into bob ( empno,ename,ssn) values ('0123abcdef','a12dldfddd','Rns0101010');
Query to select the all the columns
select * from bob
where length(trim(translate(substr(empno,1,3),'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',' '))) IS NULL
AND
length(trim(translate(substr(empno,4,4),'0123456789',' '))) IS NULL;
To do the same thing on multiple columns use union operator
select * from bob
where length(trim(translate(substr(empno,1,3),'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',' '))) IS NULL
AND
length(trim(translate(substr(empno,4,4),'0123456789',' '))) IS NULL;
union
select * from bob
where length(trim(translate(substr(ename,1,3),'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',' '))) IS NULL
AND
length(trim(translate(substr(ename,4,4),'0123456789',' '))) IS NULL;

oracle sql break out records

I have a table that has a StartDate and EndDate field, and also a ton of other fields. I need to break out each record by all the days between and including StartDate & EndDate into another table that looks exactly like the original except it has a CurrentDate field and 2 calculated fields. The CurrentDate field is the current date between StartDate and EndDate that I'm interating on.
My question is, since there are a ton of fields in this, is there any easy way from within my stored proc, to insert the entire row the cursor is currently on AND this 1 new column, without having to list out every single row in the insert statement? It's so tedious.
If your source and destination tables fit this profile:
Destination table columns are the same as your source table's columns, and
The new destination column is at the end
... then you could do something like this:
INSERT INTO dest_table
SELECT Source_Table.*, new_value
FROM Source_Table
WHERE Source_Table.PKValue = cursor.PKValue
If it's a case of your cursor resembling the destination table, something like this may work but note I haven't tested it:
CREATE PROCEDURE whatever IS
destRow dest_table%ROWTYPE;
CURSOR fromSourceTable IS
SELECT <your existing select list>, NULL AS new_value
FROM <the rest of your cursor query>;
BEGIN
FOR destRow IN fromSourceTable LOOP
destRow.new_value = <the split date>;
INSERT INTO dest_table VALUES destRow;
END LOOP;
END whatever;
I'm going out on a limb with the NULL AS new_value. If you have trouble try CAST(NULL AS DATE) AS new_value instead, and if you still have trouble try something like SYSDATE AS new_value. Again, this isn't tested but if you think it's promising and have trouble implementing I'd be happy to test it.
It's easy enough to densify the data in a single SQL statement. Assuming that you know a reasonable minimum and maximum range for your begin_date and end_date (I'll assume Jan 1, 2000 - Dec 31, 2020 for the moment but you can obviously adjust that)
WITH all_days AS (
SELECT date '2000-01-01' + level dt
FROM dual
CONNECT BY level <= date '2020-12-31' - date '2000-01-01'
)
SELECT <<list of colums from your table>>,
all_days.dt current_date
FROM your_table actual
JOIN all_days ON (actual.begin_date <= all_days.dt AND
actual.end_date >= all_days.dt)
If you don't want to hard-code the starting and ending dates, you can fetch them from your table as well. That just requires that you hit the table a second time which will generally be less efficient.

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','**'));

Can variables be passed to a SQL*Loader control file?

Suppose you have a table:
CREATE TABLE Customer
(
batch_id NUMBER,
customer_name VARCHAR2(20),
customer_address VARCHAR2(100)
)
And suppose you have a control file to populate this table:
LOAD DATA INFILE 'customers.dat'
REPLACE
INTO TABLE Customer
(
batch_id ??????,
customer_name POSITION(001:020),
customer_address POSITION(021:120)
)
Is it possible to pass a value for batch_id to my control file when I run SQL*Loader? For example, is it possible to specify a bind variable (turning the question marks into :MY_AWESOME_BATCH_ID)?
A relatively easy way to archive that is to create a stored function that returns the batch number and use it in the loader file.
create or replace function getBatchNumber return number as
begin
return 815;
end;
/
LOAD DATA INFILE 'customers.dat'
REPLACE
INTO TABLE Customer
(
batch_id "getBatchNumber",
customer_name POSITION(001:020),
customer_address POSITION(021:120)
)
Not easily, if I remember right, but here are a couple of alternatives:
If there's only going to be one process running SQLLoader at a time, use nulls or a fixed value and then run a SQLPlus script as part of the process afterwards to do the update to a sequence value.
Call a script which will grab the next sequence value for your batch ID and then spool out the control file, including the batch_id constant.
If it's acceptable to have BATCH_ID values generated automatically by incrementing on each load, than this worked for me. The 10 minutes interval in the sample would need to be adjusted to the specific load - to be accurate, the loading must complete within the specified interval and the next loading must not be started in less than time specified.
A drawback is that it slows down noticeably on large volumes - that's the price running the MAX aggregate on every line.
LOAD DATA
...
INTO TABLE XYZ
(
...
BATCH_ID expression "(select nvl(max(batch_id) + 1, 1) from extra_instruments_party_to where create_date < (sysdate - interval '10' minute))",
CREATE_DATE SYSDATE
)

Resources