Cumulative string concatenation - sql

I have a requirement where I have to show data in cumulative concatenation style, just like running total by group.
Sample data
Col1 Col2
1 a
1 b
2 c
2 d
2 e
Expected output:
Col1 Col2
1 a
1 b,a
2 c
2 d,c
2 e,d,c
The concatenation needs to be broken down by Col1. Any help regarding how to get this result by Oracle SQL will be appreciated.

Assuming something on the way you need to order, this can be a solution, based on Hierarchical Queries:
with test as
(
select 1 as col1, 'a' as col2 from dual union all
select 1 as col1, 'b' as col2 from dual union all
select 2 as col1, 'c' as col2 from dual union all
select 2 as col1, 'd' as col2 from dual union all
select 2 as col1, 'e' as col2 from dual
)
select col1, col2
from (
select col1 AS col1, sys_connect_by_path(col2, ',') AS col2, connect_by_isleaf leaf
from (
select row_number() over (order by col1 asc, col2 desc) as num, col1, col2
from test
)
connect by nocycle prior col1 = col1 and prior num = num -1
)
where leaf = 1
order by col1, col2

Try:
WITH d AS (
select col1, col2,
row_number() over (partition by col1 order by col2) as x
from tab_le
),
d1( col1, col2, x, col22) as (
SELECT col1, col2, x, col2 col22 FROM d WHERE x = 1
UNION ALL
SELECT d.col1, d.col2, d.x, d.col2 || ',' || d1.col22
FROM d
JOIN d1 ON (d.col1 = d1.col1 AND d.x = d1.x + 1)
)
SELECT * FROM d1
order by 1,2;

I'm not sure you can do this with listagg as it doesn't seem to support windowing clauses. If you're on 11g or higher you can use recursive subquery factoring to achieve your result.
with your_table (col1, col2) as (
select 1, 'a' from dual
union all select 1, 'b' from dual
union all select 2, 'c' from dual
union all select 2, 'd' from dual
union all select 2, 'e' from dual
), t as (
select col1, col2, row_number() over (partition by col1 order by col2) as rn
from your_table
), r (col1, col2, rn) as (
select col1, col2, rn
from t
where rn = 1
union all
select r.col1, t.col2 ||','|| r.col2, t.rn
from r
join t on t.col1 = r.col1 and t.rn = r.rn + 1
)
select col1, col2
from r
order by col1, rn;
COL1 COL2
---------- --------------------
1 a
1 b,a
2 c
2 d,c
2 e,d,c
The your_table CTE is just to mimic your base data. The t CTE adds a row_number() analytic column to provide a sequence for the next part. The interesting part is the r recursive CTE. The anchor member starts with the first row (according to rn from the previous CTE). The recursive member then finds the next row (against according to rn) for that col1, and for that it concatenates the current col2 with the previous one, which may itself already be a concatenation.

Related

How to find out below output using oracle query

