Separate columns by commas ignoring nulls - sql

I have the below table:
A
B
C
D
E
A1
null
C1
null
E1
A2
B2
C2
null
null
null
null
C3
null
E3
I would like the below output (separated by commas, if any value is null, then do not add a comma):
F
A1, C1, E1
A2, B2, C2
C3, E3

You can concatenate all columns regardless of whether they are null or not (saving a lot of comparisons to null), but then fix the commas with string functions. Whether this will be faster or slower than checking each value individually for being null will depend on the data (how many columns - I assume 5 is just for illustration - and how frequent null is in the data, for example).
I included more data for testing in the with clause (which, obviously, is not part of the answer; remove it and use your actual table and column names).
with
inputs (a, b, c, d, e) as (
select 'A1', null, 'C1', null, 'E1' from dual union all
select 'A2', 'B2', 'C2', null, null from dual union all
select null, null, 'C3', null, 'E3' from dual union all
select null, null, null, null, null from dual union all
select null, 'B5', null, null, null from dual
)
select a, b, c, d, e,
regexp_replace(
trim (both ',' from a || ',' || b || ',' || c || ',' || d || ',' || e)
, ',+', ', ') as f
from inputs;
A B C D E F
---- ---- ---- ---- ---- ---------------
A1 C1 E1 A1, C1, E1
A2 B2 C2 A2, B2, C2
C3 E3 C3, E3
B5 B5
EDIT
In a comment, the OP expanded the scope of the question. The new requirement is to also remove leading and/or trailing whitespace from the input tokens (including ignoring tokens altogether, if they consist entirely of whitespace).
This can be achieved as follows:
select a, b, c, d, e,
ltrim(
rtrim(
regexp_replace(
a || ',' || b || ',' || c || ',' || d || ',' || e
, '[[:space:]]*,[,[:space:]]*', ', '
)
, ', ' || chr(9)
)
, ', ' || chr(9)
) as f
from inputs;

Basically, you want concat_ws() -- which Oracle does not support. Instead, you can use:
select trim(',' from
(case when A is not null then ',' || A end ||
case when B is not null then ',' || B end ||
case when C is not null then ',' || C end ||
case when D is not null then ',' || D end ||
case when E is not null then ',' || E end
)
)

Related

postgresql - count distinct combination of three columns- order doesn't matter

I'm trying to count distinct combinations of three columns, order of the columns doesn't matter
sample :
a a a
a a b
a b a
b b a
b a b
the result I'm getting :
a a a 1
a a b 1
a b a 1
b b a 1
b a b 1
desired result
aaa 1
aab 2
bba 2
You can use an ordered array
select v[1], v[2], v[3], count(*) n
from tbl t
cross join lateral (
select array_agg(col order by col) v
from (
values (c1),(c2),(c3)
) t(col)
) s
group by v[1], v[2], v[3];
db<>fiddle
Maybe you can use checksums for getting the required result eg if it is really just combinations 'a' and 'b' that you are dealing with, you could convert the letters to integers (by calling the ASCII() function) and add these up so that you get a checksum.
TABLE
create table t (c1, c2, c3 ) as
select 'a', 'a', 'a' union all
select 'a', 'a', 'b' union all
select 'a', 'b', 'a' union all
select 'b', 'b', 'a' union all
select 'b', 'a', 'b' ;
Checksums
select c1, c2, c3, ascii( c1 ) + ascii( c2 ) + ascii( c3 ) as checksum
from t ;
-- output
c1 c2 c3 checksum
a a a 291
a a b 292
a b a 292
b b a 293
b a b 293
If this works for you, then you can use window functions eg
select c1, c2, c3, rc_ as rowcount
from (
select c1, c2, c3
, count(*) over ( partition by ascii( c1 ) + ascii( c2 ) + ascii( c3 ) order by 1 ) rc_
, row_number() over ( partition by ascii( c1 ) + ascii( c2 ) + ascii( c3 ) order by 1 ) rn_
from t
) sq
where rc_ = rn_ ;
-- output
c1 c2 c3 rowcount
a a a 1
a b a 2
b a b 2
See dbfiddle.
If you are dealing with strings that cannot easily converted to integers, you could create a mapping between the strings and integers, and implement the map_ as a view (so that it is easy to use in subsequent queries) eg
MAP
-- {1} find all distinct elements
-- {2} map each element to an integer
create view map_
as
select val_, rank() over ( order by val_ ) weight_
from (
select distinct val_
from (
select distinct c1 val_ from t union all
select distinct c2 from t union all
select distinct c3 from t
) all_elements
) unique_elements ;
Once you have this map, you can use its values for creating checksums (maybe also in a view) ...
Checksums
create view t_checksums_
as
select c1, c2, c3, c1weight + c2weight + c3weight as checksum
from (
select
c1, ( select weight_ from map_ where c1 = map_.val_ ) c1weight
, c2, ( select weight_ from map_ where c2 = map_.val_ ) c2weight
, c3, ( select weight_ from map_ where c3 = map_.val_ ) c3weight
from t
) valandweight ;
... and then, you can use the same query as before, for obtaining the final result - see dbfiddle.

