how to combine multiple column of different table into one table - sql

I have three tables a, b and c and need to arrange these table data as target table and all of these tables (a, b, c) are not in database they are fetched from from single table using queries as alias and need to arrange these tables into target table using query. How to do that
table a | table b | table c
| |
id | a_vals | id | b_vals | id | c_vals
------------ | -------------- | -------------
1 | 123 | 1 | 123 | 1 | 123
2 | 124 | 2 | 142 | 2 | 142
3 | 234 | 4 | 234 | 5 | 234
target table
id | a_val| b_val| c_val
1 | 123 | 123 | 123
2 | 124 | 142 | 142
3 | 234 | - | -
4 | - | 234 | -
5 | - | | 234

Since a, b and c share the same name for the column you want to join, you could use "USING" to avoid duplicate keys in the resulting table:
SELECT *
FROM a
FULL OUTER JOIN b USING (id)
FULL OUTER JOIN c USING (id);
Alternativly, since a, b and c's value column all have distinct names you could use NATURAL JOIN:
SELECT *
FROM a
NATURAL FULL OUTER JOIN b
NATURAL FULL OUTER JOIN c;
Be careful not to accidentally rename any of the other columns tho, since natural join removes any duplicate columns.
You can also omit the "OUTER" keyword if you like, but i would leave it for clarity, (since LEFT, RIGHT, and FULL imply an outer join).
See https://www.postgresql.org/docs/10/static/queries-table-expressions.html for details

Please try this:
select aa.id, a_val, b_val, c_val from
(select distinct id as id from table_a
union
select distinct id as id from table_b
union
select distinct id as id from table_c)aa
left join (select id, a_val from table_a)bb on aa.id = bb.id
left join (select id, b_val from table_b)cc on aa.id = cc.id
left join (select id, c_val from table_c)dd on aa.id = dd.id order by aa.id;

Try this code
SELECT
CASE
WHEN t1.id IS not null THEN t1.id
WHEN t2.id IS not null THEN t2.id
ELSE t3.id
END
AS id,
t1.a_vals AS a_val,
t2.b_vals as b_val,
t3.c_vals as c_val
FROM a t1 FULL OUTER JOIN b t2 ON t1.id=t2.id FULL OUTER JOIN c t3 ON
CASE
WHEN t1.id IS not null THEN t1.id
ELSE t2.id
END = t3.id
OR
SELECT COALESCE(t1.id, t2.id, t3.id) as id ,
t1.a_vals AS a_val,
t2.b_vals as b_val,
t3.c_vals as c_val
FROM a t1 FULL OUTER JOIN b t2 ON t1.id=t2.id
FULL OUTER JOIN c t3 ON COALESCE(t1.id, t2.id) = t3.id

You are looking for the ANSI-standard FULL OUTER JOIN:
select coalesce(a.id, b.id, c.id) as id, a.val, b.val, c.val
from a full join
b
on a.id = b.id full join
c
on c.id = coalesce(a.id, b.id);
You can also implement this with union all/group by:
select id, max(a_val) as a_val, max(b_val) as b_val, max(c_val) as c_val
from ((select id, val as a_val, null as b_val, null as c_val
from a
) union all
(select id, null as a_val, val as b_val, null as c_val
from b
) union all
(select id, null as a_val, null as b_val, val as c_val
from c
)
) abc
group by id;

This is probably better done in the 'front end' e.g. this is the kind of thing a reporting tool is designed for.
Avoiding nulls and outer joins (because they by definition produce nulls):
SELECT a_val, b_val, c_val
FROM a
NATURAL JOIN b
NATURAL JOIN c
UNION
SELECT a_val, '-' AS b_val, '-' AS c_val
FROM a
WHERE id NOT IN ( SELECT id FROM b )
AND id NOT IN ( SELECT id FROM c )
UNION
SELECT '-' AS a_val, b_val, '-' AS c_val
FROM b
WHERE id NOT IN ( SELECT id FROM a )
AND id NOT IN ( SELECT id FROM c )
UNION
SELECT '-' AS a_val, '-' AS b_val, c_val
FROM c
WHERE id NOT IN ( SELECT id FROM a )
AND id NOT IN ( SELECT id FROM b );

