Joining two tables with many to many relationship in sql - sql

I have two tables with many to many relationship. I need to join them and get the matched records.
Table 1
Column1 | column 2| column 3|
1|p1|1.0
1|p1|1.1
1|p1|1.2
Table 2
Column1 | column 2| column 3|
1|p1|2.0
1|p1|2.1
1|p1|2.2
Now I want the result as
1|p1|1.0|2.0
1|p1|1.1|2.1
1|p1|1.2|2.2
I mean column1 and column2 matching and showing values from both columns for column3
Edit 1:
I have one issue after trying MT0 query. I am very much satisfied with his answer but still need some changes to be done:
Table 1
Column1 | column 2| column 3|
1|p1|1.0
1|p1|1.1
1|p1|1.2
Table 2
Column1 | column 2| column 3|
1|p1|1.0
1|p1|1.2
Now I want the result as
1|p1|1.0|1.0
1|p1|1.1|NULL
1|p1|1.2|1.2
But I am getting as
1|p1|1.0|1.0
1|p1|1.1|1.2
1|p1|1.2|NULL
Please do some help on this

If you have unequal numbers of rows for each partition then you can do:
Oracle Setup:
CREATE TABLE table1 ( col1, col2, col3 ) AS
SELECT 1, 'P1', '1.0' FROM DUAL UNION ALL
SELECT 1, 'P1', '1.1' FROM DUAL UNION ALL
SELECT 1, 'P1', '1.2' FROM DUAL UNION ALL
SELECT 1, 'P2', '1.0' FROM DUAL UNION ALL
SELECT 1, 'P2', '1.2' FROM DUAL UNION ALL
SELECT 2, 'P1', '1.0' FROM DUAL;
CREATE TABLE table2 ( col1, col2, col3 ) AS
SELECT 1, 'P1', '2.0' FROM DUAL UNION ALL
SELECT 1, 'P1', '2.1' FROM DUAL UNION ALL
SELECT 1, 'P1', '2.2' FROM DUAL UNION ALL
SELECT 1, 'P2', '2.1' FROM DUAL UNION ALL
SELECT 2, 'P1', '2.0' FROM DUAL UNION ALL
SELECT 2, 'P1', '2.1' FROM DUAL;
Query:
SELECT COALESCE( t1.col1, t2.col1 ) AS col1,
COALESCE( t1.col2, t2.col2 ) AS col2,
t1.col3 AS t1col3,
t2.col3 AS t2col3
FROM (
SELECT t.*,
ROW_NUMBER() OVER ( PARTITION BY col1, col2
ORDER BY col3 ) AS rn
FROM table1 t
) t1
FULL OUTER JOIN
(
SELECT t.*,
ROW_NUMBER() OVER ( PARTITION BY col1, col2
ORDER BY col3 ) AS rn
FROM table2 t
) t2
ON ( t1.col1 = t2.col1 AND t1.col2 = t2.col2 AND t1.RN = t2.rn )
ORDER BY col1, col2, t1col3 NULLS LAST, t2col3 NULLS LAST;
Output:
COL1 COL2 T1COL3 T2COL3
---------- ---- ------ ------
1 P1 1.0 2.0
1 P1 1.1 2.1
1 P1 1.2 2.2
1 P2 1.0 2.1
1 P2 1.2
2 P1 1.0 2.0
2 P1 2.1

You could add a calculated column with the numbering, using the row_number window function and use that in the join:
SELECT t1.column1, t2.column2, t1.column3, t2.column3
FROM (SELECT column1, column2, column3,
ROW_NUMBER() OVER (PARTITION BY column1, column2
ORDER BY column3) AS rn
FROM table1) t1
JOIN (SELECT column1, column2, column3,
ROW_NUMBER() OVER (PARTITION BY column1, column2
ORDER BY column3) AS rn
FROM table2) t2 ON t1.column1 = t2.column1 AND
t1.column2 = t2.column2 AND
t1.rn = t2.rn

