Oracle SQL merge half empty query lines - sql

I have an Oracle12 and a table
Debit
Credit
a1
b1
c1
a1
c2
a1
b2
a2
a2
b3
a2
c2
no rows with a%+a% and b%+b%
I want to select 4 columns: Debit+Credit where exists a% and not b% in any column
and Debit+Credit where exists a% and b% in any column. The first column pair must correspond the second by a% value.
Something like
with t as (
select 'a1' Debit, 'b1' Credit from dual
union all select 'c1', 'a1' from dual
union all select 'c2', 'a1' from dual
union all select 'b2', 'a2' from dual
union all select 'a2', 'b3' from dual
union all select 'a2', 'c2' from dual)
select Debit, Credit, null DebitB, null CreditB
from t
where (Debit like 'a%' or Credit like 'a%')
and (Debit not like 'b%' and Credit not like 'b%')
union all
select null, null, Debit, Credit
from t
where (Debit like 'a%' or Credit like 'a%')
and (Debit like 'b%' or Credit like 'b%')
but merge (exclude empty cells if possible) these 6 rows into 4 "grouped" by a%. At first all merged rows with a1, then all merged rows with a2 and so on. Any order within group, nulls last. The result must be
Debit
Credit
DebitB
CreditB
c1
a1
a1
b1
c2
a1
a2
c2
b2
a2
a2
b3

You can number the rows and use a FULL OUTER JOIN matching on the a values and the row number:
SELECT t1.Debit,
t1.Credit,
t2.debit AS DebitB,
t2.credit AS CreditB
FROM (
SELECT t.*,
ROW_NUMBER() OVER (
PARTITION BY CASE WHEN debit LIKE 'a%' THEN debit ELSE credit END
ORDER BY CASE WHEN debit LIKE 'a%' THEN credit ELSE debit END
) AS rn
FROM t
WHERE debit LIKE 'a%' AND credit NOT LIKE 'b%'
OR debit NOT LIKE 'b%' AND credit LIKE 'a%'
) t1
FULL OUTER JOIN (
SELECT t.*,
ROW_NUMBER() OVER (
PARTITION BY CASE WHEN debit LIKE 'a%' THEN debit ELSE credit END
ORDER BY CASE WHEN debit LIKE 'a%' THEN credit ELSE debit END
) AS rn
FROM t
WHERE debit LIKE 'a%' AND credit LIKE 'b%'
OR debit LIKE 'b%' AND credit LIKE 'a%'
) t2
ON CASE WHEN t1.debit LIKE 'a%' THEN t1.debit ELSE t1.credit END
= CASE WHEN t2.debit LIKE 'a%' THEN t2.debit ELSE t2.credit END
AND t1.rn = t2.rn
Which, for the sample data:
CREATE TABLE t (debit, credit) as
select 'a1', 'b1' from dual union all
select 'c1', 'a1' from dual union all
select 'c2', 'a1' from dual union all
select 'b2', 'a2' from dual union all
select 'a2', 'b3' from dual union all
select 'a2', 'c2' from dual;
Outputs:
DEBIT
CREDIT
DEBITB
CREDITB
c1
a1
a1
b1
a2
c2
b2
a2
null
null
a2
b3
c2
a1
null
null
fiddle

Related

How to do aggregation with calculation in SQL

I have similar table with two columns, C2 will have multiple values . I need the output as
Condition for t2.c2 is If all the values in t1.C2 are <= 5, then 1 else 0, please advice what would be the best logic.
Another option
select distinct C1,
if(logical_and(C2 <= 5) over(partition by C1), 1, 0) as C2
from your_table
if applied to sample data in your question - output is
This will give desired result. First section has t1 table data, second section has t2 data based on logic requested.
with t1 as
(select 'A10' as C1, 2 as C2 union all
select 'A10' as C1, 3 as C2 union all
select 'A10' as C1, 4 as C2 union all
select 'A10' as C1, 5 as C2 union all
select 'A10' as C1, 3 as C2 union all
select 'A10' as C1, 4 as C2 union all
select 'A10' as C1, 2 as C2 union all
select 'A10' as C1, 4 as C2 union all
select 'A10' as C1, 5 as C2 union all
select 'A10' as C1, 3 as C2 )
select C1 , if(MAX(C2) <=5,1,0) as C2
from t1 group by C1;

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>

Count cummulative distinct

