Complex SQL query to join two tables - oracle

Problem:
Given two tables: TableA, TableB, where TableA has a one-to-many relationship with TableB, I want to retrieve all records in TableB for where the search criteria matches a certain column in TableB and return NULL for the unique TableA records for the same attribute.
Table Structures:
Table A
ID(Primary Key) | Name | City
1 | ABX | San Francisco
2 | ASDF | Oakland
3 | FDFD | New York
4 | GFGF | Austin
5 | GFFFF | San Francisco
Table B
ATTR_ID |Attr_Type | Attr_Name | Attr_Value
1 | TableA | Attr_1 | Attr_Value_1
2 | TableD | Attr_1 | Attr_Value_2
1 | TableA | Attr_2 | Attr_Value_3
3 | TableA | Attr_4 | Attr_Value_4
9 | TableC | Attr_2 | Attr_Value_5
Table B holds attribtue names and values and is a common table used across multiple tables. Each table is identified by Attr_Type and ATTR_ID (which maps to the IDs of different tables).
For instance, the record in Table A with ID 1 has two attributes in Table B with Attr_Names: Attr_1 and Attr_2 and so on.
Expected Output
ID | Name | City | TableB.Attr_Value
1 | ABX | San Francisco | Attr_Value_1
2 | ASDF | Oakland | Attr_Value_2
3 | FDFD | New York | NULL
4 | GFGF | Austin | NULL
5 | GFFFF | San Francisco | NULL
Search Criteria:
Get rows from Table B for each record in Table A with ATTR_NAME Attr_1. If a particular TableA record doesn't have Attr_1, return null.
My Query
select id, name, city,
b.attr_value from table_A
join table_B b on
table_A.id =b.attr_id and b.attr_name='Attr_1'

This is a strange data structure. You need a left outer join with the conditions in the on clause:
select a.id, a.name, a.city, b.attr_value
from table_A a left join
table_B b
on a.id = b.attr_id and b.attr_name = 'Attr_1' and b.attr_type = 'TableA';
I added the attr_type condition, because that seems logic with this data structure.

I dont have an sql server to test the command, but what you want is an inner/outer join query. You could do something like this
select id, name, city,
b.attr_value from table_A
join table_B b on
table_A.id *= b.attr_id and b.attr_name *= 'Attr_1'
Something like this should do the trick for you

Related

PL/SQL Switching two columns from two tables

Suppose I have two tables (tblA and tblB) and want to switch the second column of each table (tblA.Grade and tblB.Grade) as shown:
+-------------------------------------+
| table a table b |
+-------------------------------------+
| name grade name grade |
| a 60 f 50 |
| b 45 g 70 |
| c 30 h 90 |
+-------------------------------------+
Now, I would like to switch the grade column from table a to table b and the the grade column from table b to table a. The result should look like this:
+-----------------------------------------+
| table a table b |
+-----------------------------------------+
| name grade name grade |
| a 50 f 60 |
| b 70 g 45 |
| c 90 h 30 |
+-----------------------------------------+
I have created the tables, loaded them into cursors using bulk collect and the following code to complete the transformation:
insert into tblA values('a',60);
insert into tblA values('b',45);
insert into tblA values('c',30);
insert into tblb values('f',70);
insert into tblb values('g',80);
insert into tblb values('h',90);
.
DECLARE
TYPE tbla_type IS TABLE OF tbla%ROWTYPE;
l_tbla tbla_type;
TYPE tblb_type IS TABLE OF tblb%ROWTYPE;
l_tblb tblb_type;
BEGIN
-- All rows at once...
SELECT *
BULK COLLECT INTO l_tbla
FROM tbla;
SELECT *
BULK COLLECT INTO l_tblb
FROM tblb;
DBMS_OUTPUT.put_line (l_tblb.COUNT);
FOR indx IN 1 .. l_tbla.COUNT
LOOP
DBMS_OUTPUT.put_line (l_tbla(indx).lname);
update tbla set grade = l_tblb(indx).grade
where l_tbla(indx).lname= tbla.lname;
update tblb set grade = l_tbla(indx).grade
where l_tblb(indx).lname= tblb.lname;
END LOOP;
END;
So, although I did the task, I am wondering if there is a more simple solution that I have not thought of?
Please let me know if anyone knows if there may be a more simple solution?
Note that there is nothing called first or second record in databases as there is no guarantee that the first record entered will be the first one returned. So there should always be an order by to decide first/second etc.
So assuming you want the records to be ordered by name and then swap grade of smallest name of first table with grade of smallest name of second table,
Now assuming you fix the order thingy in your existing code, and if it is working, I believe it would be faster than the way I would do it below. Something like
Create a temp table and put names and grade ordered by name.
Reason of using temp table is mostly because later if I want to correct or revert the data, I can use the same temp table to reverse the merge.
create table tmp1 as
with ta as
(select t.* ,
row_number() over (order by name) as rnk
from tblA t)
,tb as
(select t.* ,
row_number() over (order by name) as rnk
from tblb t)
select ta.name as ta_name,ta.grade as ta_grade,
tb.name as tb_name,tb.grade as tb_grade
from ta inner join tb
on ta.rnk=tb.rnk
Output of tmp1
+---------+----------+---------+----------+
| TA_NAME | TA_GRADE | TB_NAME | TB_GRADE |
+---------+----------+---------+----------+
| a | 60 | f | 70 |
| b | 45 | g | 80 |
| c | 30 | h | 90 |
+---------+----------+---------+----------+
Then use merge to swap value from tmp1.
merge into tbla t1
using tmp1 t
on (t1.name=t.ta_name)
when matched then update
set t1.grade=t.tb_grade;
merge into tblb t1
using tmp1 t
on (t1.name=t.tb_name)
when matched then update
set t1.grade=t.ta_grade;
If satisfied with result, drop the temp table later
drop table tmp1;