SQL Concatenate based on string inclusion

I have the following SQL table:
COL_A || COL_B ||
=========================
aa || 1 ||
aa || 2 ||
aa.bb || 3 ||
aa.bb.cc || 4 ||
aa.bb.cc || 5 ||
dd || 6 ||
dd.ee || 7 ||
As part of a SELECT query, I'd like to group by the values of Col_A and concatenate the values in Col_B based on the values in Col_A being a subset of one another. Meaning, if a value of Col_A is contained by/is equal to another value of Col_A, the corresponding Col_B of the superset/same Col_A value should be concatenated together.
Desired result:
COL_A || COL_B ||
======================================
aa || [1, 2, 3, 4, 5] ||
aa.bb || [3, 4, 5] ||
aa.bb.cc || [4, 5] ||
dd || [6, 7] ||
dd.ee || [7] ||
You can use a self join with array_agg:
select t1.col_a, array_agg(distinct t2.col_b)
from vals t1 join vals t2 on t2.col_a ~ t1.col_a
group by t1.col_a order by t1.col_a
You can do this using a lateral join
select t.cola, Concat('[',x.colB,']') ColB
from t
left join lateral (
select string_agg(colb::character,',') colB
from t t2
where t2.cola ~ t.cola
)x on true
group by t.cola, x.colb;
Working fiddle

Join strings in Oracle like concat_ws in SQL Server

I have a table with multiple string columns I would like to join together with a separator.
c1
c2
c3
c4
a
b
c
d
a
b
a
The result for that should be
'a-b-c-d'
'a-b'
'a'
In SQL Server I just do
select concat_ws('-', c1, c2, c3, c4) from my_table
In Oracle I can do
SELECT COALESCE(c1, '') ||
CASE WHEN c2 IS NULL THEN '' ELSE '-' || c2 END ||
CASE WHEN c3 IS NULL THEN '' ELSE '-' || c3 END ||
CASE WHEN c4 IS NULL THEN '' ELSE '-' || c4 END
FROM my_table
Is there a better solution in Oracle or even one that works for both - SQL Server and Oracle?
A version that works in both Oracle and SQL Server is tricky because the only string concatenation function available is concat() with two arguments. But, you can do:
select trim('-' from
concat(coalesce(c1, ''),
concat(case when c2 is null then '' else concat('-', c2) end,
concat(case when c3 is null then '' else concat('-', c3) end,
case when c4 is null then '' else concat('-', c4) end
)
)
))
Here are the two db<>fiddles for SQL Server and Oracle.
select c1 || nvl2(c2, '-'||c2,c2) || nvl2(c3, '-'||c3,c3) || nvl2(c4, '-'||c4,c4)
from mytable
test it here
One option is to
concatenate all columns with - as a separator, and then
remove double (triple, ...) - signs (with regexp) and
remove leading/trailing - signs (with trim)
Something like this:
SQL> with test (c1, c2, c3, c4) as
2 (select 'a' , 'b' , 'c' , 'd' from dual union all
3 select 'a' , 'b' , null, null from dual union all
4 select 'a' , null, null, null from dual union all
5 select 'a' , null, 'c' , null from dual union all
6 select null, null, 'c' , 'd' from dual
7 )
8 select
9 c1, c2, c3, c4,
10 --
11 trim(both '-' from regexp_replace(c1 ||'-'|| c2 ||'-'|| c3 ||'-'|| c4, '-+', '-')) result
12 from test;
C1 C2 C3 C4 RESULT
-- -- -- -- --------------------
a b c d a-b-c-d
a b a-b
a a
a c a-c
c d c-d
SQL>

Exclude columns with no data in them

