Oracle SQL: Duplicate rows when trying to query data - sql

Table A:
Plant ID
Plant Name
Unit Name
Technology Type
1
ABC
Unit 1
SMR
1
ABC
Unit 2
Electrolysis
Table B:
Plant ID
Feedstock Type
1
Natural Gas
1
Water
select
a.PLANT_NAME,
a.UNIT_NAME,
b.FEEDSTOCK_TYPE,
a.TECHNOLOGY_TYPE
from
Table A a
inner join Table B b
on a.plant_ID = b.plant_ID
When I run the code, the result will be
Plant Name
Unit Name
Technology Type
Feedstock
ABC
Unit 1
SMR
Natural Gas
ABC
Unit 1
SMR
Water
ABC
Unit 2
Electrolysis
Natural Gas
ABC
Unit 2
Electrolysis
Water
But I expect the result to be
Plant Name
Unit Name
Technology Type
Feedstock Type
ABC
Unit 1
SMR
Natural Gas
ABC
Unit 2
Electrolysis
Water
The problem is I dont have a common primary key to link each unit to each feedstock. I only have one common plant id between the 2 tables. Any thoughts? Thanks in advance! :)

You have nothing to join on beyond the plant_id. If you want to "align" the rows, you can introduce a new column using row_number():
select a.PLANT_NAME, a.UNIT_NAME, b.FEEDSTOCK_TYPE, a.TECHNOLOGY_TYPE
from (select a.*,
row_number() over (partition by plant_id order by unit_name) as seqnum
from table_A a
) a join
(select b.*,
row_number() over (partition by plant_id order by feedstock_type) as seqnum
from table_B b
) b
on a.plant_ID = b.plant_ID and a.seqnum = b.seqnum;