Related

Get Count of records - Oracle

I'm trying to get count of records from table by joining 2 or more table. Suppose I have 3 table like follows,
Table A
Column 1 Column 2
121 XX
123 XX
124 A0
125 A2
126 XX
Table B
Column 1
A0
A1
A2
A3
Table C
Column 1 Column 2
121 A0
122 A1
123 A0
124 A0
125 A2
126 A3
From these I need count result as follows,
Column 1 Column 2
XX,A0 2
XX,A1 0
XX,A2 0
XX,A3 1
A0,A0 1
A1,A1 0
A2,A2 1
A3,A3 0
Here I have 121 and 123 with XX in table A and same 121,123 in table C with A0, so count should be 2. Similarly 124 have A0 in table A and A0 in table C, so count should 1 and if no record matched with any column 2 it should have 0.
I tried with below query it is not returing as expected,
select b.column2 ||','|| c.column2 column , count(a.column1) count
from table A a
join table c c
on a.column1=c.column1
join table b b
on b.column1=c.column2
group by b.column2 ||','|| c.column2
order by b.column2 ||','|| c.column2
first you would want a cartesian product to get the set of all possible combinations betweeen tablea and tableb. I changed the column names to something more readable.
create table tablea(id int, name varchar2(10));
create table tableb(name varchar2(10));
create table tablec(id int, name varchar2(10));
insert into tablea
select 121,'XX' from dual union all
select 123,'XX' from dual union all
select 124,'A0' from dual union all
select 125,'A2' from dual union all
select 126,'XX' from dual
insert into tableb
select 'A0' from dual union all
select 'A1' from dual union all
select 'A2' from dual union all
select 'A3' from dual
insert into tablec
select 121,'A0' from dual union all
select 122,'A1' from dual union all
select 123,'A0' from dual union all
select 124,'A0' from dual union all
select 125,'A2' from dual union all
select 126,'A3' from dual
--Gets all possible combination of tablea and tableb
select distinct a.name as a_name,b.name as b_name
from tablea a
join tableb b
on 1=1
Then you would do a group by on these combination fields as follows
with data
as (select distinct a.name as a_name,b.name as b_name
from tablea a
join tableb b
on 1=1
)
,tablec_data
as (select c.name c_name,a.name a_name
from tablec c
join tablea a
on c.id=a.id
)
select max(m.a_name||','||m.b_name) as val_name,count(n.c_name) as cnt
from data m
left join tablec_data n
on m.a_name=n.a_name
and m.b_name=n.c_name
group by m.a_name,m.b_name
order by m.a_name desc,m.b_name
Here is a db fiddle link
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=9e573822ecc1087e5871c80b586d1116
+----------+-----+
| VAL_NAME | CNT |
+----------+-----+
| XX,A0 | 2 |
| XX,A1 | 0 |
| XX,A2 | 0 |
| XX,A3 | 1 |
| A2,A0 | 0 |
| A2,A1 | 0 |
| A2,A2 | 1 |
| A2,A3 | 0 |
| A0,A0 | 1 |
| A0,A1 | 0 |
| A0,A2 | 0 |
| A0,A3 | 0 |
+----------+-----+
Table A should be used instead of Table B for combining the values. Table B doesn't have column Colunn2.
Also, Remember alias name for a.column2 ||','|| c.column2 should not be column as this is reserved keyword in oracle. So you can use column2 or something else.
EDIT: Following will give 0 count for not matched records.
select t1.column2,
case when t2.count1 is not null then t2.count1 else t1.count1 end as count1
from (
(select distinct a.column2||','||b.column1 as column2,
0 as count1 from tableA a join tableB b on a.column2='XX'
union
select distinct c.column2||','||b.column1 as column2,
0 as count1 from tableC c join tableB b on c.column2=b.column1
)t1
left join
(select a.Column2 ||','|| c.Column2 as column2,
count(*) count1 from TableA a join TableC c on a.column1=c.column1
join TableB B on b.column1=c.column2 group by a.column2 ||','|| c.column2
)t2
on t1.column2=t2.column2
)
order by
t1.column2
Output:
A0,A0 1
A1,A1 0
A2,A2 1
A3,A3 0
XX,A0 2
XX,A1 0
XX,A2 0
XX,A3 1
At first prepare key columns, use simple union. Then use this keys to construct join. Left join is used because we need zeros too:
with keys as (select 'XX' ka, column1 kb from b union all select column1, column1 from b)
select ka, kb, sum(case when c.column2 is not null then 1 else 0 end) cnt
from keys
left join a on ka = a.column2
left join c on kb = c.column2 and a.column1 = c.column1
group by ka, kb
order by ka, kb
dbfiddle demo