Input:
col1 col2
------------
1 A
2 1
3 B
4 2
5 C
6 Null
Output i want
Col1 Col2
__________
A 1
B 2
C Null
Oh, I see. Assuming there are no gaps in col1, you can use aggregation using arithmetic:
select max(case when mod(id, 2) = 0 then col2 end),
max(case when mod(id, 2) = 1 then col2 end)
from t
group by floor((id - 1) / 2);
Another method uses lead():
select col2, next_col2
from (select t.*, lead(col2) over (order by id) as next_col2
from t
) t
where mod(id, 2) = 1;
Use join to the next row of the same table and limit to every other row.
select t1.col2 col1, t2.col2
from tab t1
join tab t2 on t1.col1 = t2.col1 -1
where mod(t1.col1,2) = 1
C C
- -
A 1
B 2
C
Again, it assumes that your sequence in col1is without gaps.
This is an application of PIVOT, available since Oracle 11.1. (A much shorter solution is possible in Oracle 12.1 and higher, using MATCH_RECOGNIZE, but the question is explicitly tagged oracle11g.)
I use the analytic function ROW_NUMBER() to prepare the data, so that we don't need to assume anything about the ordering column - it may have gaps, and/or it doesn't even have to be a number column, it can be date or string or anything else that can be ordered.
Setup:
create table sample_data (col1, col2) as
select 1, 'A' from dual union all
select 2, '1' from dual union all
select 3, 'B' from dual union all
select 4, '2' from dual union all
select 5, 'C' from dual union all
select 6, null from dual
;
Query and output:
select col1, col2
from (
select ceil(row_number() over (order by col1) / 2) as r,
mod (row_number() over (order by col1) , 2) as c, col2
from sample_data
)
pivot (min(col2) for c in (1 as col1, 0 as col2))
order by r
;
COL1 COL2
---- ----
A 1
B 2
C
Just for fun, and for whoever may have this problem in Oracle 12 or higher, here is the match_recognize solution:
select col1, col2
from sample_data
match_recognize(
order by col1
measures x.col2 as col1, y.col2 as col2
pattern ( x y? )
define x as null is null
);

Equivalent of R cbind() function in Oracle?

I am new to sql and struggling to solve this very simple task.
Considering,
with table1 as (select '1' col1 from dual union
select '2' col1 from dual union
select 'NO_PATTERN' col1 from dual union
select 'RANDOM_STUFF' col1 from dual)
select * from table1;
and,
with table2 as (select 'aaa' col2 from dual union
select '4' col2 from dual union
select 'qwewqeq' col2 from dual
union select 'UUUUUU' col2 from dual)
select * from table2;
I want to perform a cbind() between the two columns into a new table which is the "vertical union" of table1.[col1] and table2[col2].
The EXPECTED solution is:
with solution as (select '1' col1, 'aaa' col2 from dual union
select '2' col1, '4' col2 from dual union
select 'NO_PATTERN' col1, 'qwewqeq' col2 from dual union
select 'RANDOM_STUFF' col1, 'UUUUUU' col2 from dual)
select * from solution;
Any idea?
In Oracle you need to explicitly define some value to get ordered data; without that, you could have different results every time you run a query.
With your data, this:
WITH table1 AS
(SELECT 1 rn, '1' col1 FROM DUAL UNION
SELECT 2 rn, '2' col1 FROM DUAL UNION
SELECT 3 rn, 'NO_PATTERN' col1 FROM DUAL UNION
SELECT 4 rn, 'RANDOM_STUFF' col1 FROM DUAL),
table2 AS
(SELECT 1 rn, 'aaa' col2 FROM DUAL UNION
SELECT 2 rn, '4' col2 FROM DUAL UNION
SELECT 3 rn, 'qwewqeq' col2 FROM DUAL UNION
SELECT 4 rn, 'UUUUUU' col2 FROM DUAL)
SELECT col1, col2, t1.rn
from (select row_number() over (order by rn) as rn, col1 from table1 ) t1
inner join
(select row_number() over (order by rn) as rn, col2 from table2 ) t2
on (t1.rn = t2.rn)
gives:
COL1 COL2 RN
------------ ------- ----------
1 aaa 1
2 4 2
NO_PATTERN qwewqeq 3
RANDOM_STUFF UUUUUU 4
Without an explicit ordering, for exampe, this
WITH table1 AS
(SELECT '1' col1 FROM DUAL UNION
SELECT '2' col1 FROM DUAL UNION
SELECT 'NO_PATTERN' col1 FROM DUAL UNION
SELECT 'RANDOM_STUFF' col1 FROM DUAL),
table2 AS
(SELECT 'aaa' col2 FROM DUAL UNION
SELECT '4' col2 FROM DUAL UNION
SELECT 'qwewqeq' col2 FROM DUAL UNION
SELECT 'UUUUUU' col2 FROM DUAL)
SELECT col1, col2, t1.rn
from (select rownum as rn, col1 from table1 ) t1
inner join
(select rownum as rn, col2 from table2 ) t2
on (t1.rn = t2.rn)
gives
COL1 COL2 RN
------------ ------- ----------
1 4 1
2 UUUUUU 2
NO_PATTERN aaa 3
RANDOM_STUFF qwewqeq 4
If you don't want to order them, this would be a simple way to do it -
select *, ROW_NUMBER() OVER (ORDER BY (SELECT 100)) AS SNO into #0 FROM Table 2
select *, ROW_NUMBER() OVER (ORDER BY (SELECT 100)) AS SNO into #1 FROM Table1
select a.*, b.* into #3 from #0 a join #1 b on a.SNO = b.SNO
Got the idea from here -
https://blog.sqlauthority.com/2015/05/05/sql-server-generating-row-number-without-ordering-any-columns/