Using Oracle with TOAD.
I have a table that looks something like this (columns 2 and 4 are empty and columns 1, 3 and 5 have data in them):
column_1 column_2 column_3 column_4 column_5
a1 b1 c1
a2 b2 c2
a3 b3 c3
a4 b4 c4
I would like to do a simple select that excludes columns with no data in them (= columns 2 and 4) or ain other words, to select only the columns that have data in them.
Is there a select command such as SELECT * FROM test_table WHERE columns ARE NOT NULL (this is pseudo code just for clarification for my problem).
The result should look like this:
column_1 column_3 column_5
a1 b1 c1
a2 b2 c2
a3 b3 c3
a4 b4 c4
You have to do a three-step approach but it is largely tedious but do-able in sqlplus
1)First identify the columns which are empty
2)Define the headers without those columns
3)define the body without those columns
WITH data AS
(
SELECT '1' a,
'' b ,
2 c ,
'' d,
5 e
FROM dual
UNION ALL
SELECT '7' a,
'' b ,
2 c ,
'' d,
6
FROM dual
UNION ALL
SELECT '3' a,
'' b ,
3 c ,
'' d,
7
FROM dual
UNION ALL
SELECT '4' a,
'' b ,
3 c ,
'' d,
8
FROM dual
UNION ALL
SELECT '5' a,
'' b ,
2 c ,
'' d,
9
FROM dual),d1 AS
(
SELECT First_value(a) ignore nulls over (PARTITION BY a ORDER BY ROWNUM) ca,
first_value(b) ignore nulls over (PARTITION BY b ORDER BY ROWNUM) cb,
first_value(c) ignore nulls over (PARTITION BY c ORDER BY ROWNUM) cc,
first_value(d) ignore nulls over (PARTITION BY d ORDER BY ROWNUM) cd,
first_value(e) ignore nulls over (PARTITION BY e ORDER BY ROWNUM) ce
FROM data
WHERE ROWNUM=1 ),
d2 as (SELECT 0 rw,
CASE
WHEN ca IS NOT NULL THEN 'a'
ELSE ''
END
||chr(9)
||
CASE
WHEN cb IS NOT NULL THEN 'b'
ELSE ''
END
||chr(9)
||
CASE
WHEN cc IS NOT NULL THEN 'c'
ELSE ''
END
||chr(9)
||
CASE
WHEN cd IS NOT NULL THEN 'd'
ELSE ''
END
||chr(9)
||
CASE
WHEN ce IS NOT NULL THEN 'e'
ELSE ''
END as DATA1
FROM d1
UNION ALL
SELECT
rownum rw,
a
||chr(9)
||b
||chr(9)
||c
||chr(9)
||d
||chr(9)
||e
FROM data)
select /*ansiconsole*/ DATA1
from d2 order by rw asc;

How to convert vertical string into horizontal in Oracle

O
N
K
A
R
how to convert it into ONKAR. reverse of it I know. But this I am not able to solve.
You can't do what you want generally without also having a second column which provides the ordering for each letter. Assuming you do have a column for the position, we can try:
SELECT LISTAGG(letter, '') WITHIN GROUP (ORDER BY position) word
FROM yourTable;
Demo
Data:
letter | position
O | 1
N | 2
K | 3
A | 4
R | 5
Listagg is right solution for strings up to 4000 bytes cuz it returns varchar2 data type. But for longer strings you may get clob data type.
with s (letter, position) as (
select 'O', 1 from dual union all
select 'N', 2 from dual union all
select 'K', 3 from dual union all
select 'A', 4 from dual union all
select 'R', 5 from dual)
select xmlcast(xmlagg(xmlelement(x, letter) order by position) as clob) c
from s;
C
---------------
ONKAR
You can use this as long as data result has all rows that you want to stick together.
with data as (select 'O' as letter from dual
union all
select 'N' from dual
union all
select 'K' from dual
union all
select 'A' from dual
union all
select 'R' from dual)
SELECT LISTAGG(letter, '') WITHIN GROUP (ORDER BY rownum)
FROM data;
If your data is in a single row separated by newline (ASCII 13) characters then you can just use REPLACE( value, CHR(13) ):
Oracle Setup:
CREATE TABLE test_data ( value ) AS
SELECT 'O' || CHR(13) || 'N' || CHR(13) || 'K' || CHR(13) || 'A' || CHR(13) || 'R' FROM DUAL
Query:
SELECT value, REPLACE( value, CHR(13) ) FROM test_data
Output:
VALUE | REPLACE(VALUE,CHR(13))
:-------- | :---------------------
O | ONKAR
N |
K |
A |
R |
db<>fiddle here