Using CASE on empty string - sql

I have a code that goes like this:
SELECT
'"35933-14",' ||
'"' || us_1.gr_UniqueName || '",' ||
'"' || (CASE WHEN us_1.mls0_PrimaryString = '' THEN 'This is empty'
WHEN CAST(Length(us_1.mls0_PrimaryString) AS INT) < 4 THEN ('Less than 4: '|| SUBSTR(us_1.mls0_PrimaryString,1,10000))
ELSE SUBSTR(us_1.mls0_PrimaryString,1,10000) END) || '",' ||
'"",' ||
'"",' ||
'""'
FROM
us_GroupTab us_1
WHERE (us_1.gr_Active = 1)
AND (us_1.gr_PurgeState = 0)
AND (us_1.gr_PartitionNumber = 0)
AND (us_1.gr_UniqueName IN ('US_HARDWARE_1', 'US_HARDWARE_2','GROUP_NULL'));
Basically the problem is that not all empty string is handled, some users are only inputting multiple spaces which the first case statement does not handle. Is there any way to do this, I have tried using TRIM function but it does not work.
Thanks!

An empty string is the same as null in Oracle, and you can't compare anything to null. You need to use is null instead of = null or = ''.
CASE WHEN TRIM(us_1.mls0_PrimaryString) IS null THEN 'This is empty' ...
You also don't need to cast the length check to int. And the maximum length of a varchar2 before 12c is 4000 chars, so there's no point using 10000 in your substr. In fact the first substr isn't going to do anything anyway as you already know the length is less than 4.
If you want to remove new lines and carriage returns before checking - and that is perhaps something you should be doing client-side, unless you want to store those too - then you can either replace them first:
CASE WHEN TRIM(REPLACE(REPLACE(us_1.mls0_PrimaryString, CHR(10)), CHR(13))) IS null
THEN ...
Or more generically remove all whitespace which would catch tabs etc. too:
CASE WHEN REGEXP_REPLACE(us_1.mls0_PrimaryString, '[[:space:]]') IS NULL THEN ...
Or:
CASE WHEN REGEXP_LIKE(us_1.mls0_PrimaryString, '^[[:space:]]*$') THEN ...
Note that don't need a separate trim with regexp_replace.

Best solution would be to validate and filter that kind of input before it even enters the database.
But as that is not the case, a solution that could work:
regexp_matches()

Related

PL/SQL Query to check row's length