I have the below table. Is it possible to do a cummulative distinct count? For example, if A1 has 3 distinct values, then the count for it will be 3. Afterwards, check for A1 and A2. If A1 and A2 together have 5 distinct values, 5. Repeat until A1 + A2 ... + An and count the distinct values.
A
V
A1
V1
A1
V2
A1
V2
A2
V1
A2
V2
A2
V3
My expected output would be:
A
C
A1
2
A2
3
This answers the original version of the question.
You can aggregate twice . . . once to keep the first occurrence of v and the second to aggregate again:
select a, count(*) as new_cs
from (select v, min(a) as a
from t
group by v
) v
group by a;
Note: The above only shows as that have new values. If you want all a, then window functions are a better approach:
select a, sum(case when seqnum = 1 then 1 else 0 end) as c
from (select t.*, row_number() over (partition by v order by a) as seqnum
from t
) t
group by a
order by a;
Here is a db<>fiddle.
You can use ROW_NUMBER() window function to find the 1st occurrence of each V and then COUNT() window function to count only these 1st occurrences:
SELECT DISTINCT A,
COUNT(CASE WHEN rn = 1 THEN 1 END) OVER (ORDER BY A) C
FROM (
SELECT A, ROW_NUMBER() OVER (PARTITION BY V ORDER BY A) rn
FROM tablename
) t
ORDER BY A
See the demo.
You can use a partitioned outer join to ensure that all V values are counted for all A values and then use the FIRST_VALUE analytic function to find whether a value exists in the current or preceding A values for the V:
SELECT a,
COUNT( DISTINCT fv ) AS c
FROM (
SELECT t.a,
FIRST_VALUE(t.v) IGNORE NULLS OVER (
PARTITION BY v.v
ORDER BY t.a
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS fv
FROM ( SELECT DISTINCT v FROM table_name ) v
LEFT OUTER JOIN table_name t
PARTITION BY ( t.a )
ON ( t.v = v.v )
)
GROUP BY a
ORDER BY a
Which, for the sample data:
CREATE TABLE table_name ( A, V ) AS
SELECT 'A1', 'V1' FROM DUAL UNION ALL
SELECT 'A1', 'V2' FROM DUAL UNION ALL
SELECT 'A1', 'V3' FROM DUAL UNION ALL
SELECT 'A2', 'V1' FROM DUAL UNION ALL
SELECT 'A2', 'V3' FROM DUAL UNION ALL
SELECT 'A2', 'V4' FROM DUAL UNION ALL
SELECT 'A3', 'V2' FROM DUAL UNION ALL
SELECT 'A3', 'V3' FROM DUAL UNION ALL
SELECT 'A4', 'V1' FROM DUAL UNION ALL
SELECT 'A4', 'V5' FROM DUAL;
Outputs:
A
C
A1
3
A2
4
A3
4
A4
5
db<>fiddle here

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;

Generate random percentage using oracle sql

I have the below table account_ownership:
account holder
A1 H1
A1 H2
A2 H3
A2 H4
A2 H5
A3 H6
A3 H7
A3 H8
A3 H9
Below is what i need.
Output:
account holder ownership
A1 H1 50
A1 H2 50
A2 H3 50
A2 H4 25
A2 H5 25
A3 H6 60
A3 H7 30
A3 H8 5
A3 H9 5
Note: The ownership can have any value between 1 and 99 (no fractional part) but it should add up to 100 within the account.
WITH tbl1 (acct, hldr)
AS (SELECT 'A1', 'H1' FROM DUAL
UNION ALL
SELECT 'A1', 'H2' FROM DUAL
UNION ALL
SELECT 'A2', 'H3' FROM DUAL
UNION ALL
SELECT 'A2', 'H4' FROM DUAL
UNION ALL
SELECT 'A2', 'H5' FROM DUAL
)
select acct, hldr,
case when rn=1 then 100-sum(percent) over(partition by acct)+percent
else percent end AS percent
from (
select acct, hldr,
ceil(dbms_random.value(1,100/count(1) over(partition by acct))) percent,
row_number() over(partition by acct order by NULL) rn
from tbl1
)
Use SQL as below.
WITH account_ownership (account, holder)
AS (SELECT 'A1', 'H1' FROM DUAL
UNION ALL
SELECT 'A1', 'H2' FROM DUAL
UNION ALL
SELECT 'A2', 'H3' FROM DUAL
UNION ALL
SELECT 'A2', 'H4' FROM DUAL
UNION ALL
SELECT 'A2', 'H5' FROM DUAL
UNION ALL
SELECT 'A3', 'H6' FROM DUAL),
table1 as(
select a.account,holder,
sum(1)over(partition by a.account order by holder) as myorder,
b.mycount,
dbms_random.value(1,100) myvalue
from account_ownership a
left join (select account,count(*) mycount
from account_ownership
group by account) b
on a.account = b.account),
table2 as(
select a.account,holder,myorder,mycount,trunc(myvalue/sumvalue*100) true_value
from table1 a
left join ( select account,sum(myvalue) sumvalue from table1
group by account) b
on a.account = b.account)
select account,holder,case when myorder != mycount then true_value
else 100 - sum_value + true_value end as ownership
from (select account,holder,myorder,mycount,true_value,
sum(true_value) over (partition by account order by myorder) as sum_value
from table2)
Try this. The logic is explained inlined.
--Dataset
WITH tbl1 (acct, hldr)
AS (SELECT 'A1', 'H1' FROM DUAL
UNION ALL
SELECT 'A1', 'H2' FROM DUAL
UNION ALL
SELECT 'A2', 'H3' FROM DUAL
UNION ALL
SELECT 'A2', 'H4' FROM DUAL
UNION ALL
SELECT 'A2', 'H5' FROM DUAL),
--dataset end
-- Counting records for each account
tbl2 (acct, cnt)
AS ( SELECT acct, COUNT (1)
FROM tbl1
GROUP BY acct),
-- Counting end
tbl3 (acct,
hldr,
prcnt,
rnk)
AS (SELECT tbl1.acct,
tbl1.hldr,
ROUND (100 / tbl2.cnt) prcnt,
DENSE_RANK ()
OVER (PARTITION BY tbl1.acct ORDER BY tbl1.acct, tbl1.hldr)
rnk
FROM tbl1 INNER JOIN tbl2 ON tbl1.acct = tbl2.acct)
SELECT tbl3.acct,
tbl3.hldr,
CASE
WHEN (rnk = 1) AND MOD (tbl2.cnt, 2) = 1
THEN (prcnt + 1)
ELSE prcnt
END
FROM tbl3
INNER JOIN tbl2
ON tbl3.acct = tbl2.acct
ORDER BY ACCT;