2

The conventional handling of null in SQL, and the language specification, is that if any part of an expression is null, the whole expression is null.

However in Oracle, text concatenation converts null to a <blank>, eg:

select concat(concat('foo', null), 'bar') from dual;  --> returns "foobar"
select 'foo' || null || 'bar' from dual;              --> returns "foobar"

I want the conventional behaviour, where the result would be null if any term is null.

Is there a method or function provided by Oracle that concatenates text using a single expression, without recoding any term, such that if any term is null, the result is null?


Notes:

  • I don't want to repeat any terms, which would be required by a case etc, because the terms are very long and complex, and besides it's bad practice to repeat code
  • I can’t define any functions. I must use just a single SQL query using nothing but standard syntax and plain Oracle provided functions/operators
  • Side-stepping the no-repeat requirement by using a subquery or a CTE isn’t answering the question, it’s avoiding it: I want to know if Oracle can concatenate Strings using a single expression in the same way every other database I know does
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • I don't think there is any direct function available to do so. We need to apply logic only and there are many ways to do. I approach I can suggest is using length function. select decode(length( 'foo') *length('NULL')*length('bar'),NULL,NULL,('foo' || null || 'bar')) from dual; – Bhanu Yadav Jul 30 '18 at 06:39
  • @BhanuYadav your suggestion repeats `'foo'`. Please understand that `'foo'` is just a placeholder for a complex, long expression that 200 characters long – Bohemian Jul 30 '18 at 06:56
  • Your bullet points seem slightly contradictory; using a CTE or subquery is standard syntax and plain functions/operators, and doesn't repeat terms. I understand where you're coming from, but there isn't a simple method or function to achieve this though, so anything that achieves this is going to be a workaround as its avoiding the restriction of Oracle's behaviour. (I'm tempted to post an XML approach but I'm not sure that will go down well *8-) – Alex Poole Jul 31 '18 at 15:13
  • @alex I want a **single expression** that works as per normal null handling. – Bohemian Jul 31 '18 at 20:17
  • @alex I would still like to see your “XML” approach. If it doesn’t repeat any expressions being concatenated and is itself a single expression, your answer will probably be accepted! – Bohemian Jul 31 '18 at 20:23
  • You are right to put "XML" in quotes, I should have said XML DB or XQuery; no real XML component. Added just for interest really *8-) – Alex Poole Jul 31 '18 at 21:18

3 Answers3

0

I'm not avere of a SQL function and this NULL behavior on VARCHAR2 is posible conventional, but for sure not usual expected. The reason is that Oracle doesn't distinct betwen NULL and a string with length zero (''). For string concatenation the NULLs are considered as empty strings.

Anyway you may use subqueries to avoid repeating the expressions:

with t1 as (
select 'foo' col1, null col2, 'bar' col3 from dual union all
select null col1, null col2, null col3 from dual union all
select 'foo' col1, 'baz' col2, 'bar' col3 from dual
) 
select  col1,col2,col3,
case when col1 is not NULL and col2 is not NULL and col3 is not NULL then
  col1||col2||col3 end as concat
from t1;

returns

COL COL COL CONCAT   
--- --- --- ---------
foo     bar          

foo baz bar foobazbar

Alternatively you may write the predicate in teh CASE statement a bit more compact using the Group Comparison Conditions

select  
case when 0 < ALL(length(col1),length(col2),length(col3)) then
  col1||col2||col3 end as concat
from t1;

Unfortunately the Group Comparison Conditions doesn't allow a dierct IS NULL test, so a workaround with length must be used.

The third option is a bit ugly (as requires some special string that doesn't exists in regular strings, but probably meets best your requriements.

Simple NVL all strings before concatenation and than exclude those mappend by NVL

with t2 as 
(select  nvl(col1,'#§$%')||nvl(col2,'#§$%')||nvl(col3,'#§$%') as concat
from t1)
select  
case when concat not like '%#§$\%%' escape'\' then concat end as concat
from t2;
Marmite Bomber
  • 19,886
  • 4
  • 26
  • 53
  • This doesn't answer the question, because (as per my question) I want `null` is **any** term is `null`. Your solution only works if *all* terms are `null`. – Bohemian Jul 30 '18 at 06:54
  • Sorry for the mistake @Bohemian, I updated the answer. – Marmite Bomber Jul 30 '18 at 08:02
  • Given that I want the concatenation, and I don’t want to repeat the expression, how exactly does your code help? – Bohemian Jul 30 '18 at 13:07
  • @Bohemian well, you use a *subquery* to separate the *evaluation of the expression* from the *concatenation*. The expression are evaluated only once in a subquery (`T1` in my exaple). For the concatenation only the column names are (repeatedly) used. (`col1,col2,col3`in my example). – Marmite Bomber Jul 30 '18 at 13:22
  • Thanks for the effort, but I don’t want a work around. I want Oracle to concatenate text in the way expected by the language specification and the same way as every other database (AFAIK Oracle is the only database that turns nulls into blanks). I clarified the question. – Bohemian Jul 30 '18 at 13:25
  • You’re welcome @Bohemian, and of course you may open a SR with Oracle;) – Marmite Bomber Jul 30 '18 at 14:36
