Equivalent of R cbind() function in Oracle? - sql

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/

Related

Dates between date1 and date2 are not working in cross joining

Hi I am trying to filter data with dates between but it insert null 0 rows created its cross joining table I am not aware is there problem due to cross joining or something else. There is no error message
without dates it works fine any solution Please
insert into PAY_IN_OUT2 (EMP_CODE, DATE_IN, DATE_OUT, ATT_DATE, DATE_INA, DATE_OUTA, DATE_INB, DATE_OUTB, DATE_INC, DATE_OUTC, ATT_PRESENT)
select a.EMPLOYEE_ID1, b.DT, b.DT1, B.ATT_DT, B.DT3, B.DT4, B.DT4, B.DT5, B.DT5, B.DT3, 'P'
from CALENDAR_DATES4 b cross join EMPLOYEES a
WHERE A.EMPLOYEE_ID1 BETWEEN 70001 AND 70009
AND B.ATT_DT BETWEEN TO_DATE('10/02/2021', 'DD/MM/YYYY') AND TO_DATE('20/02/2021', 'DD/MM/YYYY') ;
without this its working fine but I have to filter this with dates AND B.ATT_DT BETWEEN TO_DATE('10/02/2021', 'DD/MM/YYYY') AND TO_DATE('20/02/2021', 'DD/MM/YYYY')
If you're cross joining two tables and filtering on each table, and you end up selecting 0 rows, then at least one of your predicates is causing no rows to be returned.
For example, here's a set of queries demonstrating some different results, depending on what the predicates cause to be returned:
WITH t1 AS (SELECT 1 ID, 10 val FROM dual UNION ALL
SELECT 2 ID, 20 val FROM dual UNION ALL
SELECT 3 ID, 30 val FROM dual UNION ALL
SELECT 4 ID, 40 val FROM dual UNION ALL
SELECT 5 ID, 50 val FROM dual),
t2 AS (SELECT 'A' col1, 'AA' col2 FROM dual UNION ALL
SELECT 'B' col1, 'BA' col2 FROM dual UNION ALL
SELECT 'C' col1, 'AC' col2 FROM dual UNION ALL
SELECT 'D' col1, 'DD' col2 FROM dual)
SELECT 1 case_no, -- case 1: no rows expected to be returned (no t1 rows with an id between 6 and 10)
t1.*,
t2.*
FROM t1
CROSS JOIN t2
WHERE t1.id BETWEEN 6 AND 10
AND t2.col2 LIKE 'A%'
UNION ALL
SELECT 2 case_no, -- case 2: no rows expected to be returned (no t2 rows with a col2 starting with "C")
t1.*,
t2.*
FROM t1
CROSS JOIN t2
WHERE t1.id BETWEEN 2 AND 4
AND t2.col2 LIKE 'C%'
UNION ALL
SELECT 3 case_no, -- case 3: no rows expected to be returned (no t1 rows with an id between 6 and 10, and no t2 rows with a col2 starting with "C")
t1.*,
t2.*
FROM t1
CROSS JOIN t2
WHERE t1.id BETWEEN 6 AND 10
AND t2.col2 LIKE 'C%'
UNION ALL
SELECT 4 case_no, -- case 4: 6 rows expected to be returned (3 t1 rows with an id between 2 and 4, and 2 t2 rows with a col2 starting with "A"; 3 x 2 = 6)
t1.*,
t2.*
FROM t1
CROSS JOIN t2
WHERE t1.id BETWEEN 2 AND 4
AND t2.col2 LIKE 'A%';
CASE_NO ID VAL COL1 COL2
---------- ---------- ---------- ---- ----
4 2 20 A AA
4 3 30 A AA
4 4 40 A AA
4 2 20 C AC
4 3 30 C AC
4 4 40 C AC
You can see that only case 4 (rows in both tables match the predicates) has any rows returned by the cross join.
ETA: if you were wanting to see employee rows regardless of whether there are matching date rows or not, you would need to use an outer join, e.g. using my earlier example, case 2 would become:
WITH t1 AS (SELECT 1 ID, 10 val FROM dual UNION ALL
SELECT 2 ID, 20 val FROM dual UNION ALL
SELECT 3 ID, 30 val FROM dual UNION ALL
SELECT 4 ID, 40 val FROM dual UNION ALL
SELECT 5 ID, 50 val FROM dual),
t2 AS (SELECT 'A' col1, 'AA' col2 FROM dual UNION ALL
SELECT 'B' col1, 'BA' col2 FROM dual UNION ALL
SELECT 'C' col1, 'AC' col2 FROM dual UNION ALL
SELECT 'D' col1, 'DD' col2 FROM dual)
SELECT t1.*,
t2.*
FROM t1
LEFT OUTER JOIN t2 ON t2.col2 LIKE 'C%'
WHERE t1.id BETWEEN 2 AND 4;
ID VAL COL1 COL2
---------- ---------- ---- ----
2 20
3 30
4 40