You can't get it as there's no way to distinguish whether to take "Natural Gas" or "Water" as both values share the same PLANT_ID in table_B.
You can take one of them using an aggregate function, e.g. MAX, but that's just a workaround which doesn't return what you wanted anyway. For example (sample data in lines #1 - 9) (note MAX in line #12 which also requires the GROUP BY clause):
SQL> with
2 table_a (plantid, plant_name, unit_name, technology_type) as
3 (select 1, 'ABC', 'Unit 1', 'SMR' from dual union all
4 select 1, 'ABC', 'Unit 2', 'Electrolysis' from dual
5 ),
6 table_b (plantid, feedstock_type) as
7 (select 1, 'Natural Gas' from dual union all
8 select 1, 'Water' from dual
9 )
10 select a.plant_name,
11 a.unit_name,
12 max(b.feedstock_type) feedstock_type,
13 a.technology_type
14 from table_a a join table_b b on a.plantid = b.plantid
15 group by a.plant_name, a.unit_name, a.technology_type;
PLA UNIT_NAME FEEDSTOCK_TYPE TECHNOLOGY_TYPE
--- ---------- --------------- ---------------
ABC Unit 2 Water Electrolysis
ABC Unit 1 Water SMR
SQL>

There are no implicit values in a table, only explicit ones in columns. You appear to want to join "row 1" of table A to "row 1" of table B, and so on. In that case you need an explicit value 1, 2, ... in column in each table and join on that.

Related

How to write SQL to select a value in table B if not exists in table A otherwise select value in table A?

I want to get a default rate for activity or an override rate if one exists in another table. How can I write SQL for this?
I have this query but it produces an error "every derived table must have its own alias".
select A.id, rate from (
select
A.id, coalesce(B.rate, A.rate) as rate
from
A
left join B on B.id = A.id
);
Consider the following data
table user
user_id name
1 johnny
2 sam
table activity_types
activity_type_id description rate
1 cook steak $12.00
2 flip burgers $9.00
3 wait tables $8.00
4 wash dishes $8.00
table personal_override_rates
user_id activity_type_id rate
1 1 $18
table activities
activity_id user_id activity_type_id qty
1 1 1 1
2 1 2 1
3 2 1 1
4 2 2 1
desired result:
johnny cook steak 1 $18.00
johnny flip burgers 1 $9.00
sam cook steak 1 $12.00
sam flip burgers 1 $9.00
The error you get is because you have a derived table without an alias. You must provide one and then select the id and rate using the alias.
Like this:
select derived_table.id, derived_table.rate from (
select
A.id, coalesce(B.rate, A.rate) as rate
from
A
left join B on B.id = A.id
) as derived_table;

Jumbling the data within the table

I have a scenario, where i have to mask the data with data within the table
let's say I have a table student_details(ID, CODE, NAME)
1 A XYZ
2 A 123
3 A QWERTY
I want the output as
1 A QWERTY
2 A XYZ
3 A 123
I want the name to be within the name list in that table
for same id I Want different name which is in the table.
select * from emp_details order by dbms_random.value;
is giving some random names which are not in list.
Can any one help me with this?
Here's one option: recalculate the ID value using ROW_NUMBER analytic function which orders rows by the hash value over concatenated name, code and id columns (that's just for example; you can pick something different).
SQL> with test (id, code, name) as
2 (select 1, 'A', 'XYZ' from dual union all
3 select 2, 'A', '123' from dual union all
4 select 3, 'A', 'QUERTY' from dual
5 ),
6 inter as
7 (select row_number() over (order by ora_hash(name || code || id)) id,
8 code, name
9 from test
10 )
11 select t.id, t.code, i.name
12 from test t join inter i on t.id = i.id;
ID C NAME
---------- - ------
1 A XYZ
2 A QUERTY
3 A 123
SQL>
If you intend to permutate selected columns in your table and leave the rest of the table unchanged, you may use a join with a key permutation table.
Assume your data as follows:
ID CODE NAME
---------- ---- ------
1001 A XYZ
1002 B 123
1004 C QUERTY
1005 A FOO
Note, that the PK is not continuous, wich is the generall case. If you have the PK a continuous sequence starting with 1, you may even simplify the solution (as proposed in other answer).
First lets define the permutation table assigning to each PK a new key in random order.
create table PERM as
with rn as (
select
id,
row_number() over (order by id) rn,
row_number() over (order by dbms_random.value) rn_new
from student)
select a.ID, b.ID ID_NEW
from rn a
join rn b
on a.RN = b.RN_NEW;
ID ID_NEW
---------- ----------
1001 1004
1002 1001
1004 1005
1005 1002
The query defines two row_number sequences, first in the order of the PK, second in random order. The final join gets the original and new (permutated) IDs.
Now to permute a selected colums is as easy as to join your table twice with the permutation table in between and choose preserved columns from the first table, the permuted columns from the second one.
select a.ID, a.code, b.name
from student a
join PERM p on a.id = p.id
join student b on p.id_new = b.id
order by a.id;
ID CODE NAME
---------- ---- ------
1001 A QUERTY
1002 B XYZ
1004 C FOO
1005 A 123
As far as you preserv the permutation table you can reconstruct the former state, if you drop it, there is no way to get the original data.

Ranking of a tuple in another table

So I have 2 tables, team A and team B, with their score. I want the rank of the score of every member of team A within team B using SQL or vertica, as shown below
Team A Table
user score
-------------
asa 100
bre 200
cqw 50
duy 50
Team B Table
user score
------------
gfh 20
ewr 80
kil 70
cvb 90
Output:
Team A Table
user score rank in team B
------------------------------
asa 100 1
bre 200 1
cqw 50 4
duy 50 4
Try this - and this only works in Vertica.
INTERPOLATE PREVIOUS VALUE is an outer-join predicate specific to Vertica that joins two tables on non-equal columns, using the 'last known' value in the outer-joined table to make a match succeed.
WITH
-- input, don't use in query itself
table_a (the_user,score) AS (
SELECT 'asa',100
UNION ALL SELECT 'bre',200
UNION ALL SELECT 'cqw',50
UNION ALL SELECT 'duy',50
)
,
table_b(the_user,score) AS (
SELECT 'gfh',20
UNION ALL SELECT 'ewr',80
UNION ALL SELECT 'kil',70
UNION ALL SELECT 'cvb',90
)
-- end of input - start WITH clause here
,
ranked_b AS (
SELECT
RANK() OVER(ORDER BY score DESC) AS the_rank
, *
FROM table_b
)
SELECT
a.the_user AS a_user
, a.score AS a_score
, b.the_rank AS rank_in_team_b
FROM table_a a
LEFT JOIN ranked_b b
ON a.score INTERPOLATE PREVIOUS VALUE b.score
ORDER BY 1
;
a_user|a_score|rank_in_team_b
asa | 100| 1
bre | 200| 1
cqw | 50| 4
duy | 50| 4
Simple correlated query should do:
select
a.*,
(select count(*) + 1 from table_b b where b.score > a.score) rank_in_b
from table_a a;
All you need to do is count the number of people with more score than current user in the table b and add 1 to it to get the rank.