I'm having some trouble on solving this.
I have to check on the table, if there's any row that exceed the length of 34 characters (for this, I'm using this first part of the query using Lenght command), if found, return the error with the variable 'END_CNPJ$' (that's being populated by the second part of the query 'ENDERECO') so the user can see which row has more than 34 characters. Is this code correct (probably not)? If it isn't, how can I fix it?
SELECT
LENGTH(CONCAT (CONCAT (CONCAT(CONCAT (CONCAT (CONCAT (CONCAT (
'', T.TTIPO_LOGR),
''), T.TENDERE),
''), T.NNRO_ENDER),
''),T.TCOMPL_ENDER) ),
T.TTIPO_LOGR || ' ' || T.TENDERE || ', ' || T.NNRO_ENDER || ' ' || T.TCOMPL_ENDER || ' - ' || TMUNICI || ' CNPJ: ' || T.NCGC AS ENDERECO
INTO CHARACTER_COUNT$, END_CNPJ$
FROM TBENDER T
WHERE T.CEMPRES = :ENDER_BLK.CEMPRES;
IF CHARACTER_COUNT$ > 34 THEN
MSG_ALERT_COSMO(' You exceeded 34 character for this address: ' || END_CNPJ$ );
RAISE FORM_TRIGGER_FAILURE;
END IF;
I hope I'm not violating any rule, just got here yesterday :D
TIA
That's actually Oracle Forms, is it not? raise form_trigger_failure and :ender_blk smell so.
In that case, the only drawback might be possibility of no rows in that table for block item value (which will raise no_data_found) or two or more rows for it (which will then raise too_many_rows).
Other than that, this should be OK.
Though, it is kind of unclear why you nicely concatenated values (using the double pipe || operator) for END_CNPJ$ and nested that many concat functions for CHARACTER_COUNT$.
Also, you didn't post the whole trigger code (missing declarations, begin-end keywords, perhaps something else).
But, as I said, in general - that's OK.

Removing specified character from string in SQL

Say I have a string looking like this ",LI,PA,LK";
I want to remove the first char, so it looks like "LI,PA,LK";
In Java my code to handle this, will look like this:
public String returnSubs(String val) {
int index = val.indexOf(",");
String res = val.substring(index+1, val.length());
return res;
}
I want to achieve the exact same thing in SQL, having this query
select patientID, case when liver is not null then 'LI' else '' end
|| case when kidney_r is not null then ',KR' else '' end
|| case when kidney_l is not null then ',KL' else ''end
|| case when heart is not null then ',HE' else '' end
|| case when liver_domino is not null then ',LI-Dom' else '' end
|| case when lung_r is not null then ',LungR' else '' end
|| case when pancreas is not null then ',PA' else '' end
|| case when liver_split is not null then ',Lsplit' else '' end
|| case when lung_l is not null then ',LungL' else '' end
|| case when intestine is not null then ',Intestine' else '' end
into organType
from offers
where patientID > 1
;
Also, the string I get from the query above, could look like LI, PA, KL, (notice the comma is at the end, and not the begining)
I see that I can use the SUBSTRING and/or INSTR of SQL. But I'm not really sure how. I am creating a procedure where this will be handled
Thanks for any help
Oracle has a function trim() that does exactly what you want:
trim(leading ',' from col)
You can use this in either an update or select.
Note: You appear to be storing multiple values in a comma-delimited list. That is a very bad way to model data. You do not want to overload what strings are by storing multiple values. Oracle has many better alternatives -- association tables, nested tables, JSON, and XML come to mind.
You could also use LTRIM here:
SELECT
LTRIM(organTypes, ',') AS col_out
FROM offers;
Some databases, such as MySQL, offer functions like CONCAT_WS which concatenate with a separator while ensuring that no dangling separators are added to the resulting output. Oracle does not have this, but LTRIM should be sufficient here.
even this will work:
substr(',LI,PA,LK',2)
In SQL SERVER:
SUBSTRING(VAL,2,LEN(VAL))
VAL--> COLUMN NAME
2--> IT SKIPS 1ST VALUE
LEN-->LENGTH OF THE COLUMN

check if integer value inside a string list with Firebird

I am writing a stored procedure and I need to check if an integer variable value is inside a list.
I tried like this
If (var_int1 in (4,6,9) ) then ....
This work but the list of numbers will return to me from a varchar field so is there a way to still use IN and without using a temporary table.
You could use the builtin function position:
if(position(val IN valList) > 0)then
Note that you need to avoid false positives where only part of the number matches, ie given the values '123,456,789' position for '2' would return true which you probably don't want. To avoid that you could add comma (as you have comma separated values in the varchar field) as prefix and suffix to your search strings, ie
val = ',' || cast(var_int1 as varchar(10)) || ',';
valList = ',' || valList || ',';
if(position(val IN valList) > 0)then ...
Just to provide another possible solution:
valList = '2,3,4'; --- varchar
val = 3; --- integer
if ( valList like '%'||val||'%' ) then ...

How to join tables on regex

Say I have two tables msg for messages and mnc for mobile network codes.
They share no relations. But I want to join them
SELECT msg.message,
msg.src_addr,
msg.dst_addr,
mnc.name,
FROM "msg"
JOIN "mnc"
ON array_to_string(regexp_matches(msg.src_addr || '+' || msg.dst_addr, '38(...)'), '') = mnc.code
But query fails with error:
psql:marketing.sql:28: ERROR: argument of JOIN/ON must not return a set
LINE 12: ON array_to_string(regexp_matches(msg.src_addr || '+' || msg...
Is there a way to do such join? Or am I moving wrong way?
A very odd way to join. Every match on one side is combined with every row from the other table ...
regexp_matches() is probably the wrong function for your purpose. You want a simple regular expression match (~). Actually, the LIKE operator will be faster:
Presumably fastest with LIKE
SELECT msg.message
, msg.src_addr
, msg.dst_addr
, mnc.name
FROM mnc
JOIN msg ON msg.src_addr LIKE ('%38' || mnc.code || '%')
OR msg.dst_addr LIKE ('%38' || mnc.code || '%')
WHERE length(mnc.code) = 3;
In addition, you only want mnc.code of exactly 3 characters.
With regexp match
You could write the same with regular expressions but it will most definitely be slower. Here is a working example close to your original:
SELECT msg.message
, msg.src_addr
, msg.dst_addr
, mnc.name
FROM mnc
JOIN msg ON (msg.src_addr || '+' || msg.dst_addr) ~ (38 || mnc.code)
AND length(mnc.code) = 3;
This also requires msg.src_addr and msg.dst_addr to be NOT NULL.
The second query demonstrates how the additional check length(mnc.code) = 3 can go into the JOIN condition or a WHERE clause. Same effect here.
With regexp_matches()
You could make this work with regexp_matches():
SELECT msg.message
, msg.src_addr
, msg.dst_addr
, mnc.name
FROM mnc
JOIN msg ON EXISTS (
SELECT *
FROM regexp_matches(msg.src_addr ||'+'|| msg.dst_addr, '38(...)', 'g') x(y)
WHERE y[1] = mnc.code
);
But it will be slow in comparison.
Explanation:
Your regexp_matches() expression just returns an array of all captured substrings of the first match. As you only capture one substring (one pair of brackets in your pattern), you will exclusively get arrays with one element.
You get all matches with the additional "globally" switch 'g' - but in multiple rows. So you need a sub-select to test them all (or aggregate). Put that in an EXISTS - semi-join and you arrive at what you wanted.
Maybe you can report back with a performance test of all three?
Use EXPLAIN ANALYZE for that.
Your immediate problem is that regexp_matches could return one or more rows.
Try using "substring" instead, which extracts a substring given a regex pattern.
SELECT msg.message,
msg.src_addr,
msg.dst_addr,
mnc.name
FROM "msg"
JOIN "mnc"
ON substring(msg.src_addr || '+' || msg.dst_addr from '38(...)') = mnc.code

How can I determine if a string is numeric in SQL?

In a SQL query on Oracle 10g, I need to determine whether a string is numeric or not. How can I do this?
You can use REGEXP_LIKE:
SELECT 1 FROM DUAL
WHERE REGEXP_LIKE('23.9', '^\d+(\.\d+)?$', '')
You ca try this:
SELECT LENGTH(TRIM(TRANSLATE(string1, ' +-.0123456789', ' '))) FROM DUAL
where string1 is what you're evaluating. It will return null if numeric. Look here for further clarification
I don't have access to a 10G instance for testing, but this works in 9i:
CREATE OR REPLACE FUNCTION is_numeric (p_val VARCHAR2)
RETURN NUMBER
IS
v_val NUMBER;
BEGIN
BEGIN
IF p_val IS NULL OR TRIM (p_val) = ''
THEN
RETURN 0;
END IF;
SELECT TO_NUMBER (p_val)
INTO v_val
FROM DUAL;
RETURN 1;
EXCEPTION
WHEN OTHERS
THEN
RETURN 0;
END;
END;
SELECT is_numeric ('333.5') is_numeric
FROM DUAL;
I have assumed you want nulls/empties treated as FALSE.
As pointed out by Tom Kyte in http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:7466996200346537833, if you're using the built-in TO_NUMBER in a user defined function, you may need a bit of extra trickery to make it work.
FUNCTION is_number(x IN VARCHAR2)
RETURN NUMBER
IS
PROCEDURE check_number (y IN NUMBER)
IS
BEGIN
NULL;
END;
BEGIN
PRAGMA INLINE(check_number, 'No');
check_number(TO_NUMBER(x);
RETURN 1;
EXCEPTION
WHEN INVALID_NUMBER
THEN RETURN 0;
END is_number;
The problem is that the optimizing compiler may recognize that the result of the TO_NUMBER is not used anywhere and optimize it away.
Says Tom (his example was about dates rather then numbers):
the disabling of function inlining will make it do the call to
check_date HAS to be made as a function call - making it so that the
DATE has to be pushed onto the call stack. There is no chance for the
optimizing compiler to remove the call to to_date in this case. If the
call to to_date needed for the call to check_date fails for any
reason, we know that the string input was not convertible by that date
format.
Here is a method to determine numeric that can be part of a simple query, without creating a function. Accounts for embedded spaces, +- not the first character, or a second decimal point.
var v_test varchar2(20);
EXEC :v_test := ' -24.9 ';
select
(case when trim(:v_test) is null then 'N' ELSE -- only banks, or null
(case when instr(trim(:v_test),'+',2,1) > 0 then 'N' ELSE -- + sign not first char
(case when instr(trim(:v_test),'-',2,1) > 0 then 'N' ELSE -- - sign not first char
(case when instr(trim(:v_test),' ',1,1) > 0 then 'N' ELSE -- internal spaces
(case when instr(trim(:v_test),'.',1,2) > 0 then 'N' ELSE -- second decimal point
(case when LENGTH(TRIM(TRANSLATE(:v_test, ' +-.0123456789',' '))) is not null then 'N' ELSE -- only valid numeric charcters.
'Y'
END)END)END)END)END)END) as is_numeric
from dual;
I found that the solution
LENGTH(TRIM(TRANSLATE(string1, ' +-.0123456789', ' '))) is null
allows embedded blanks ... it accepts "123 45 6789" which for my purpose is not a number.
Another level of trim/translate corrects this. The following will detect a string field containing consecutive digits with leading or trailing blanks such that to_number(trim(string1)) will not fail
LENGTH(TRIM(TRANSLATE(translate(trim(string1),' ','X'), '0123456789', ' '))) is null
For integers you can use the below. The first translate changes spaces to be a character and the second changes numbers to be spaces. The Trim will then return null if only numbers exist.
TRIM(TRANSLATE(TRANSLATE(TRIM('1 2 3d 4'), ' ','#'),'0123456789',' ')) is null