Count on case Oracle

WE have below data in oracle database -
col1 col2
Z1 A
Z1 B
Z2 A
Z2 C
Z3 A
Z4 D
I want count on column two in such a way that -
Ouput -
col2 count
A 3 (Z1,Z2,Z3)
B 0 (Dont count if A is already present for record)
C 0
D 1 (Z4)
Best Regards
You can use window function rank() to achieve this.
select col2, count(case when rn = 1 then 1 end) cnt from (
select t.*,
rank() over (partition by col1 order by case when col2 = 'A' then 1 else 2 end) rn
from table t
) group by col2;
The most general solution to your propositions where each key COL1 is counted only in the first occurrence of the key COL2 (in alphabetical order)
WITH tab AS
(
SELECT 'Z1' col1, 'A' col2 FROM dual UNION ALL
SELECT 'Z1' col1, 'B' col2 FROM dual UNION ALL
SELECT 'Z2' col1, 'A' col2 FROM dual UNION ALL
SELECT 'Z2' col1, 'C' col2 FROM dual UNION ALL
SELECT 'Z3' col1, 'A' col2 FROM dual UNION ALL
SELECT 'Z4' col1, 'D' col2 FROM dual
), tab2 as (
select COL1, COL2,
row_number() over (partition by COL1 order by COL2) as rn
from tab)
select COL1, COL2,
case when rn = 1 then 1 else 0 end is_valid
from tab2
order by 1,2
;
COL1 COL2 IS_VALID
---- ---- ----------
Z1 A 1
Z1 B 0
Z2 A 1
Z2 C 0
Z3 A 1
Z4 D 1
The rest is simple group by with a SUM on IS_VALID
select COL2, sum(is_valid) cnt from tab3 -- TAB3 is the above row source
group by COL2
order by 1
COL2 CNT
---- ----------
A 3
B 0
C 0
D 1
Thanks Guys. But I could do this way -
select count(case
when (LISTAGG(col2,'-') WITHIN GROUP (ORDER BY col2)) like '%A%' then 1
else null
end) A,
count(case
when (LISTAGG(col2,'-') WITHIN GROUP (ORDER BY col2)) = 'B' then 1
else null
end) B,
count(case
when (LISTAGG(col2,'-') WITHIN GROUP (ORDER BY col2)) = 'C' then 1
else null
end) C,
count(case
when (LISTAGG(col2,'-') WITHIN GROUP (ORDER BY col2)) = 'D' then 1
else null
end) D
from T
GROUP BY col1
Thanks for your replies
Assume your table name is table_name, One way to do it is using this:
WITH table_a AS
(
SELECT DISTINCT col1
FROM table_name
WHERE col2 = 'A'
)
SELECT col2,
SUM(CASE WHEN col1 IN (SELECT col1 FROM table_a)
THEN DECODE(col2, 'A', 1, 0)
ELSE 1 END
) count
FROM table_name
GROUP BY col2
ORDER BY col2;
Tested ok:
WITH table_name AS
(
SELECT 'Z1' col1, 'A' col2 FROM dual UNION ALL
SELECT 'Z1' col1, 'B' col2 FROM dual UNION ALL
SELECT 'Z2' col1, 'A' col2 FROM dual UNION ALL
SELECT 'Z2' col1, 'C' col2 FROM dual UNION ALL
SELECT 'Z3' col1, 'A' col2 FROM dual UNION ALL
--SELECT 'Z4' col1, 'B' col2 FROM dual UNION ALL
SELECT 'Z4' col1, 'D' col2 FROM dual
)
, table_a AS
(
SELECT DISTINCT col1
FROM table_name
WHERE col2 = 'A'
)
SELECT col2,
SUM(CASE WHEN col1 IN (SELECT col1 FROM table_a)
THEN DECODE(col2, 'A', 1, 0)
ELSE 1 END
) count
FROM table_name
GROUP BY col2
ORDER BY col2;
You want to count each record where either col2 is 'A' or no 'A' record exists for col1.
select
col2,
count(
case
when col2 = 'A' or col1 not in (select col1 from table_name where col2 = 'A') then 1
end) as cnt
from table_name
group by col2;
select col2, count(case when col2 = col3 then 'x' end) as ct
from ( select col2, min(col2) over (partition by col1) as col3
from your_table
)
group by col2
order by col2 -- if needed
;
Explanation:
There is an inner query (a.k.a. "subquery") which returns one row for each row in the original table. It returns col2 as is, and an additional (new) column, labeled col3. col3 is calculated as the "first" or min() value of col2 (in alphabetical order) for all the rows in the original table that have the same value in col1 as the current row does. This is a typical example of an analytic function; partition by col1 is similar to group by col1 but it returns all the rows in the group (all the original rows from the original table) instead of one row per group, as would an aggregate function.
To see what the inner query does by itself, select it and run it in your favorite front-end. You may add col1 to the select in the inner query - that will make what's going on in this query even clearer. You'll get the initial table, with one more column, col3, that shows the "min" col2 for each value of col1. I didn't include col1 in the subquery because I don't need it, but add it back to see what the subquery really does.
Then in the outer query I take the results from the inner query and I group by col2. For each col2 I count just how many times it is equal to the "min" value of col2 for the corresponding col1 value. That's what the case expression does in the count() function; when col2 is not equal to col3, then case returns null (by default) so the expression - and therefore the row - is not counted.
I should add that the query written this way assumes there are no duplicate (col1, col2) rows in the original table. If there are, then the inner subquery should select from a sub-subquery; line 3 of my code should be
from (select distinct col1, col2 from your_table)
Use the below script:
SELECT A.COL2, NVL(B.CNT, 0) AS CNT
FROM (SELECT DISTINCT COL2 FROM TET) A
LEFT JOIN (SELECT COL2, COUNT(COL2) AS CNT
FROM (SELECT SUBSTR(F, 1, INSTR(F, ',') - 1) AS COL2,
ROW_NUMBER() OVER(PARTITION BY SUBSTR(F, 1, INSTR(F, ',') - 1) ORDER BY SUBSTR(F, 1, INSTR(F, ',') - 1)) AS U
FROM (SELECT COL1,
LISTAGG(COL2, ',') WITHIN GROUP(ORDER BY COL2) || ',' AS F
FROM TET
GROUP BY COL1)) A
GROUP BY COL2) B
ON A.COL2 = B.COL2
ORDER BY A.COL2;