Eliminating NOT IN from query

I'm trying to eliminate the need to use NOT IN in my query:
select count(*)
FROM TABLE1 T1
LEFT OUTER JOIN TABLE2 T2
ON T1.DATAID = T2.EXISTING_DOCUMENT
AND T1.ownerid = -2000
AND T1.SUBTYPE = 144
AND T1.dataid NOT IN (SELECT T3.dataid
FROM TABLE3 T3
WHERE T3.ID = 123)
Reason: I read that NOT IN is slow (+500k rows) and doesn't use indices
I tried:
select count(*)
FROM TABLE1 T1
LEFT OUTER JOIN TABLE2 T2
ON T1.DATAID = T2.EXISTING_DOCUMENT
AND T1.ownerid = -2000
AND T1.SUBTYPE = 144
left outer join TABLE3 T3
on T3.ancestorid = T1.dataid
where T3.ID = 123
NOT IN does use indices, at least in a competent database such as Oracle. However, you can write this using joins if you prefer.
But, why doesn't this do what you want?
select count(*)
FROM TABLE1 T1
WHERE T1.ownerid = -2000 AND T1.SUBTYPE = 144;
You are using a LEFT JOIN, so the only difference is that your version counts duplicates in TABLE2. But that may not really apply.
Your query doesn't really make sense, because you are comparing T1.dataid to T1.dataid. But, further, the comparison to Table3 has no impact on the result. So, you can just remove it:
select count(*)
FROM TABLE1 T1 LEFT OUTER JOIN
TABLE2 T2
ON T1.DATAID = T2.EXISTING_DOCUMENT AND
T1.ownerid = -2000 AND
T1.SUBTYPE = 144 ;
Because of the LEFT JOIN, filtering in the ON clause will not remove any rows. And because it is NOT IN, there is no possibility of duplication.
Use a WHERE x IS NULL filter to emulate a NOT IN.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE t1 ( ownerid int, subtype int, dataid int, note varchar(100) ) ;
INSERT INTO t1 ( ownerid, subtype, dataid, note )
SELECT 1 as ownerid, 1 as subtype, 1 as dataid, 'WHERE Filter' as note FROM DUAL UNION ALL
SELECT -2000, 1,1, 'IN WHERE Filter' FROM DUAL UNION ALL
SELECT -2000,144,1, 'IN WHERE, NOT IN t3' FROM DUAL UNION ALL
SELECT -2000,144,2, 'IN WHERE, IN t3' FROM DUAL UNION ALL
SELECT -2000,144,3, 'IN WHERE, NOT IN t3' FROM DUAL
;
CREATE TABLE t2 ( existing_document int, note varchar(100) ) ;
INSERT INTO t2 (existing_document, note)
SELECT 1 as existing_document, 'JOIN t1' as note FROM DUAL UNION ALL
SELECT 2, 'JOIN t1' FROM DUAL UNION ALL
SELECT 2, 'JOIN t1, DUPE' FROM DUAL UNION ALL
SELECT 3, 'JOIN t1' FROM DUAL UNION ALL
SELECT 3, 'JOIN t1, DUPE' FROM DUAL UNION ALL
SELECT 4, 'NOT JOIN t1' FROM DUAL
;
CREATE TABLE t3 ( id int, dataid int, note varchar(100) ) ;
INSERT INTO t3 (id, dataid, note)
SELECT 1 as id, 1 as dataid, 'No filter. No match.' as note FROM DUAL UNION ALL
SELECT 1, 4, 'No filter. No match t1.' FROM DUAL UNION ALL
SELECT 123,2,'Match JOIN filter. Match t1' FROM DUAL
;
Read the notes in the setup to view how I'm building up the data. It's very simple and not a lot to count, but it should give you an idea on how this data works together.
Query:
SELECT * /* Not counting here so you can see what's supposed to be counted. */
FROM t1
INNER JOIN t2 ON t1.dataid = t2.EXISTING_DOCUMENT
LEFT OUTER JOIN t3 ON t1.dataid = t3.dataid
AND t3.ID = 123
WHERE t1.ownerid = -2000
AND t1.subtype = 144
AND t3.dataid IS NULL /* This is the NOT IN */
Results:
| OWNERID | SUBTYPE | DATAID | NOTE | EXISTING_DOCUMENT | NOTE | ID | DATAID | NOTE |
|---------|---------|--------|---------------------|-------------------|---------------|--------|--------|--------|
| -2000 | 144 | 1 | IN WHERE, NOT IN t3 | 1 | JOIN t1 | (null) | (null) | (null) |
| -2000 | 144 | 3 | IN WHERE, NOT IN t3 | 3 | JOIN t1 | (null) | (null) | (null) |
| -2000 | 144 | 3 | IN WHERE, NOT IN t3 | 3 | JOIN t1, DUPE | (null) | (null) | (null) |
The optimizer usually runs very well with the WHERE x IS NULL syntax and indexes should still apply, but if Oracle is able to make use of the indexes in the NOT IN, that is a big plus. If you're dealing with a lot of data, the IS NULL method can be a lot faster. The best check is to just test it with your actual data.
how about NOT EXISTS?
select count(*)
FROM TABLE1 T1
LEFT OUTER JOIN TABLE2 T2
ON T1.DATAID = T2.EXISTING_DOCUMENT
AND T1.ownerid = -2000
AND T1.SUBTYPE = 144
AND NOT EXISTS (SELECT 1
FROM TABLE3 T3
WHERE T3.ID = 123
AND T3.dataid = T1.dataid)

