Unexpected result in RPAD - sql

I am using Oracle 11g. I am using the Scott account and the demo EMP table. I inserted one record with ENAME BRUCE WILLIAM. My aim is to show the first name and last name in two columns. I used this code:
select trim rpad(ename, instr(ename,' '))) "First Name",
trim(substr(ename, instr(ename,' '))) "Last Name"
from emp;
This gives a weird result. The First Name is extended to second line. I used
select trim(substr(ename, 1, instr(ename,' '))),
trim(substr(ename, instr(ename, ' ')))
from emp;
I got the expected output. My question is why the first line of query is giving extra spaces?

You are not getting extra spaces in your string, and if you were then the trim() would remove them again. SQL*Plus is just formatting the results in a way you don't expect. The documentation mentions the default formatting for column types, and can usually work it out for system functions (though the characterset can make it bigger than you expect).
It seems like SQL*Plus, and SQL Developer, can't determine a sensible default for your rpad case, but can for your substr. Well, SQL*Plus is really just getting a result set cursor from the database, and using the cursor metadata to determine the default widths to apply to the fields for display, so it isn't getting the length you expect from that metadata. But what length should it use?
The database only knows how big the rpad value can be if the padding length is a simple value - it doesn't even mind zero (it returns null, which you're relying on). If the padding length is determined by a function then there's no way to tell how big the result could be, apart from calculating it for every value in the result set before returning the metadata and and actual data, which isn't practical, and would produce inconsistent output as the data changed.
It also wouldn't be practical to try to determine a theoretical maximum, even though it looks superficially straightforward in your case. substr can't ever return something longer than the original value; but rpad could potentially produce something huge even from a short input value, so it has to allow for that possibility if it can't easily determine a limit (i.e. from a fixed value).
So it plays safe and allows for it being up to the maximum length for a varchar2, which is 4000 characters, as this dynamic SQL demonstrates:
declare
l_curid integer;
l_desctab dbms_sql.desc_tab3;
l_colcnt integer;
begin
l_curid := dbms_sql.open_cursor;
dbms_sql.parse(l_curid, 'select rpad(ename, instr(ename,'' '')), '
|| 'rpad(ename, 4), '
|| 'substr(ename, 1, instr(ename,'' '')) '
|| 'from emp where ename like ''B%''' , dbms_sql.native);
dbms_sql.describe_columns3(l_curid, l_colcnt, l_desctab);
for i in 1 .. l_colcnt loop
dbms_output.put_line('column ' || i
|| ' ' || l_desctab(i).col_name
|| ' type ' || l_desctab(i).col_type
|| ' length ' || l_desctab(i).col_max_len
);
end loop;
dbms_sql.close_cursor(l_curid);
end;
/
column 1 RPAD(ENAME,INSTR(ENAME,'')) type 1 length 4000
column 2 RPAD(ENAME,4) type 1 length 16
column 3 SUBSTR(ENAME,1,INSTR(ENAME,'')) type 1 length 40
As you can see, it knows the length for a fixed-length rpad and a substr (note the size is four times the actual string length due to the multibyte characterset), but falls back to the maximum for the rpad using a function.
What you're seeing is SQL*Plus showing a 4000-char column. If you did this in SQL Developer you would see the header for that column is indeed 4000 characters. SQL*Plus helps a bit by reducing the displayed column header to the line size, and wraps the next column onto a separate line.