how to get the maximum occurrence value from a table for a combination?

I have the following table;
column 1 column 2 column 3
1 2 X
1 2 X
1 2 Y
1 3 Z
1 3 X
I need to write an SQL query to get the output as;
1 2 X (because X is the maximum occurrence)
1 3 Z or X(because number of occurrence of Z or X is same)
How do i do this ?
I think i have a solution for you, try this script using the functions RANK(), ROW_NUMBER() & DENSE_RANK(), you choose the function that fits with your needs :
with temp as (
select 1 as col1, 2 AS col2, 'X' as col3 union all
select 1 as col1, 2 AS col2, 'Y' as col3 union all
select 1 as col1, 2 AS col2, 'X' as col3 union all
select 1 as col1, 3 AS col2, 'Z' as col3 union all
select 1 as col1, 3 AS col2, 'T' as col3 union all
select 1 as col1, 3 AS col2, 'Y' as col3 union all
select 1 as col1, 3 AS col2, 'Y' as col3 union all
select 1 as col1, 4 AS col2, 'Y' as col3 union all
select 1 as col1, 4 AS col2, 'W' as col3)
,temp2 AS (
select
col1
,col2
,col3
,COUNT(1) nb_occurence
,RANK() OVER(PARTITION BY col1,col2 ORDER BY COUNT(1) DESC) Ordre_RANK
,ROW_NUMBER() OVER(PARTITION BY col1,col2 ORDER BY COUNT(1) DESC) Ordre_ROW_NUMBER
,DENSE_RANK() OVER(PARTITION BY col1,col2 ORDER BY COUNT(1) DESC) Ordre_DENSE_RANK
from temp
GROUP BY
col1
,col2
,col3 )
SELECT *
FROM temp2
--WHERE Ordre_RANK = 1
--WHERE Ordre_ROW_NUMBER = 1
--WHERE Ordre_DENSE_RANK = 1
I hope this will help you.