Create one more column only having the matching ID in both tables like
alter table 1
add column1 char(100)
alter table 2
add column2 char(100)
so table 1
Column1 | column 2| column 3|column4
1|p1|1.0
1|p1|1.1
1|p1|1.2
update table table1
set column4 = left(column1,4)
Do the same in table 2 and use that as a key for left join.

Please try below.
create table tab1(Col1 int,col2 varchar(10), col3 varchar(10))
insert into tab1
values(1,'p1','1.0'),
(1,'p1','1.1'),
(1,'p1','1.2')
create table tab2(Col1 int,col2 varchar(10), col3 varchar(10))
insert into tab2
values(1,'p1','2.0'),
(1,'p1','2.1'),
(1,'p1','2.2')
SELECT a.col1,a.col2,a.col3,b.col3
FROM
(
select
*,ROW_NUMBER() over(order by col1) as rownum
from tab1
)a
inner join
(
select *,ROW_NUMBER() over(order by col1) as rownum
from tab2
)b ON a.rownum = b.rownum and a.Col1 = b.Col1

Related

How to check 1 to 1 relationship in Oracle(in one table)

Suppose I have a table with many columns, but only two of them are important to me. I want to check for the 1 to 1 relationship between col1 and col2(and if col1 exists more then one record of col2 it must be shown). Here is my sql query:
select tbl1.col1, count(tbl1.col1)
from admin.table_1 tbl1
left join ( select col2,col1 from admin.table_1) tbl2
on tbl1.col1 = tbl2.col1 and tbl1.col2 = tbl2.col2
group by tbl1.col1
having count(tbl1.col1) >1;
Is my calculation correct? Maybe it can be improved?
You need to check there is one col2 for each col1 and the reflexive relationship that there is one col1 for each col2. You can perform these checks using an analytic COUNT function:
Oracle Setup:
CREATE TABLE TABLE_1 ( col1, col2 ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 2, 3 FROM DUAL UNION ALL
SELECT 4, 4 FROM DUAL UNION ALL
SELECT 5, 4 FROM DUAL;
Query:
SELECT col1, col2
FROM (
SELECT col1,
col2,
COUNT( col1 ) OVER ( PARTITION BY col2 ) AS col1_per_col2,
COUNT( col2 ) OVER ( PARTITION BY col1 ) AS col2_per_col1
FROM table_1
)
WHERE col1_per_col2 > 1
OR col2_per_col1 > 1;
Output:
COL1 | COL2
---: | ---:
2 | 2
2 | 3
4 | 4
5 | 4
db<>fiddle here
You can directly use the group by as following:
SELECT
TBL1.COL1,
COUNT(1)
FROM
ADMIN.TABLE_1 TBL1
GROUP BY
TBL1.COL1
HAVING
COUNT(DISTINCT TBL1.COL2) > 1;
Cheers!!
You can use exists :
select tbl1.col1, count(tbl1.col1)
from admin.table_1 tbl1
where exists ( select 1
from admin.table_1 tbl2
where tbl2.col1 = tbl1.col2 )
group by tbl1.col1
having count(tbl1.col1)>1

SQL query to append comma with successor value in Oracle [duplicate]

I have a table of two columns
Col1 Col2
A 1
A 2
A 3
B 1
B 2
B 3
Output I need is like this
Col1 Col2
A 1
A 1,2
A 1,2,3
B 1
B 1,2
B 1,2,3
Thank you in advance.
Here is a solution which would work for MySQL. It uses a correlated subquery in the select clause to group concatenate together Col2 values. The logic is that we only aggregate values which are less than or equal to the current row, for a given group of records sharing the same Col1 value.
SELECT
Col1,
(SELECT GROUP_CONCAT(t2.Col2 ORDER BY t2.Col2) FROM yourTable t2
WHERE t2.Col2 <= t1.Col2 AND t1.Col1 = t2.Col1) Col2
FROM yourTable t1
ORDER BY
t1.Col1,
t1.Col2;
Demo
Here is the same query in Oracle:
SELECT
Col1,
(SELECT LISTAGG(t2.Col2, ',') WITHIN GROUP (ORDER BY t2.Col2) FROM yourTable t2
WHERE t2.Col2 <= t1.Col2 AND t1.Col1 = t2.Col1) Col2
FROM yourTable t1
ORDER BY
t1.Col1,
t1.Col2;
Demo
Note that the only real change is substituting LISTAGG for GROUP_CONCAT.
with s (Col1, Col2) as (
select 'A', 1 from dual union all
select 'A', 2 from dual union all
select 'A', 3 from dual union all
select 'B', 1 from dual union all
select 'B', 2 from dual union all
select 'B', 3 from dual)
select col1, ltrim(sys_connect_by_path(col2, ','), ',') path
from s
start with col2 = 1
connect by prior col2 = col2 - 1 and prior col1 = col1;
C PATH
- ----------
A 1
A 1,2
A 1,2,3
B 1
B 1,2
B 1,2,3
6 rows selected.

Select data from two tables with no join condition, t-sql

I'd appreciate if someone could help.
I have two tables that have no relationship:
Table_1
ID NAME VALUE
1 abc 10
2 def 20
3 def 20
Table_2
ID2 NAME2 VALUE2
5 ghi 30
6 gkl 40
I want to have a select statement that would show the data from both tables like this:
ID NAME VALUE ID2 NAME2 VALUE2
1 abc 10 5 ghi 30
2 def 20 6 gkl 40
3 def 20
The point is to show data of each record in one row, the table can look like:
ID NAME VALUE ID2 NAME2 VALUE2
5 ghi 30
6 gkl 40
If Table_1 has no records. Same is true for Table_2.
I tried to use cross join, but then the data will repeat.
Thanks a lot
You need to add a join condition. In this case, by using row_number() to add a sequential number on each side. Then full outer join to get all the records:
select t1.id, t1.name, t1.value, t2.id as id2, t2.name as name2, t2.value as value2
from (select t1.*, row_number() over (order by id) as seqnum
from table_1 t1
) t1 full outer join
(select t2.*, row_number() over (order by id) as seqnum
from table_2 t2
) t2
on t1.seqnum = t2.seqnum;
Try this:
with Table_1(ID, NAME, VALUE) as (
select 1, 'abc', 10 union all
select 2, 'def', 20 union all
select 3, 'def', 20
), Table_2(ID2, NAME2, VALUE2) as (
select 5, 'ghi', 30 union all
select 6, 'gkl', 40
), prep_table_1 (ID, NAME, VALUE, rn) as (
select id, name, value, row_number() over(order by id)
from table_1
), prep_table_2 (ID2, NAME2, VALUE2, rn) as (
select id2, name2, value2, row_number() over(order by id2)
from table_2
)
select t1.ID, t1.NAME, t1.VALUE, t2.ID2, t2.NAME2, t2.VALUE2
from prep_table_1 t1
full outer join prep_table_2 t2 on t1.rn = t2.rn
SQLFiddle
This also works
select * from Table_1,Table_2

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)