Select distinct values based on multiple column from table

I am having below dummy table
select * from (
select 'A' as col1, 'B' as col2 from dual
union
select 'B' as col1, 'A' as col2 from dual
union
select 'A' as col1, 'C' as col2 from dual
union
select 'C' as col1, 'A' as col2 from dual
union
select 'A' as col1, 'D' as col2 from dual
)a
which will give output as below
col1 col2
A B
A C
A D
B A
C A
I wants to find the distinct values from that table like below
col1 col2
A B
A C
A D
first row can be A B or B A same as second can be A C or C A
Is it possible??
We got the solution for above problem which is below
select distinct least(col1, col2), greatest(col1, col2)
from the_table;
but if there is more than 2 column, then i wouldn't work
Let us assume the below scenario
Input
col1 col2 col3
A B E
A C E
A D E
B A F
C A E
Output
col1 col2 col3
A B E
A D E
B A F
C A E
then what would be the possible solution ?
Here is one method:
select col1, col2
from t
where col1 <= col2
union all
select col1, col2
from t
where col1 > col2 and
not exists (select 1 from t t2 where t2.col1 = t.col2 and t2.col2 = t.col1);
The following will work for Oracle and Postgres:
select distinct least(col1, col2), greatest(col1, col2)
from the_table;
Online example: http://rextester.com/BZXC69735
select DISTINCT * from (
select 'A' as col1, 'B' as col2 from dual
union
select 'B' as col1, 'A' as col2 from dual
union
select 'A' as col1, 'C' as col2 from dual
union
select 'C' as col1, 'A' as col2 from dual
union
select 'A' as col1, 'D' as col2 from dual
)a
select col1, col2 from t where col1 <= col2
union
select col2, col1 from t where col1 > col2

Cumulative string concatenation

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.

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)

Select records where all rows have same value in two columns

Here is my sample table
Col1 Col2
A 1
B 1
A 1
B 2
C 3
I want to be able to select distinct records where all rows have the same value in Col1 and Col2. So my answer should be
Col1 Col2
A 1
C 3
I tried
SELECT Col1, Col2 FROM Table GROUP BY Col1, Col2
This gives me
Col1 Col2
A 1
B 1
B 2
C 3
which is not the result I am looking for. Any tips would be appreciated.
Try this out:
SELECT col1, MAX(col2) aCol2 FROM t
GROUP BY col1
HAVING COUNT(DISTINCT col2) = 1
Output:
| COL1 | ACOL2 |
|------|-------|
| A | 1 |
| C | 3 |
Fiddle here.
Basically, this makes sure that amount the different values for col2 are unique for a given col1.
Try this:
SELECT * FROM MYTABLE
GROUP BY Col1, Col2
HAVING COUNT(*)>1
For example SQLFiddle here
you can try either of the below -
select col1, col2 from
(
select 'A' Col1 , 1 Col2
from dual
union all
select 'B' , 1
from dual
union all
select 'A' ,1
from dual
union all
select 'B' ,2
from dual
)
group by col1, col2
having count(*) >1;
OR
select col1, col2
from
(
select col1, col2, row_number() over (partition by col1, col2 order by col1, col2) cnt
from
(
select 'A' Col1 , 1 Col2
from dual
union all
select 'B' , 1
from dual
union all
select 'A' ,1
from dual
union all
select 'B' ,2
from dual
)
)
where cnt>1;