Hive How to get non-grouped columns for each group of grouped results?

I have a table similar to the following.
|name | grp | dt
------------------------------
|foo | A | 2016-01-01
|bar | A | 2016-01-02
|hai | B | 2016-01-01
|bai | B | 2016-01-02
|baz | C | 2016-01-01
For each group, I want to find the name whose dt is the most recent. In other words, MAX(dt), GROUP by grp, and associate the name whose dt is the max of the group to the output:
|name | grp | dt
------------------------------
|bar | A | 2016-01-02
|bai | B | 2016-01-02
|baz | C | 2016-01-01
In Oracle, the following query works and is very clean (taken from here):
SELECT o.name, o.grp, o.dt
FROM tab o
LEFT JOIN tab b
ON o.grp = b.grp AND o.dt < b.dt
WHERE b.dt IS NULL
However this fails with [Error 10017]: Line 4:43 Both left and right aliases encountered in JOIN 'service_effective_from' From another question quoting the documentation, I learn that I cannot use an inequality operator in a join statement:
Only equality joins, outer joins, and left semi joins are supported in Hive. Hive does not support join conditions that are not equality conditions as it is very difficult to express such conditions as a map/reduce job.
What is a clean solution for obtaining this in Hive, given that I cannot use an inequality operator in a join condition?
The following works and is taken from here, but I don't find it very clean:
SELECT o.name, ogrp, o.dt
FROM tab o
JOIN (
SELECT grp, MAX(dt) dt
FROM tab
GROUP BY grp
) b
ON o.grp = b.grp AND o.dt = b.dt
As an aside, it takes 164 seconds on my environment for a comparable test table with 4 rows.

Retrieve from Oracle db key value pair

I need to retrieve 3 values with different key from a key value pair table.
My database schema as follows. I need to reach to table3 from table1 by taking the E_SUBID and then joining the table2 with E_SUBID. Once table1 and table2 are joined I need take to take E_CID from table2 to join it with table2 E_CID to get the "Attr_Value" keeping E_CID as a criteria.
Table1
------------------------
|E_SUBID| B_LocationID |
|1 100 |
|2 101 |
|3 102 |
Table2
-----------------
|E_CID | E_SUBID|
|10 1 |
|11 2 |
|12 3 |
Table3
---------------------------------
|E_CID | Attr_name | Attr_Value |
|10 Product Samsung |
|10 Model Smartphone |
|10 usage daily |
|11 Product Apple |
|11 Model Ipad |
|11 usage everyday |
|12 Model smartwatch |
I have been successful to join table1,table2 and table3 but I cannot get the required output which as follows
OUTPUT
|Product | Model | Usage |
Samsung Smartphone daily
Apple Ipad everyday
null smartwatch null
The query which joins table1, table2 and table3 as follows
select distinct t3.Attr_value as Product
from table1 t1, table2 t2, table3 t3
where t1.E_SUBID = t2.E_SUBID and
t2.E_CID = t3.E_CID and
t3.Attr_name=?????
order by Product;
Thank you for your time.
In a case like this, you can join to table3 as often as you need to for each attribute name you wish to display:
select
p.attr_value product,
m.attr_value "model", -- Quotes to escape reserved word
u.attr_value usage
from table1 t1
join table2 t2 on t1.e_subid = t2.e_subid
left outer join table3 p on t2.e_cid = p.e_cid and p.attr_name = 'Product'
left outer join table3 m on t2.e_cid = m.e_cid and m.attr_name = 'Model'
left outer join table3 u on t2.e_cid = u.e_cid and u.attr_name = 'Usage'
order by 1;
Edit
Based on the comment, by making table3 optional (outer join) the query should return all rows and whether or not a Model or Usage or Product has been defined.
Try as below ... Basically you are trying to transpose the rows to column in table3.
Select Product, "Model", Usage
From
(
Select
t1.E_SUBID,
t2.E_CID,
Max(Case when T3.Attr_name = 'Product' Then T3.Attr_Value else null end) Product,
max(Case when T3.Attr_name = 'Model' Then T3.Attr_Value else null end) Model,
max(Case when T3.Attr_name = 'Usage' Then T3.Attr_Value else null end) Usage
From Table1 t1,
Table2 t2,
Table3 t3
Where
t1.E_SUBID = t2.E_SUBID
and t2.E_CID = t3.E_CID
group by t1.t1.E_SUBID,t2.E_CID
);

Oracle compare two count() different tables