lpad ('string', n [, 'string_pad')
rpad ('string', n [, 'string_pad')
string is left padded to length n with string_pad. If string_pad is ommited, a space will be used as default
rpad is similar, but pads right instead of left.
from http://www.adp-gmbh.ch/ora/sql/rpad.html
and here is good example for understanding
begin
for i in 1 .. 15 loop
dbms_output.put_line(
rpad('string', i) || '<'
);
end loop;
end;

Related

Replace function doesn't work as expected

I'm having trouble figuring out why REPLACE() doesn't work correctly.
I'm getting a string formatted as:
RISHON_LEZION-CMTSDV4,Cable7/0/4/U1;RISHON_LEZION-CMTSDV4,Cable7/0/4/U2;RISHON_LEZION-CMTSDV4,Cable7/0/5/U0;.....
Up to 4000 characters .
Each spot of ; represent a new string(can be up to about 15 in one string). I'm splitting it by using REPLACE() - each occurence of ; replace with $ + go down a line + concat the entire string again (I have another part that is splitting down the string)
I think the length of the string is some how effecting the result, though I never heard replace has some kind of limitation about the length of the string.
SELECT REPLACE(HOT_ALERTKEY_PK, ';', '$' || CHR(13) || CHR(10) || HOT_ALERTKEY_PK || '$')
from (SELECT 'RISHON_LEZION-CMTSDV4,Cable7/0/3/U0;RISHON_LEZION-CMTSDV4,Cable7/0/3/U1;RISHON_LEZION-CMTSDV4,Cable7/0/3/U2;RISHON_LEZION-CMTSDV4,Cable7/0/4/U0;RISHON_LEZION-CMTSDV4,Cable7/0/4/U1;RISHON_LEZION-CMTSDV4,Cable7/0/4/U2;RISHON_LEZION-CMTSDV4,Cable7/0/5/U0;RISHON_LEZION-CMTSDV4,Cable7/0/5/U1;RISHON_LEZION-CMTSDV4,Cable7/0/5/U2;RISHON_LEZION-CMTSDV4,Cable7/0/7/U0;RISHON_LEZION-CMTSDV4,Cable7/0/7/U1;RISHON_LEZION-CMTSDV4,Cable7/0/7/U2;RISHON_LEZION-CMTSDV4,Cable7/0/9/U0;RISHON_LEZION-CMTSDV4,Cable7/0/9/U1;RISHON_LEZION-CMTSDV4,Cable7/0/9/U2' as hot_alertkey_pk
FROM dual)
This for some reason result in splitting the string correctly, up to cable7/0/5/U0; , and stops. If I remove one or more parts from the start of the string (up to the semicolumn is each part) then I'm getting it up to the next cables, according to how many I remove from the beggining.
Why is this happening ?
Thanks in advance.
If you wrap your sample input string within to_clob() in the inner query, and you wrap the resulting string within length() in the outer query, you will find that the result is 8127 characters. This answers your question, but only partially.
I am not sure why replace doesn't throw an error, or perhaps just truncate the result at 4000 characters. I got exactly the same result as you did in Oracle 11.2, with the result chopped off after 3503 characters. I just looked quickly at the Oracle documentation for replace() and it doesn't say what the behavior should be if the input is VARCHAR2 but the output is more than 4000 characters. It looks as though it performed as many substitutions as it could and then it stopped (the next substitution would have gone above 4000 characters).

Teradata table creation and select statements fail when using LPAD and RPAD

Background: The application I'm working on is not using any character delimiters. Fields are fixed length. Alphanumeric fields have to be left justified and space filled to the right, and numeric fields are right justified and zero-filled to the left.
I've been trying to accomplish this by using the RPAD and LPAD functions. The problem I'm running into is the error Teradata is displaying, "Response Row size or Constant Row size overflow". Each record if 4000 Bytes, and (from what I've read) the maximum size for each record in Teradata is 64KB, so I'm well under the maximum Teradata-allowed length.
Here is a small sample of the code that is generating an error;
SELECT
RPAD(t1.MemberNbr, 20, ' ') AS MemberNbr
,RPAD(t1.LastName, 35, ' ') AS LastName
,RPAD(t1.FirstName, 25, ' ') AS FirstName
,CAST(t1.B_Day AS DATE FORMAT 'YYYYMMDD') (char(8)) AS BirthDay
FROM someTable AS t1
Can anyone explain to me why this isn't working? Thanks
When you check the resulting data type (SELECT TYPE(RPAD(t1.MemberNbr, 20, ' '))) you will notice it's either a VARCHAR(32000) CHARACTER SET UNICODE or VARCHAR(64000) CHARACTER SET LATIN, you need to decrease it using a cast:
CAST(RPAD(t1.MemberNbr, 20, ' ') AS CHAR(20))
I know it's stupid, but RPAD & LPAD are no built-in functions but FastPath UDFs, thus the parser/optimizer doesn't seem to know about the actual result size (Otherwise it's ok for other UDFs, e.g. LTRIM/RTRIM)

ORACLE SQL IN Clause (SQL Query)

I'm having : delimited column like 1:2:3:. I want to get this into 1,2,3. My query looks like,
select name
from status where id IN (SELECT REPLACE(NEXT_LIST,':',',')
FROM status);
but I got an error
ORA-01722: invalid number
(1, 2, 3, 4) is different from ('1, 2, 3, 4'). IN requires the former, a list of values; you give it the latter, a string.
You have two options mainly:
Build the query dynamically, i.e. get the list first, then use this to build a query string.
Tokenize the string. This can be done with a custom pipelined function or a recursive query, maybe also via some XML functions. Google "Oracle tokenize string" to find a method that suits you.
UPDATE Option #3: Use LIKE as in ':1:2:3:4:' like '%:3:%'
(This requires your next_list to contain only simple numbers separated with colons. No leading zeros, no blanks, no other characters.)
select name
from status
where (select ':' || next_list || ':' from status) like '%:' || id || ':%'
i agreed with Thorsten but i wonder if we just replace one more time would it works? i mean like this:
select name
from status where id IN (SELECT replace(REPLACE(NEXT_LIST,':',','),'''','')
FROM status);
The REPLACE function returns a string, so the nested query returns a list of string values (where colons replaced with commas), but not a list of number values. When Oracle engine interprets id IN (str_value) it tries to cast the str_value to number and raises exception ORA-01722: invalid number because there are cases like '1:2:3' which are definetely unparseable.
The "pure sql" approach leads us to using custom function detecting if a number is in a colon-separated list:
-- you need Oracle 12c to use function in the WITH clause
-- on earlier versions just unwrap CASE statement and put it into query
WITH
FUNCTION in_list(p_id NUMBER, p_list VARCHAR2) RETURN NUMBER DETERMINISTIC IS
BEGIN
RETURN CASE WHEN
instr(':' || p_list || ':', ':' || p_id || ':') > 0
THEN 1 ELSE 0 END;
END;
SELECT *
FROM status
WHERE in_list(id, next_list) = 1;
Here I assume that values in the next_list column are strings containing numbers separated with colon without spaces. In common case you shall modify the function to match specific list formats.

Oracle SQL - Is there a better way to concatenate multiple strings with a given delimiter?

First question, so apologies in advance if this is stupid or unoriginal, but I've searched for about 30 mins now without finding any mention anywhere of my exact question:
Is there a way to concatenate a series of strings, to be separated by a given delimiter, without manually putting the delimiter between each column being concatenated?
To give a concrete example, I currently have this:
SELECT member_no as Member#,
(member_gname
|| ' '
|| member_fname) as Name,
(member_street
|| ' '
|| member_city
|| ' '
|| member_state
|| ' '
|| member_postcode) AS Address,
member_phone AS Phone,
TO_CHAR(member_joindate, 'dd-Mon-yyyy') as Joined
FROM MEMBER;
It works fine, and produces exactly the output I wanted, but as this is for study I'm less concerned about the output and more concerned with the readability and 'best practise' factors of the .sql file itself. I understand that CONCAT() only takes two arguments, so that won't work without nesting them (which is even uglier and less readable). I'm coming in totally naively here, but I was hoping there'd be some kind of magical AWESOMECONCAT() type of function that would take all the columns i need, as well as allowing me to specify what character I want separating them (in this case, a space). Any ideas?
Also, this is a separate question not worthy of posting by itself, but is there any way to select a column 'AS' and give it a name including whitespace? E.g 'Member #' would look better imo, and 'Join Date' would be clearer, but I've tried both brackets and single quotes after the AS and neither seems to fly with SQL developer.
We can still write our own AWESOMECONCAT(). Unfortunately, Oracle has no in built function. As the concatenate operator does the basic thing.
Using double quotes in the alias, you can make the column references case sensitive and even accept blanks. But note that, any more references to that column/expression needs double quotes with same text.
SELECT member_no as "Member #",
(member_gname
|| ' '
|| member_fname) as Name,
(member_street
|| ' '
|| member_city
|| ' '
|| member_state
|| ' '
|| member_postcode) AS Address,
member_phone AS Phone,
TO_CHAR(member_joindate, 'dd-Mon-yyyy') as "Join Date"
FROM MEMBER;
Is there a way to concatenate a series of strings, to be separated by a given delimiter, without manually putting the delimiter between each column being concatenated?
The best way to do concatenation from 11g onwards is the new string literal technique q'[]'.
For example :
select q'[This is a string, 'this is also a string'.]' from dual

Oracle RPAD() padding with an empty string

I am trying to create a dump file from within SQL*Plus. The requirement is to create null '' for padding but when I use NULL even the data value is getting nullified see below.
SQL> select RPAD(1234,10,' ') from dual ;
RPAD(1234,
----------
1234
SQL> select RPAD(1234,10,'') from dual;
R
-
I have seen other scripts where they seem to be using null('') for padding
Please help thanks
RPAD accepts a character or string as its 3rd parameter which is used to "pad" the initial string to a particular length.
RPAD can be used to return a string which is "guaranteed" to be n characters long (as per the 2nd parameter).
Since NULL does not represent any particular character or string and has zero length, it cannot be used for padding - RPAD apparently returns NULL in this instance, which makes sense as the only other option would be for RPAD to raise an exception.
This code:
RPAD(1234,10,'')
concatenates 1234 to '', which in Oracle is equivalent to NULL, therefore it results in NULL (anything concatenated to NULL yields NULL)
There is no NULL('') in Oracle.
Hope that helps.