Join strings in Oracle like concat_ws in SQL Server - sql

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>

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.

Separate columns by commas ignoring nulls

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
)
)

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;

Effective way to check whether particular data exist in each row and column or not?

1) I want to pick row which contain data x in below table
2) I want to pick column which contain data x in below table
xtable
ID C1 C2 C3 C4
--- -- -- --- --
1 A - - -
2 - A - -
3 A - A -
4 - A - -
5 - - - -
Solution i tried
SELECT CASE
WHEN exists (SELECT 1 FROM xtable WHERE C1 = 'A') THEN ROWNUM
WHEN exists (SELECT 1 FROM xtable WHERE C2 = 'A') THEN ROWNUM
WHEN exists (SELECT 1 FROM xtable WHERE C3 = 'A') THEN ROWNUM
WHEN exists (SELECT 1 FROM xtable WHERE C4 = 'A') THEN ROWNUM
ELSE 0
END "Exist"
FROM xtable;
But i wont work
Give some effective solution for above problem
Sample data:
SQL> select * From test;
ID C1 C2 C3 C4
---------- --- --- --- ---
1 A
2 A
3 A A
4 A
5
Rows that contain 'A':
SQL> select id
2 from test
3 where c1 = 'A' or c2 = 'A' or c3 = 'A' or c4 = 'A';
ID
----------
1
2
3
4
SQL>
Columns that contain 'A':
SQL> select
2 case when sum(case when c1 = 'A' then 1 else 0 end) > 0 then 'yes' else 'no' end c1,
3 case when sum(case when c2 = 'A' then 1 else 0 end) > 0 then 'yes' else 'no' end c2,
4 case when sum(case when c3 = 'A' then 1 else 0 end) > 0 then 'yes' else 'no' end c3,
5 case when sum(case when c4 = 'A' then 1 else 0 end) > 0 then 'yes' else 'no' end c4
6 from test;
C1 C2 C3 C4
--- --- --- ---
yes yes yes no
SQL>
Do you just want to unpivot and select?
select t.*
from ((select id, c1 as c, 'c1' as which from t
) union all
(select id, c2, 'c2' as which from t
) union all
(select id, c3, 'c3' as which from t
) union all
(select id, c4, 'c4' as which from t
) union all
(select id, c5, 'c5' as which from t
)
) t
where c = 'A'
A query for both 1 & 2.
SELECT ID
, CASE WHEN C1=X.C THEN 'yes' WHEN C1 IS NOT NULL THEN 'no' END AS C1
, CASE WHEN C2=X.C THEN 'yes' WHEN C2 IS NOT NULL THEN 'no' END AS C2
, CASE WHEN C3=X.C THEN 'yes' WHEN C3 IS NOT NULL THEN 'no' END AS C3
, CASE WHEN C4=X.C THEN 'yes' WHEN C4 IS NOT NULL THEN 'no' END AS C4
FROM XTABLE T
CROSS JOIN (SELECT 'A' C FROM DUAL) X
WHERE X.C IN (C1,C2,C3,C4)
A test on db<>fiddle here
Example of result:
ID | C1 | C2 | C3 | C4
-: | :--- | :--- | :--- | :---
1 | yes | null | null | null
2 | null | yes | null | no
3 | yes | null | yes | null
4 | null | null | null | yes
If you're looking for whether at least one non-null value exists in the rows or columns of your table, then one option to use would be combinations of decode(), sign() and nvl2() functions together ;
For rows :
SELECT ID,
decode( sign( nvl2(C1,1,0)+nvl2(C2,1,0)+nvl2(C3,1,0)+nvl2(C4,1,0) ), 1, 'yes','no')
as "Exists"
FROM xtable
For columns :
SELECT decode( sign(sum( nvl2(C1,1,0) )), 1, 'yes','no') as "Exists in C1",
decode( sign(sum( nvl2(C2,1,0) )), 1, 'yes','no') as "Exists in C2",
decode( sign(sum( nvl2(C3,1,0) )), 1, 'yes','no') as "Exists in C3",
decode( sign(sum( nvl2(C4,1,0) )), 1, 'yes','no') as "Exists in C4"
FROM xtable
Demo
If you're looking for exact matching for the letter 'A', then consider using :
SELECT ID,
decode( sign( decode(C1,'A',1,0)+
decode(C2,'A',1,0)+
decode(C3,'A',1,0)+
decode(C4,'A',1,0) ), 1, 'yes','no') as "Exists"
FROM xtable
and
SELECT decode( sign(sum( decode(C1,'A',1,0) )), 1, 'yes','no') as "Exists in C1",
decode( sign(sum( decode(C2,'A',1,0) )), 1, 'yes','no') as "Exists in C2",
decode( sign(sum( decode(C3,'A',1,0) )), 1, 'yes','no') as "Exists in C3",
decode( sign(sum( decode(C4,'A',1,0) )), 1, 'yes','no') as "Exists in C4"
FROM xtable
e.g.replace nvl2(Cn,1,0) expressions with decode(Cn,'A',1,0)
Demo

Select only rows that have unique fields

What is an SQL command that checks for rows that have rows with no duplicate fields in them.
ex:
A A A B B B should not be in the resulting table.
Only rows such as A B C D E F
i.e. given data like:
A A A B B B
A B C D E F
A A B G H Q
Should return A B C D E F
There is no simple command to do this.
is seems an unusual requirement and possibly an indication that the table is not in first normal form if all columns are interchangeable.
The following works in Microsoft SQL Server
;With YourData AS
(
select 'A' as C1, 'A' as C2, 'A' as C3, 'B' as C4, 'B' as C5, 'B' as C6 UNION ALL
select 'A' as C1, 'B' as C2, 'C' as C3, 'D' as C4, 'E' as C5, 'F' as C6
)
SELECT *
FROM YourData
WHERE 1 =
( SELECT TOP 1 COUNT(*) AS Cnt
FROM (
SELECT C1 AS C
UNION ALL
SELECT C2
UNION ALL
SELECT C3
UNION ALL
SELECT C4
UNION ALL
SELECT C5
UNION ALL
SELECT C6
) D
GROUP BY C
ORDER BY Cnt DESC
)
Select distinc * returns unique ROWS not unique values from fields.
You should compare each column's value with others. (Assuming column types are the same). For example, for a 4 column table you should do smoething like:
SELECT Col1, Col2, Col3, Col4 FROM MyTable WHERE
Col1 NOT IN (Col2,Col3,Col4) AND
Col2 NOT IN (Col3,Col4) AND
Col3 <> Col4
SELECT DISTINCT * FROM tablename
SELECT DISTINCT col FROM tabl
SELECT * FROM
mytable
WHERE mytable.col1 != mytable.col2 != mytable.col3 ...