Max values on distinct groups

Column1 Column2 Column3
------- ------- -------
jim 1788 5F
jim 2000 9F
jim 500 9F
ben 190 4H
matt 400 46
matt 20 3G
I need to run a query that outputs:
Column1 MaxValue PL
------- ------- -------
jim 2000 9F
jim 2000 NULL
ben 190 4H
matt 400 46
matt 400 NULL
For each value in Column1 (e.g. jim, ben, matt): we group the data by Column1 and for each group we display the row that has the maximum value on column2.
Then, for each row found in this manner, it displays it again but with NULL in column3, if the groupping by Column1 returns more than 1 row and there are smaller values in Column2 than the maximum found number in the previous step.
ben 190 NULL is not displayed because we have ben only once on Column1.
Thank you in advance for any tips or suggestions.
This is what I tried so far but I receive an error prompting me to include Column2 and Column3 in the GROUP By clause, but if I do so I don't reach the desired output as shown above.
CREATE VIEW VIEWB AS
SELECT DISTINCT t1.Column1,
/* MAX_Value */
(MAX(t1.[Column2])) AS [MAX Value],
/* PL */
(CASE
WHEN t1.[Column2] = MAX(t1.[Column2]) THEN t1.[Column3]
ELSE NULL
END) AS PL
FROM TABLEA AS t1
GROUP BY t1.Column1;
Try this code:
DECLARE #t TABLE (Column1 VARCHAR(50), Column2 INT, Column3 VARCHAR(50))
INSERT #t
VALUES
('jim' ,1788 ,'5F'),
('jim' ,2000 ,'9F'),
('jim' ,500 ,'9F'),
('ben' ,190 ,'4H'),
('matt' ,400 ,'46'),
('matt' ,20 ,'3G')
;WITH a AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Column1 ORDER BY Column2 DESC) RowNum,
MAX(Column2) OVER (PARTITION BY Column1) Maximum
FROM #t
)
SELECT Column1,
Maximum,
CASE WHEN RowNum = 1 THEN Column3 END
FROM a
WHERE RowNum IN (1, 2)
ORDER BY Column3 DESC
If you need to, you can put this in a view.
try this
;with cte as (select *,ROW_NUMBER() over(partition by Column1
order by Column2 desc) as row_num from table_A),
a as (
select Column1,MAX(Column2) [Column2],null [row_num]
from table_A
group by Column1
having COUNT(*)>1),
b as (select Column1,Column2,Column3 from cte where row_num=1
union all
select * from a)
select Column1,Column2 [MaxValue],Column3 [PL] from b
order by Column2 desc,Column1,ISNULL(Column3,'') desc
with data as (
select * from
(values
( 'jim' , 1788 , '5F' ),
( 'jim' , 2000 , '9F' ),
( 'jim' , 500 , '9F' ),
( 'ben' , 190 , '4H' ),
( 'matt' , 400 , '46' ),
( 'matt' , 20 , '3G' )
) foo (col1, col2, col3)
),
maxes as (
select d.col1, d.col2, d.col3
from
data d
inner join (select col1, max(col2) as col2 from data group by col1) m on d.col1 = m.col1 and d.col2 = m.col2
)
select col1, col2, col3
from maxes
union all
select col1, col2, null
from maxes
where exists (select 0 from data where data.col1 = maxes.col1 and data.col2 < maxes.col2)
order by col1, col3 desc
declare #t table(column1 varchar(10),column2 int, column3 varchar(10))
insert into #t
select 'jim', 1788, '5F' union all
select 'jim', 2000, '9F' union all
select 'jim', 500, '9F' union all
select 'ben', 190, '4H' union all
select 'matt', 400, '46' union all
select 'matt', 20, '3G'
select column1,column2,column3 from
(
select *, row_number() over (partition by column1 order by column2 desc) as sno from #t
) as t
where sno=1
union
select t1.column1,t2.column2,NULL
from #t as t1 inner join
(
select Column1, max(Column2) as column2,count(*) as counting from #t
group by column1 having count(*)>1
) as t2
on t1.column1=t2.column1
Might not be the most efficient way of doing this, so if you have a really large table this may not be an ideal solution, but an option:
create table #temp (column1 varchar(10), column2 float, column3 varchar(10))
insert #temp select 'jim', 1788, '5F'
insert #temp select 'jim', 2000, '9F'
insert #temp select 'jim', 500, '9F'
insert #temp select 'ben', 190, '4H'
insert #temp select 'matt', 400, '46'
insert #temp select 'matt', 20, '3G'
SELECT column1, column2 as MaxValue, column3 as PL FROM #temp
WHERE column2=(SELECT Max(column2) FROM #temp t2 WHERE t2.column1=#temp.column1)
union
SELECT column1, column2, NULL FROM #temp
WHERE column2=(SELECT Max(column2) FROM #temp t3 WHERE t3.column1=#temp.column1)
AND EXISTS(SELECT 1 FROM #temp t4 WHERE t4.column2<>#temp.column2 and t4.column1=#temp.column1)
DROP TABLE #temp