0

Yes, concat and || do not work in a standard (as in SQL92 spec) way. I guess, this is because there is no distinction between an empty string and null value in Oracle DB.

You can create a user defined function and use it in SQL.

CREATE OR REPLACE FUNCTION standard_concat (
    a VARCHAR2,
    b VARCHAR2
) RETURN VARCHAR2
    AS
BEGIN
    IF
        a IS NULL OR b IS NULL
    THEN
        RETURN NULL;
    ELSE
        RETURN a || b;
    END IF;
END;
/

Using this function gives you these results:

select standard_concat(standard_concat('foo', ''), 'bar') from dual; returns null

select standard_concat(standard_concat('foo', null), 'bar') from dual; returns null

select standard_concat(standard_concat('foo', 'foo'), 'bar') from dual; returns "foofoobar"

As you can see, empty string will be treated as null since this is the way Oracle DB treats Strings. There is no way to make a distinction here.

If this function is only needed for one query you can inline your function definition into the SQL itself as in:

WITH
    FUNCTION standard_concat (
        a VARCHAR2,
        b VARCHAR2
    ) RETURN VARCHAR2
        AS
    BEGIN
        IF
            a IS NULL OR b IS NULL
        THEN
            RETURN NULL;
        ELSE
            RETURN a || b;
        END IF;
    END;
SELECT
    standard_concat(standard_concat('foo',''),'bar')
FROM
    dual;

I hope it helps.

Julius Zaldokas
  • 234
  • 2
  • 7
  • Of course one can write a stored proc to behave this way, but I wanted to now if there’s a way provided in oracle. I will update the question to clarify. – Bohemian Jul 30 '18 at 13:10
  • Oracle DB can not have such behaviour in it's standard functions because it doesn't make sense in Oracle. Historically, there was a decision that empty string and null are the same thing and I think most people wouldn't be happy if concatenating an empty string with some non-empty string would result in an undefined (`NULL`) value. Also, see this answer (https://stackoverflow.com/a/1268219/6090993), it gives some more insight from the historical perspective. – Julius Zaldokas Jul 31 '18 at 09:36
  • 1
    @JuliusZaldokas - most functions in Oracle do result in a null - they treat null and empty string the same but by treating both as null. Concatenation is unusual as it treats both as an empty string instead (against the standard). Also, [the docs](https://docs.oracle.com/en/database/oracle/oracle-database/12.2/sqlrf/Concatenation-Operator.htm) say "this may not continue to be true in future versions", though I'm expecting that to happen around the same time they finally redefine `varchar` as they've been threatening since about version 6... *8-) – Alex Poole Jul 31 '18 at 14:48
0

There ins't a single expression that will do what you want unfortunately - there isn't a stanrd-compliant equivalent of concat() or the concatenation operator.

I'm sure this doesn't meet the criteria either, but as requested (if slightly mis-advertised), an 'XNL' (well, XMLDB anyway) workaround/hack/abomination:

set null "(null)"

select xmlquery(
  'if (min((string-length($x), string-length($y), string-length($z))) = 0) 
    then "" else concat($x, $y, $z)'
  passing 'foo' as "x", null as "y", 'bar' as "z"
  returning content)
from dual;

XMLQUERY('IF(MIN((STRING-LENGTH($X),STRING-LENGTH($Y),STRING-LENGTH($Z)))=0)THEN
--------------------------------------------------------------------------------
(null)

With no null expressions:

select xmlquery(
  'if (min((string-length($x), string-length($y), string-length($z))) = 0) 
    then "" else concat($x, $y, $z)'
  passing 'foo' as "x", 'bar' as "y", 'baz' as "z"
  returning content)
from dual;

XMLQUERY('IF(MIN((STRING-LENGTH($X),STRING-LENGTH($Y),STRING-LENGTH($Z)))=0)THEN
--------------------------------------------------------------------------------
foobarbaz

As well as being a hack, of course, it suffers from the problem of not being general as the number of terms is fixed. I haven't come up with a way to pass any number of terms in.

Alex Poole
  • 183,384
  • 11
  • 179
  • 318