Apply the distinct on 2 fields and also fetch the unique data for each columns

According to some weird requirement, i need to select the record where all the output values in both the columns should be unique.
Input looks like this:
col1 col2
1 x
1 y
2 x
2 y
3 x
3 y
3 z
Expected Output is:
col1 col2
1 x
2 y
3 z
or
col1 col2
1 y
2 x
3 z
I tried applying the distinct on 2 fields but that returns all the records as overall they are distinct on both the fields. What we want to do is that if any value is present in the col1, then it cannot be repeated in the col2.
Please let me know if this is even possible and if yes, how to go about it.
Great problem! Armunin has picked up on the deeper structural issue here, this is a recursive enumerable problem description and can only be resolved with a recursive solution - base relational operators (join/union/etc) are not going to get you there. As Armunin cited, one approach is to bring out the PL/SQL, and though I haven't checked it in detail, I'd assume the PL/SQL code will work just fine. However, Oracle is kind enough to support recursive SQL, through which we can build the solution in just SQL:
-- Note - this SQL will generate every solution - you will need to filter for SOLUTION_NUMBER=1 at the end
with t as (
select 1 col1, 'x' col2 from dual union all
select 1 col1, 'y' col2 from dual union all
select 2 col1, 'x' col2 from dual union all
select 2 col1, 'y' col2 from dual union all
select 3 col1, 'x' col2 from dual union all
select 3 col1, 'y' col2 from dual union all
select 3 col1, 'z' col2 from dual
),
t0 as
(select t.*,
row_number() over (order by col1) id,
dense_rank() over (order by col2) c2_rnk
from t),
-- recursive step...
t1 (c2_rnk,ids, str) as
(-- base row
select c2_rnk, '('||id||')' ids, '('||col1||')' str
from t0
where c2_rnk=1
union all
-- induction
select t0.c2_rnk, ids||'('||t0.id||')' ids, str||','||'('||t0.col1||')'
from t1, t0
where t0.c2_rnk = t1.c2_rnk+1
and instr(t1.str,'('||t0.col1||')') =0
),
t2 as
(select t1.*,
rownum solution_number
from t1
where c2_rnk = (select max(c2_rnk) from t1)
)
select solution_number, col1, col2
from t0, t2
where instr(t2.ids,'('||t0.id||')') <> 0
order by 1,2,3
SOLUTION_NUMBER COL1 COL2
1 1 x
1 2 y
1 3 z
2 1 y
2 2 x
2 3 z
You can use a full outer join to merge two numbered lists together:
SELECT col1, col2
FROM ( SELECT col1, ROW_NUMBER() OVER ( ORDER BY col1 ) col1_num
FROM your_table
GROUP BY col1 )
FULL JOIN
( SELECT col2, ROW_NUMBER() OVER ( ORDER BY col2 ) col2_num
FROM your_table
GROUP BY col2 )
ON col1_num = col2_num
Change ORDER BY if you require a different order and use ORDER BY NULL if you're happy to let Oracle decide.
What would be the result if another row of
col1 value as 1 and col2 value as xx ?
A single row is better in this case:
SELECT DISTINCT TO_CHAR(col1) FROM your_table
UNION ALL
SELECT DISTINCT col2 FROM your_table;
My suggestion is something like this:
begin
EXECUTE IMMEDIATE 'CREATE global TEMPORARY TABLE tmp(col1 NUMBER, col2 VARCHAR2(50))';
end;
/
DECLARE
cur_print sys_refcursor;
col1 NUMBER;
col2 VARCHAR(50);
CURSOR cur_dist
IS
SELECT DISTINCT
col1
FROM
ttable;
filtered sys_refcursor;
BEGIN
FOR rec IN cur_dist
LOOP
INSERT INTO tmp
SELECT
col1,
col2
FROM
ttable t1
WHERE
t1.col1 = rec.col1
AND t1.col2 NOT IN
(
SELECT
tmp.col2
FROM
tmp
)
AND t1.col1 NOT IN
(
SELECT
tmp.col1
FROM
tmp
)
AND ROWNUM = 1;
END LOOP;
FOR rec in (select col1, col2 from tmp) LOOP
DBMS_OUTPUT.PUT_LINE('col1: ' || rec.col1 || '|| col2: ' || rec.col2);
END LOOP;
EXECUTE IMMEDIATE 'DROP TABLE tmp';
END;
/
May still need some refining, I am especially not happy with the ROWNUM = 1 part.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE tbl ( col1, col2 ) AS
SELECT 1, 'x' FROM DUAL
UNION ALL SELECT 1, 'y' FROM DUAL
UNION ALL SELECT 2, 'x' FROM DUAL
UNION ALL SELECT 2, 'y' FROM DUAL
UNION ALL SELECT 3, 'x' FROM DUAL
UNION ALL SELECT 3, 'y' FROM DUAL
UNION ALL SELECT 4, 'z' FROM DUAL;
Query 1:
WITH c1 AS (
SELECT DISTINCT
col1,
DENSE_RANK() OVER (ORDER BY col1) AS rank
FROM tbl
),
c2 AS (
SELECT DISTINCT
col2,
DENSE_RANK() OVER (ORDER BY col2) AS rank
FROM tbl
)
SELECT c1.col1,
c2.col2
FROM c1
FULL OUTER JOIN c2
ON ( c1.rank = c2.rank)
ORDER BY COALESCE( c1.rank, c2.rank)
Results:
| COL1 | COL2 |
|------|--------|
| 1 | x |
| 2 | y |
| 3 | z |
| 4 | (null) |
And to address the additional requirement:
What we want to do is that if any value is present in the col1, then it cannot be repeated in the col2.
Query 2:
WITH c1 AS (
SELECT DISTINCT
col1,
DENSE_RANK() OVER (ORDER BY col1) AS rank
FROM tbl
),
c2 AS (
SELECT DISTINCT
col2,
DENSE_RANK() OVER (ORDER BY col2) AS rank
FROM tbl
WHERE col2 NOT IN ( SELECT TO_CHAR( col1 ) FROM c1 )
)
SELECT c1.col1,
c2.col2
FROM c1
FULL OUTER JOIN c2
ON ( c1.rank = c2.rank)
ORDER BY COALESCE( c1.rank, c2.rank)