SQL - Left Join many-to-many only once

I have a two tables that are setup like the following examples
tablea
ID | Name
1 | val1
1 | val2
1 | val3
2 | other1
3 | other
tableb
ID | Amount
1 | $100
2 | $50
My desired output would be to left join tableb to tablea but only join tableb once on each value. ID is the only relationship
tablea.ID | tablea.Name | tableb.id | tableb.amount
1 | val1 | 1 | $100
1 | val2
1 | val3
2 | other1 | 2 | $50
3 | other
Microsoft SQL
You can do the following:
select ROW_NUMBER() OVER(ORDER BY RowID ASC) as RowNum, ID , Name
from tablea
which gives you :
RowNum | RowID | Name
1 | 1 | val1
2 |1 | val2
3 |1 | val3
4 |2 | other1
5 |3 | other
You then get the minimum row number for each RowID:
Select RowId, min(RowNum)
From (
select ROW_NUMBER() OVER(ORDER BY RowID ASC) as RowNum, ID , Name
from tablea )
Group By RowId
Once you have this you can then join tableb onto tablea only where the RowId is the minimum
WITH cteTableA As (
select ROW_NUMBER() OVER(ORDER BY RowID ASC) as RowNum, ID , Name
from tablea ),
cteTableAMin As (
Select RowId, min(RowNum) as RowNumMin
From cteTableA
Group By RowId
)
Select a.RowID, a.Name, b.Amount
From cteTableA a
Left join cteTableAMin amin on a.RowNum = amin.RowNumMin
and a.ID = amin.RowId
Left join tableb b on amin.ID = b.ID
This can be tidied up... but helps to show whats going on.
Then you MUST specify which row in tableA you wish to join to. If there are more than one row in the other table, How can the query processor know which one you want ?
If you want the one with the lowest value of name, then you might do this:
Select * from tableB b
join tableA a
on a.id = b.Id
and a.name =
(Select min(name) from tableA
where id = b.id)
but even that won't work if there multiple rows with the same values for both id AND name. What you might really need is a Primary Key on tableA.
Use:
select
a.id,
a.name,
b.amount
from
(select
id,
name,
row_number() over (partition by id order by name) as rn
from tablea) a
left join (
select
id,
amount,
row_number() over (partition by id order by amount) as rn
from tableb) b
on a.id = b.id
and a.rn = b.rn
order by a.id, a.name