SQL - Find the Top Level Parent and Multiply Quantities

I have two tables which track part numbers as well as the hierarchy of assemblies.
Table: Config
ConfigNum AssemblyNum Qty
1 A 1
1 B 2
1 C 2
2 A 1
2 C 1
Table: SubAssembly
SubAssembly PartNum Qty
A AA 2
A BB 4
A CC 2
A DD 4
B EE 4
B FF 8
AA AAA 2
I would like to create a flat version of these tables which shows the ConfigNum (Top level parent) with all associated assembly and part numbers, for each ConfigNum in the Config table. The column Config.AssemblyNum is equivalent to SubAssembly.SubAssembly.
The Subassembly table shows the partent to child relation ship between parts. For example Assembly 'A' has a child assembly 'AA'. Since 'AA' exists in the SubAssembly Column is it self an assembly and as you can see it has a child part 'AAA'. 'AAA' does not exist in the SubAssemly columns therefore it is the last child in the series.
I would also like to have an accurate quantity count of each part based on multiplying of parent to child down the chain.
For example in the output:
(Total Qty of AAA) = (Qty A) x (Qty AA) x (Qty AAA)
4 = 1 x 2 x 2
Example Output table: (for Config 1)
ConfigNum SubAssembly PartNum TotalQty
1 A AA 2
1 A BB 4
1 A CC 2
1 A DD 4
1 B EE 8
1 B FF 16
1 A AAA 4
Any suggestion on how to complete this task would be greatly appreciated.
EDIT: I have been able to create this code based on suggestions made in the answers.
I am still having trouble getting the quantities to multiply down.
I have received the error "Types don't match between the anchor and the recursive part in column "PartQty" of recursive query "RCTE"."
;WITH RCTE (AssemblyNum, PartNum, PartQty, Lvl) AS
(
SELECT AssemblyNum, PartNum, PartQty, 1 AS Lvl
FROM SP_SubAssembly r1
WHERE EXISTS (SELECT * FROM SP_SubAssembly r2 WHERE r1.AssemblyNum = r2.PartNum)
UNION ALL
SELECT rh.AssemblyNum, rc.PartNum, (rc.PartQty * rh.PartQty), Lvl+1 AS Lvl
FROM dbo.SP_SubAssembly rh
INNER JOIN RCTE rc ON rh.PartNum = rc.AssemblyNum
)
SELECT CB.ID, CB.ConfigNum, CB.PartNum, CB.PartQty, r.AssemblyNum, r.PartNum, SUM(r.PartQty * COALESCE(CB.PartQty,1)) AS TotalQty
FROM SP_ConfigBOM CB
FULL OUTER JOIN RCTE r ON CB.PartNum = r.AssemblyNum
WHERE CB.ConfigNum IS NOT NULL
ORDER BY CB.ConfigNum
Thanks,
For this problem I think you must use a recursive query. In fact I think SubAssembly table should have some ProductID field other than SubAssembly to easily identify the main product that contains assemblies.
You can find a similar example in SLQ Server documentation.
Can check it here: http://rextester.com/FQYI80157
Change the Qty in Config table to change the final result.
create temp table t1 (cfg int, part varchar(10), qty int);
create temp table t2 (part varchar(10), sasm varchar(10), qty int);
insert into t1 values (1,'A',2);
insert into t2 values ('A','AA',2);
insert into t2 values ('A','BB',4);
insert into t2 values ('A','CC',2);
insert into t2 values ('A','DD',4);
insert into t2 values ('AA','AAA',2);
WITH cte(sasm, part, qty)
AS (
SELECT sasm, part, qty
FROM #t2 WHERE part = 'A'
UNION ALL
SELECT p.sasm, p.part, p.qty * pr.qty
FROM cte pr, #t2 p
WHERE p.part = pr.sasm
)
SELECT #t1.cfg, cte.part, cte.sasm, SUM(cte.qty * COALESCE(#t1.qty,1)) as total_quantity
FROM cte
left join #t1 on cte.part = #t1.part
GROUP BY #t1.cfg, cte.part, cte.sasm;
This is the result:
+------+------+----------------+
| part | sasm | total_quantity |
+------+------+----------------+
| A | AA | 4 |
+------+------+----------------+
| A | DD | 8 |
+------+------+----------------+
| AA | AAA | 4 |
+------+------+----------------+
| A | BB | 8 |
+------+------+----------------+
| A | CC | 4 |
+------+------+----------------+
insert into #Temp
SELECT A.[ConfigNum] ,
A.[AssemblyNum],
B.[PartNum],
A.[Qty],
A.QTY * B.Qty TotalQty
INTO #Temp
FROM [Config] A,
[SubAssembly] B
WHERE A.[AssemblyNum] = B.[SubAssembly]
SELECT A.[ConfigNum] ,
A.[AssemblyNum],
A.[PartNum],
A.[Qty],
A.TotalQty
FROM #Temp A
union
SELECT A.[ConfigNum] ,
A.[AssemblyNum],
B.[PartNum],
A.[Qty],
A.TotalQty * B.Qty
FROM #Temp A,
[SubAssembly] B
WHERE
A.[PartNum] = B.[SubAssembly]