I have a problem with a query, see I have two tables, let say:
table a:
progid | name | type
12 | john | b
12 | anna | c
13 | sara | b
13 | ben | c
14 | alan | b
15 | george| b
table b:
progid | name | type
12 | john | b
12 | anna | c
13 | sara | b
14 | alan | b
15 | george| b
table a gets count
progid | count(*)
12 | 2
13 | 2
14 | 1
15 | 1
table b gets
progid | count(*)
12 | 2
**13 | 1**<-this is what I want to find different count
14 | 1
15 | 1
What I want is to find which progid in table b aren't in table a by count, (because as you can see the prog id is there but they should be there the same times! So ben is gone but the progid 13 is there)
So I want to get progid where count varies in the tables, I tried:
select a.progid from
(select progid ,count(*) total from tablea group by progid) a,
(select progid ,count(*) total from tableb group by progid) b
where
a.progid=b.progid and a.total<>b.total;
I get b.total invalid identifier
if I use a.count(progid)<>b.count(progid)
Error says can't use group functions there, any ideas? I'm desperate!
ok i've checked your answers and here's the original one
select a.beneficiarioid from
(select beneficiarioid,count(*) total from lmml_ejercicio_2012_3 where programaid=61 group by beneficiarioid order by beneficiarioid) a,
(select beneficiarioid,count(*) total from ejercicio_2012_3 where programaid=61 group by beneficiarioid order by beneficiarioid) where
a.beneficiarioid=b.beneficiarioid and a.total<>b.total;
anyway, i'll try your querys and let you know!! thank you very much!!
btw it's Oracle 11g
You should be able to use a subquery to get each count and then join them using a FULL OUTER JOIN:
select coalesce(a.progId, b.progId) progid,
coalesce(a.atotal, 0) atotal,
coalesce(b.btotal, 0) btotal
from
(
select progid, count(*) aTotal
from tablea
group by progId
) a
full outer join
(
select progid, count(*) bTotal
from tableb
group by progId
) b
on a.progid = b.progid
where coalesce(a.atotal, 0) <> coalesce(b.btotal, 0);
See SQL Fiddle with Demo. I used a FULL OUTER JOIN in the event you have rows in one table that do not exist in the other table.
Even though your query works fine on my database, I would prefer set operation:
(select progid ,count(*) total from tablea group by progid)
minus
(select progid ,count(*) total from tableb group by progid)

How to make an efficient UNION in Oracle?

I'm using Oracle 11g.
I have 2 related tables: stored values (A) and new values to insert (B). Both are related between them with an id of 3 columns (client, group and personcode). Each table has about 20 other columns (let's call them attributes).
I have to match them so I can know which values are new (id in B and not in A) so I insert them in A, which are equals (id in B and in A with the same attributes) and which are not in the new values (id in A but not in B anymore), so I delete them from the stored values (A).
For instance:
A:
client | group | personcode | name | surname
_________________________________________________
1 | 1 | 1 | joe | doe
1 | 1 | 2 | carl | john
1 | 1 | 3 | john | john
B:
client | group | personcode | name | surname
_________________________________________________
1 | 1 | 1 | joe | doe
1 | 1 | 3 | john | john
1 | 1 | 4 | mary | con
In this example, person 4 is new, person 2 should be deleted and 1 and 3 remains the same.
So, I need a query which returns the following results:
client | group | personcode | action
_________________________________________
1 | 1 | 1 | equal
1 | 1 | 2 | remove
1 | 1 | 3 | equal
1 | 1 | 4 | new
What I've made is the following query:
WITH
A AS (
-- select from A table
),
B AS
(
-- select from B table
),
delete AS
(
-- select from A WHERE NOT EXISTS (B.id = A.ID)
),
news AS
(
-- select from B WHERE NOT EXISTS (A.id = B.ID)
),
eq AS
(
-- select A.* from A, B WHERE A.id = B.id AND A.attributes = B.attributes
)
select action.client, action.group, action.personcode, 'remove' from delete action
UNION ALL
select action.client, action.group, action.personcode, 'new' from news action
UNION ALL
select action.client, action.group, action.personcode, 'equal' from eq action
;
The problem is that, although each of those 3 lasts selects runs in less than 10 seconds, when I merge them using UNION or UNION ALL, the complete query lasts about 90 seconds, even if delete or new or equal are empty. It could be more than 3000 rows in A or in B.
Is there any way to get this results in a better, faster way?
You could outer join the tables to produce a log of the differences between them.
select coalesce(a.id,b.id) id,
case when a.id is null
then 'new'
when b.id is null
then 'remove'
when a.col1 = b.col1 and a.col2 = b.col2 ...
then 'same'
else 'different'
end
from a full outer join b on (a.id = b.id)
If the table B has the data that you want, why do you not use that table instead of that in table A? Create a synonym that points to the one with the correct data in it and reference that.
Well, thanks all for your reply.
I've finally made a view to which I pass some parameters to filter the first two queries, using the strategy described in this blog
The complete process lasts 30 secs now, and 0 if there are no rows at A or B (before, it lasts 90 secs always).
This is the solution which less affects my current procedures.

Resources