Multiple join not joining as expected

I have the following tables
table 2 with a fk to table 1
table 3 with a fk to table 1
In table 2 I have two rows linked to table 1
In table 3 I have one row linked table 1
I am trying to produce a table that has
| table1 pk | table 2 pk | null |
| table1 pk | table 2 pk | null |
| table1 pk | null | table 3 pk |
However when I try the following I get
select tab1.id, tab2.id, tab3.id
from table1 tab1
left join tab2 on tab1.id = tab2.tab1_id
left join tab3 on tab1.id = tab3.tab1_id
gives this table
| table1 pk | table 2 pk | table 3 pk |
| table1 pk | table 2 pk | table 3 pk |
Can anyone help with this SQL please?
Thanks in advance
EDIT
I think I may have simplified this a bit too much. Ideally output would be
| table1 pk | table 2 pk |
| table1 pk | table 3 pk |
| table1 pk | table 3 pk |
Once I get this join working it will be added to another massive query...
One unusual solution is
select coalesce(t2.tab1_id, t3.tab1_id) pk,
t2.pk, t3.pk
from table2 t2
full join table3 t3
on t3.tab1_id = t2.tab1_id
where exists (select * from table1
where pk in (t2.tab1_id, t3.tab1_id)
NOTE (Edit) As noted by Andriy M in comment, the where clause only eliminates rows from table2 and table3 where the FK does not exist in table1, which cannot exist if FK constraints have been properly applied to table2 and table3.
I don't see anything crazy here
WITH TAB1
AS (SELECT 1 AS ID FROM DUAL
UNION ALL
SELECT 2 AS ID FROM DUAL
UNION ALL
SELECT 3 AS ID FROM DUAL),
TAB2
AS (SELECT 1 AS TAB1_ID, 'A' AS ID FROM DUAL
UNION ALL
SELECT 2 AS TAB1_ID, 'B' AS ID
FROM DUAL),
TAB3
AS (SELECT 3 AS TAB1_ID, 'C' AS ID
FROM DUAL)
SELECT
TAB1.ID,
COALESCE ( TAB2.ID,
TAB3.ID )
FROM
TAB1
LEFT JOIN TAB2
ON TAB1.ID = TAB2.TAB1_ID
LEFT JOIN TAB3
ON TAB1.ID = TAB3.TAB1_ID;
1 A
2 B
3 C
You could use a join to the result of a union:
SELECT
t1.table1pk,
t23.table2pk,
t23.table3pk
FROM table1 t1
INNER JOIN
(
SELECT table2pk, NULL AS table3pk, table1fk
FROM table2
UNION ALL
SELECT NULL AS table2pk, table3pk, table1fk
FROM table3
) t23
ON t1.table1pk = t23.table1fk
;
Or you could use a union of two joins' results:
SELECT
t1.table1pk,
t2.table2pk,
NULL AS table3pk
FROM table1 t1
INNER JOIN table2 t2
ON t1.table1pk = t2.table1fk
UNION ALL
SELECT
t1.table1pk,
NULL AS table2pk,
t3.table3pk
FROM table1 t1
INNER JOIN table3 t3
ON t1.table1pk = t3.table1fk
;
Both methods could be adapted to produce the two-column version of the desired output:
a join to a union:
SELECT
t1.table1pk,
t23.otherpk
FROM table1 t1
INNER JOIN
(
SELECT table2pk AS otherpk, table1fk
FROM table2
UNION ALL
SELECT table3pk AS otherpk, table1fk
FROM table3
) t23
ON t1.table1pk = t23.table1fk
;
a union of joins:
SELECT
t1.table1pk,
t2.table2pk AS otherpk
FROM table1 t1
INNER JOIN table2 t2
ON t1.table1pk = t2.table1fk
UNION ALL
SELECT
t1.table1pk,
t3.table3pk AS otherpk
FROM table1 t1
INNER JOIN table3 t3
ON t1.table1pk = t3.table1fk
;

Within a SQL Server view - how to combine multiple column results into one column

I have a SQL Server database with the following 2 tables:
I have created a view with the following query and results:
My question is what query would bring the (3) ID columns in 'Table2' into one master ID List to where the final result would look like this:
ID Table1ID
test1 1
test1 4
test2 1
test2 2
test3 1
test3 2
test3 3
Note: here is the view as shown above:
SELECT
dbo.Table1.Description, Table2_1.ID AS Table2ID_1, Table2_2.ID AS Table2ID_2,
dbo.Table2.ID AS Table2ID_3
FROM
dbo.Table1
LEFT OUTER JOIN
dbo.Table2 ON dbo.Table1.ID = dbo.Table2.Table1ID3
LEFT OUTER JOIN
dbo.Table2 AS Table2_2 ON dbo.Table1.ID = Table2_2.Table1ID2
LEFT OUTER JOIN
dbo.Table2 AS Table2_1 ON dbo.Table1.ID = Table2_1.Table1ID1
My suggestion would be to UNPIVOT the data in Table2 so you can easily join on the data, then you can return the table1 description and the table2 id. The UNPIVOT portion of this query using CROSS APPLY:
select col, value, t2.Id
from table2 t2
cross apply
(
select 'table1id1', table1id1 union all
select 'table1id2', table1id2 union all
select 'table1id3', table1id3
) c (col, value);
See SQL Fiddle with Demo. This gives a result:
| COL | VALUE | ID |
---------------------------
| table1id1 | 1 | 1 |
| table1id2 | 2 | 1 |
| table1id3 | 3 | 1 |
| table1id1 | 2 | 2 |
| table1id2 | 3 | 2 |
| table1id3 | (null) | 2 |
| table1id1 | 3 | 3 |
Now that you have the data in rows, you can easily join on the value column to return the id:
select t1.description,
d.id
from table1 t1
inner join
(
select col, value, t2.Id
from table2 t2
cross apply
(
select 'table1id1', table1id1 union all
select 'table1id2', table1id2 union all
select 'table1id3', table1id3
) c (col, value)
) d
on t1.id = d.value
order by t1.description, d.id;
See SQL Fiddle with Demo
If you really want to use UNPIVOT, then you can use the following which doesn't join on each table multiple times to get the result:
select t1.description, t2.id
from table1 t1
inner join
(
select id, col, value
from
(
select id, [Table1ID1], [Table1ID2], [Table1ID3]
from table2
) d
unpivot
(
value for col in ([Table1ID1], [Table1ID2], [Table1ID3])
) unpiv
) t2
on t1.id = t2.value
order by t1.description, t2.id;
See SQL Fiddle with Demo.
The UNPIVOT and the CROSS APPLY is doing the same thing as a UNION ALL query:
select t1.description, t2.id
from table1 t1
inner join
(
select id, 'table1id1' col, table1id1 value
from table2
union all
select id, 'table1id2' col, table1id2
from table2
union all
select id, 'table1id3' col, table1id3
from table2
) t2
on t1.id = t2.value
order by t1.description, t2.id;
See SQL Fiddle with Demo
Microsoft SQL Server 2005 and higher support an UNPIVOT statement making the CROSS APPLY unnecessary.
SELECT Description AS [ID], Table1ID
FROM (SELECT Table1.Description, Table2_1.ID AS Table2ID_1, Table2_2.ID AS Table2ID_2, Table2.ID AS Table2ID_3
FROM Table1 LEFT OUTER JOIN
Table2 ON Table1.ID = Table2.Table1ID3 LEFT OUTER JOIN
Table2 AS Table2_2 ON Table1.ID = Table2_2.Table1ID2 LEFT OUTER JOIN
Table2 AS Table2_1 ON Table1.ID = Table2_1.Table1ID1) AS pvttbl
UNPIVOT ( Table1ID FOR ID IN (Table2ID_1, Table2ID_2, Table2ID_3)) AS unpvttbl
ORDER BY Description, Table1ID
See Using PIVOT and UNPIVOT on MSDN.