Oracle sql union with no duplicate on a single column

In Oracle, is it possible to perform a union where the duplicate condition is on a single column rather than the entire row?
I have table Aand B that have 2 columns: item_name, price. I'd like to create a view that for certain item_names, it looks in table A to see if the item_name is present, and if so use the price in A, if not go to B and use the price in B, then union the rest of item_name in B that have not yet been added to the view.
For example,
Table A Table B
---------------- ----------------
item_name price item_name price
---------------- ----------------
shoe 10 shoe 8
socks 2 socks 4
shirt 5 t-shirt 3
gloves 1 glasses 15
pants 7
For shoe and socks I'd like to use table A's prices if available, and if not use table B. So in the end, my view should look like this:
View
-----------------------
item_name price source
-----------------------
shoe 10 A
socks 2 A
t-shirt 3 B
glasses 15 B
pants 7 B
I tried
select * from A a
where item_name in ('shoe', 'socks')
union
select * from B b
where b.item_name not in
(select item_name from A
where item_name in ('shoe', 'socks'))
Which I don't like because the query select * from A where item_name in ('shoe', 'socks') is duplicated. Is there a better/more efficient way of doing this?
I think you are looking for a join:
select coalesce(a.item_name, b.item_name) as item_name,
coalesce(a.price, b.price) as price,
(case when a.price is not null then 'A' else 'B' end) as source
from a full outer join
b
on a.item_name = b.item_name
Since you are using Oracle, I may suggest the following, it would do the trick
select NVL(A.ITEM_NAME,B.ITEM_NAME) AS ITEM_NAME,
NVL(A.PRICE,B.PRICE) AS PRICE
FROM A as a RIGHT JOIN B as b ON A.ITEM_NAME=B.ITEM_NAME
To understand why it works, simply try it without NVL, the resulting right join results
A_item A_price B_item B_price
shoe 10 shoe 8
socks 2 socks 4
(null) (null) glasses 15
(null) (null) t-shirt 3
(null) (null) pants 7
Since you do not want the null values from table A, use NVL
NVL has also equivalent functions in mysql/mssql etc
Try this,
create view viewname as (
select coalesce(a.item_name, b.item_name) as item_name,
coalesce(a.price, b.price) as price,
(case when a.item_name=b.item_name then 'A' else 'B' end) as source
from tablea a right outer join
tableb b
on a.item_name = b.item_name)
made slight change Gordon's ans