TO_CLOB cutting off report - sql

I have a select statement with multiple concats using ||. The statement worked fine until the data became too big so I used TO_CLOB for the select to get around "String concatenation too long" error. The report is now generated but only partially with the data being cut out after 1 rows and the 3rd column of 2nd row.
Here's a short version of my code:
SET pagesize 0
SET echo off
SET feedback off
SET verify off
PROMPT value1, value2, value3, difference
SELECT
TO_CLOB('field1' || ',' || num1 || ',' || num2 || ',' || diff || CHR(10) ||
'field2' || ',' || num1 || ',' || num2 || ',' || diff || CHR(10) ||
....... (there's about 80 such lines here) .......
)
from table
OUTPUT I'm getting as the report:
value1, value2, value3, difference
field1, num1, num2, diff
field2, num1, num2
and the rest is blank. The rest of about 80 rows are not being generated.
PS: I tested the query on sql developer and it works fine but does this when run in linux.
Please let me know if there's something I'm missing

You need to get Oracle to concatenate the individual values as CLOB data types; wrap each field in TO_CLOB (otherwise, it will try to concatenate then as strings and then after concatenating will try to convert the single string to a CLOB, which would have already failed if they totalled more than 4000 characters):
SELECT TO_CLOB( field1 ) || ',' || TO_CLOB( field2 ) || CHR(10)
|| TO_CLOB( field3 ) || ',' || TO_CLOB( field4 ) AS value
FROM table_name;
Which, for the sample data:
CREATE TABLE table_name ( field1, field2, field3, field4 ) AS
SELECT LPAD( 'A', 4000, 'A' ),
LPAD( 'B', 4000, 'B' ),
LPAD( 'C', 4000, 'C' ),
LPAD( 'D', 4000, 'D' )
FROM DUAL;
Outputs:
| VALUE |
| ------------------------------------------- |
| AAA ... x4000 ... AAA,BBB ... x4000 ... BBB |
| CCC ... x4000 ... CCC,DDD ... x4000 ... DDD |
It may be sufficient just to convert the first value in the list to a CLOB:
SELECT TO_CLOB( field1 ) || ',' || field2 || CHR(10)
|| field3 || ',' || field4 AS value
FROM table_name;
db<>fiddle here

If your intention is to generate a CSV file from the command line, I would highly recommend trying out SQLcl. It works just like SQLPlus, but has much more functionality similar to what you would find in SQL Developer but through a command line interface. It is Java based so you will need at least Java 8.
By setting the sqlformat option, you can quickly generate a CSV file with ease. Since it is 99% compatible with SQLPlus commands, you can still spool and set whatever other format options you need. You can see from the example that it will even properly escape " as "" if there is a " in the columns being selected to generate a properly structured CSV file.
Edit: Andrew Sayer pointed out in the comments that this functionality is also available in the normal SQLPlus client since client version 12.2 using the set markup csv on command.
Here is a quick demonstration:
SQL> SELECT LEVEL, LEVEL + 1 AS next_level, 'Level: "' || LEVEL || '"' AS level_text
2 FROM DUAL
3 CONNECT BY LEVEL <= 10;
LEVEL NEXT_LEVEL LEVEL_TEXT
________ _____________ ______________
1 2 Level: "1"
2 3 Level: "2"
3 4 Level: "3"
4 5 Level: "4"
5 6 Level: "5"
6 7 Level: "6"
7 8 Level: "7"
8 9 Level: "8"
9 10 Level: "9"
10 11 Level: "10"
10 rows selected.
SQL> set sqlformat csv
SQL> SELECT LEVEL, LEVEL + 1 AS next_level, 'Level: "' || LEVEL || '"' AS level_text
2 FROM DUAL
3 CONNECT BY LEVEL <= 10;
"LEVEL","NEXT_LEVEL","LEVEL_TEXT"
1,2,"Level: ""1"""
2,3,"Level: ""2"""
3,4,"Level: ""3"""
4,5,"Level: ""4"""
5,6,"Level: ""5"""
6,7,"Level: ""6"""
7,8,"Level: ""7"""
8,9,"Level: ""8"""
9,10,"Level: ""9"""
10,11,"Level: ""10"""
10 rows selected.

Related

convert string into rows with SQL

I have a string, like:
'one,two,three'
and I want to convert it in rows and use it into IN clause in an SQL:
one
two
three
I tried something like :
SELECT column_value
FROM XMLTable('"one","two","three"');
and it worked fine but in a join condition it fails.
SELECT 1
FROM dual
WHERE 'one' IN (SELECT column_value
FROM XMLTable('"one","two","three"'));
it gaves me the error:
ORA-00932: inconsistent datatypes: expected - got CHAR
00932. 00000 - "inconsistent datatypes: expected %s got %s"
Can anyone help me on this, please?
NOTE: I would like not use PLSQL
What you need is nothing but just casting a CLOB value to a [VAR]CHAR[2] data type such as
SELECT 1
FROM dual
WHERE 'one' IN (SELECT CAST(column_value AS VARCHAR2(20))
FROM XMLTable('"one","two","three"'))
1
---
1
in order to make it comparable with a literal(such as 'one').
Moreover, CAST might be replaceable with XMLCast as well.
You do not need XML to split the string and can use simple (fast) string functions:
WITH data (value) AS (
SELECT 'one,two,three' FROM DUAL
),
bounds (value, spos, epos) AS (
SELECT value, 1, INSTR(value, ',', 1)
FROM data
UNION ALL
SELECT value, epos + 1, INSTR(value, ',', epos + 1)
FROM bounds
WHERE epos > 0
)
SEARCH DEPTH FIRST BY value SET order_id
SELECT CASE epos
WHEN 0
THEN SUBSTR(value, spos)
ELSE SUBSTR(value, spos, epos-spos)
END as item
FROM bounds;
Which outputs:
ITEM
one
two
three
However
In your case you have an XY-problem and you DO NOT NEED to split the string as you can use LIKE to match the search string against a sub-string of your list:
SELECT 1
FROM dual
WHERE ',' || :your_list || ',' LIKE '%,' || :search_value || ',%';
or with hardcoded strings:
SELECT 1
FROM dual
WHERE ',' || 'one,two,three' || ',' LIKE '%,' || 'one' || ',%';
db<>fiddle here
Query that raised an error - if rewritten to this - works:
SQL> select 1
2 from dual
3 where 'one' in (select regexp_substr('one,two,three', '[^,]+', 1, level)
4 from dual
5 connect by level <= regexp_count('one,two,three', ',') + 1
6 );
1
----------
1
SQL>
Subquery (that uses regexp_substr) splits a comma-separated list of values ('one,two,three') into rows.
Alternatively, if you use Oracle Apex (or have it installed in your database), you can simplify it by utilizing apex_string.split:
SQL> select 1
2 from dual
3 where 'one' in (select * from apex_string.split('one,two,three', ','));
1
----------
1
SQL>
You could use REGEXP_COUNT to find your word in the list.
This would be faster than converting
But read this canonical thread Is storing a delimited list in a database column really that bad?
SELECT
CASE WHEN REGEXP_COUNT('one,two,three', '^([^,]+,)*one(,[^,]+)*$', 1, 'i') > 0
THEN 1 ELSE 0
END as cnt
FROM DUAL;
| CNT |
| --: |
| 1 |
db<>fiddle here

Multiline bind parameter

I'm trying the following query from DBeaver (backend is oracle):
SELECT * FROM mytable where mycolumn in (REPLACE( :req_id_list, CHR(13), ','))
when it prompts for the value of req_id_list I want to paste the values from Excel, which will be one value per line. the query is failing with ORA-00907: missing right parenthesis. is there a way to convert the multiline value to a CSV one ?
I don't use DBeaver so this is a SQL*Plus example. Basically, you'll have to "split" that multi-line input value into separate rows (that's what subquery in lines #8 - 10 does):
Declaring a bind variable:
SQL> var req_id_list varchar2(20)
Storing A + carriage return + C into it:
SQL> exec :req_id_list := 'A' || chr(13) || 'C';
PL/SQL procedure successfully completed.
Query itself (lines #1 - 5 represent sample data; I expect A and C to be returned as that's what I passed to req_id_list):
SQL> with mytable (mycolumn) as
2 (select 'A' from dual union all
3 select 'B' from dual union all
4 select 'C' from dual
5 )
6 select *
7 from mytable
8 where mycolumn in (select regexp_substr(replace(:req_id_list, chr(13), '#'), '[^#]+', 1, level)
9 from dual
10 connect by level <= regexp_count(:req_id_list, chr(13)) + 1
11 );
M
-
A
C
SQL>

Concat list of string in a comma separated string on Oracle (hidden parameter)

On an Oracle type database I have a list of strings in a parameter that I can't edit, I don't even have access to see the table structure.
All I know is that if I do somenthing like that:
select concat(?parameter) as results
I got
abcxyz012
So let's assume that this output depends on something like
select concat( "abc", "xyz", "012" )
Is there any way that I can get
abc,xyz,012
Or similar?
EDIT: Please note that the point here is that the parameter - the list of strings - is a variable whose value and length vary by user and is not known a priori.
The purpose of this query is precisely to identify all the strings that make up the parameter
If You are receiving the abcxyz012 and you want to convert it to abc,xyz,012 then You can use substr as follows:
select substr(results,1,3) || ',' || substr(results,4,6) || ',' || substr(results,7,9)
from (your_query)
In Oracle, CONCAT only takes 2 arguments so you would need to use:
SELECT CONCAT(
'abc',
CONCAT(
',',
CONCAT(
'xyz',
CONCAT(
',',
'012'
)
)
)
) AS value
FROM DUAL;
or, use the string concatenation operator ||:
SELECT 'abc' || ',' || 'xyz' || ',' || '012' AS value FROM DUAL;
(Note: In Oracle, double quotes " signify case-sensitive identifiers such as table/column names. If you want a string literal then you need to use single quotes.)
Both of which output:
| VALUE |
| :---------- |
| abc,xyz,012 |
db<>fiddle here
If you want to split the string into 3-character substrings then:
SELECT SUBSTR(value,1,3) AS value1,
SUBSTR(value,4,3) AS value2,
SUBSTR(value,7,3) AS value3
FROM (
SELECT 'abc' || 'xyz' || '012' AS value FROM DUAL
);
Outputs 3 columns:
VALUE1 | VALUE2 | VALUE3
:----- | :----- | :-----
abc | xyz | 012
Else, if you want one delimited value then, if the string is fixed length:
SELECT SUBSTR(value,1,3) || ',' || SUBSTR(value,4,3) || ',' || SUBSTR(value,7,3) AS delimited_value
FROM (
SELECT 'abcxyz012' AS value FROM DUAL
);
Or, if the string is variable length and does not end with a comma:
SELECT RTRIM(REGEXP_REPLACE(value,'(...)', '\1,'),',') AS delimited_value
FROM (
SELECT 'abcxyz012' AS value FROM DUAL
)
Which both output:
| DELIMITED_VALUE |
| :-------------- |
| abc,xyz,012 |
db<>fiddle here
Maybe I wasn't very clear in expressing my request, the answer to my question was simply:
select concat_ws(',', ?parameter)
Thanks for your solutions anyway!

Oracle SQL inserting parenthesis into phone number

The original format:
123-456-7890
My goal format:
(123)456-7890
I wanted to go the route of concatenating between substrings but I continually get flagged for errors. I am unsure of a better method to go about implementing a way to format.
My query:
select || '(' || substr(telephone,0, 3)|| ')' ||
substr(telephone,4, 3)|| ' '||
substr(telephone,7, 4)) as telephone,
from book;
My current error:
"missing expression"
You have an extra dangling parenthesis at the end of your SELECT, and you also have a dangling concatenation operator || in the front. Try this:
SELECT '(' || SUBSTR(telephone, 0, 3) || ')' ||
SUBSTR(telephone, 4, 3) || ' ' || SUBSTR(telephone, 7, 4) AS telephone
FROM book
Update:
You should really use this query, because it turns out you also had a problem with forming your desired output as well:
SELECT '(' || SUBSTR(telephone, 1, 3) || ')' || SUBSTR(telephone, 5, 8) AS telephone
FROM book
You can use regular expressions to do.
select regexp_replace
(phoneNo,
'([[:digit:]]{3})\-([[:digit:]]{3})\-([[:digit:]]{4})',
'(\1)\2-\3'
)
from(
select '123-456-7890' as phoneNo from dual)
Output
(123)456-7890
SELECT '123-456-7890','('||SUBSTR('123-456-7890',1,3)||')'||SUBSTR('123-456-7890',5,8) FROM dual;
Using SUBSTR:
SQL> WITH sample_data AS(
2 SELECT '123-456-7890' num FROM dual
3 )
4 -- end of sample_data mimicking real table
5 SELECT num,
6 '('
7 || SUBSTR(num, 1, 3)
8 ||
9 ')'
10 || SUBSTR(num, 5, 8) AS my_num
11 FROM sample_data;
NUM MY_NUM
------------ ---------------
123-456-7890 (123)456-7890
SQL>
Remember, the index for SUBSTR starts from 1. It is bad practice to use 0 as starting index.
You could also do it using REGEXP_REPLACE.
Pattern: (\d{3})(-)(\d{3})(-)(\d{4})
Expression: regexp_replace(num, '(\d{3})(-)(\d{3})(-)(\d{4})', '(\1)\3\4\5')
For example,
SQL> WITH sample_data AS(
2 SELECT '123-456-7890' num FROM dual
3 )
4 -- end of sample_data mimicking real table
5 SELECT num,
6 regexp_replace(num, '(\d{3})(-)(\d{3})(-)(\d{4})', '(\1)\3\4\5') my_num
7 FROM sample_data;
NUM MY_NUM
------------ ---------------
123-456-7890 (123)456-7890
SQL>

How to recognize if a variable contains a newline character in Oracle

I have a procedure which takes 2 variables as input, one among them may have string characters separated by enter or space.
Space however is easy to figure out, but how to we figure out if the variable has a newline character in it.
I have tried using chr(10) but no use, because I have to recognize if the strings are separated by a newline character. By the way even chr(10) doesn't work to insert string divided with space.
select 'ABC' || chr(10) || 'DEF' as c from dual
I currently have no such variable as input so can't even experiment to narrow down to some solutions, Also the above query isn't running fine, I mean it's not giving output like below.
ABC
DEF
I have also searched for different oracle documentation, but didn't find any.
Help would be appreciated.
A new line depends on the Operating system. In Unix based OS, it is CHR(10), in Windows it is CHR(13) followed by CHR(10).
You could use either of the following:
LIKE '%'||chr(10)||'%'
INSTR(column_name, chr(10)) > 0
Let's look at test cases in Windows OS:
SQL
Using LIKE
SQL> WITH DATA AS(
2 SELECT 'ABC' || chr(10) || 'DEF' AS c FROM dual UNION ALL
3 SELECT 'PQR' || ' ' || 'XYZ' AS c FROM dual UNION ALL
4 SELECT 'QWE' || CHR(13) || 'RTY' AS c FROM dual UNION ALL
5 SELECT 'no_space' AS c FROM dual
6 )
7 SELECT * FROM DATA WHERE c LIKE '%'||chr(10)||'%';
C
--------
ABC
DEF
SQL>
Using INSTR
SQL> WITH DATA AS(
2 SELECT 'ABC' || chr(10) || 'DEF' AS c FROM dual UNION ALL
3 SELECT 'PQR' || ' ' || 'XYZ' AS c FROM dual UNION ALL
4 SELECT 'QWE' || CHR(13) || 'RTY' AS c FROM dual UNION ALL
5 SELECT 'no_space' AS c FROM dual
6 )
7 SELECT * FROM DATA WHERE INSTR(c, chr(10)) > 0;
C
--------
ABC
DEF
SQL>
PL/SQL
Using LIKE
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 c VARCHAR2(100);
3 BEGIN
4 c:='ABC' || chr(10) || 'DEF';
5 IF c LIKE '%'||chr(10)||'%' THEN
6 dbms_output.put_line('found chr(10)');
7 ELSE
8 dbms_output.put_line('not found');
9 END IF;
10 END;
11 /
found chr(10)
PL/SQL procedure successfully completed.
SQL>
Using INSTR
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 c VARCHAR2(100);
3 BEGIN
4 c:='ABC' || chr(10) || 'DEF';
5 IF INSTR(c, chr(10)) > 0 THEN
6 dbms_output.put_line('found chr(10)');
7 ELSE
8 dbms_output.put_line('not found');
9 END IF;
10 END;
11 /
found chr(10)
PL/SQL procedure successfully completed.
SQL>
create table sample (a varchar2(100));
insert into sample (a) values ('HI_next_line
hello_next_line
hello2');
insert into sample (a) values ('singleline1');
insert into sample (a) values ('singleline2');
SQL> select * from sample;
A
-----------------------------------------------------------------------
HI_next_line
hello_next_line
hello2
singleline1
singleline2
SQL> select *
2 from sample
3 where instr(a, chr(10)) > 0;
A
-----------------------------------------------------------------------
HI_next_line
hello_next_line
hello2