Compare table content - oracle

I have 2 tables and I need to do a table compare:
TABLE A
LABEL
VALUE
TABLE B
LABEL
VALUE
Basically I want:
Records in where the values are not equal on matching labels
Records in TABLE A that are not in TABLE B
Records in TABLE B that are not in TABLE A
With that information, I can record the proper historical data I need to. It will show me where the value has changed, or where a label was added or deleted......you can say TABLE A is the "new" set of data, and TABLE B is the "old" set of data. So I can see what is being added, what was deleted, and what was changed.
Been trying with UNION & MINUS, but no luck yet.
Something like:
A LABEL A VALUE B LABEL B VALUE
---------------------------------------
XXX 5 XXX 3
YYY 2
ZZZ 4
WWW 7 WWW 8
If the labels and values are the same, I do not need them in the result set.

Here is one way (and possibly the most efficient way) to solve this problem. The main part is the subquery that does a UNION ALL and GROUP BY on the result, keeping only groups consisting of a single row. (The groups with two rows are those where the same row exists in both tables.) This method was invented by Marco Stefanetti - first discussed on the AskTom discussion board. The benefit of this approach - over the more common "symmetric difference" approach - is that each base table is read just once, not twice.
Then, to put the result in the desired format, I use a PIVOT operation (available since Oracle 11.1); in earlier versions of Oracle, the same can be done with a standard aggregate outer query.
Note that I modified the inputs to show the handling of NULL in the VALUE column also.
Important: This solution assumes LABEL is primary key in both tables; if not, it's not clear how the required output would even make sense.
with
table_a ( label, value ) as (
select 'AAA', 3 from dual
union all select 'CCC', null from dual
union all select 'XXX', 5 from dual
union all select 'WWW', 7 from dual
union all select 'YYY', 2 from dual
union all select 'HHH', null from dual
),
table_b ( label, value ) as (
select 'ZZZ', 4 from dual
union all select 'AAA', 3 from dual
union all select 'HHH', null from dual
union all select 'WWW', 8 from dual
union all select 'XXX', 3 from dual
union all select 'CCC', 1 from dual
)
-- End of test data (NOT PART OF THE SOLUTION!) SQL query begins below this line.
select a_label, a_value, b_label, b_value
from (
select max(source) as source, label as lbl, label, value
from (
select 'A' as source, label, value
from table_a
union all
select 'B' as source, label, value
from table_b
)
group by label, value
having count(*) = 1
)
pivot ( max(label) as label, max(value) as value for source in ('A' as a, 'B' as b) )
;
Output:
A_LABEL A_VALUE B_LABEL B_VALUE
------- ------- ------- -------
YYY 2
CCC CCC 1
WWW 7 WWW 8
ZZZ 4
XXX 5 XXX 3

Related

Oracle ordering with IN clause [duplicate]

Is it possible to keep order from a 'IN' conditional clause?
I found this question on SO but in his example the OP have already a sorted 'IN' clause.
My case is different, 'IN' clause is in random order
Something like this :
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
I would like to retrieve results in (45,2,445,12,789) order. I'm using an Oracle database. Maybe there is an attribute in SQL I can use with the conditional clause to specify to keep order of the clause.
There will be no reliable ordering unless you use an ORDER BY clause ..
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
order by case TestResult.SomeField
when 45 then 1
when 2 then 2
when 445 then 3
...
end
You could split the query into 5 queries union all'd together though ...
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField = 4
union all
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField = 2
union all
...
I'd trust the former method more, and it would probably perform much better.
Decode function comes handy in this case instead of case expressions:
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
ORDER BY DECODE(SomeField, 45,1, 2,2, 445,3, 12,4, 789,5)
Note that value,position pairs (e.g. 445,3) are kept together for readability reasons.
Try this:
SELECT T.SomeField,T.OtherField
FROM TestResult T
JOIN
(
SELECT 1 as Id, 45 as Val FROM dual UNION ALL
SELECT 2, 2 FROM dual UNION ALL
SELECT 3, 445 FROM dual UNION ALL
SELECT 4, 12 FROM dual UNION ALL
SELECT 5, 789 FROM dual
) I
ON T.SomeField = I.Val
ORDER BY I.Id
There is an alternative that uses string functions:
with const as (select ',45,2,445,12,789,' as vals)
select tr.*
from TestResult tr cross join const
where instr(const.vals, ','||cast(tr.somefield as varchar(255))||',') > 0
order by instr(const.vals, ','||cast(tr.somefield as varchar(255))||',')
I offer this because you might find it easier to maintain a string of values rather than an intermediate table.
I was able to do this in my application using (using SQL Server 2016)
select ItemID, iName
from Items
where ItemID in (13,11,12,1)
order by CHARINDEX(' ' + Convert("varchar",ItemID) + ' ',' 13 , 11 , 12 , 1 ')
I used a code-side regex to replace \b (word boundary) with a space. Something like...
var mylist = "13,11,12,1";
var spacedlist = replace(mylist,/\b/," ");
Importantly, because I can in my scenario, I cache the result until the next time the related items are updated, so that the query is only run at item creation/modification, rather than with each item viewing, helping to minimize any performance hit.
Pass the values in via a collection (SYS.ODCINUMBERLIST is an example of a built-in collection) and then order the rows by the collection's order:
SELECT t.SomeField,
t.OtherField
FROM TestResult t
INNER JOIN (
SELECT ROWNUM AS rn,
COLUMN_VALUE AS value
FROM TABLE(SYS.ODCINUMBERLIST(45,2,445,12,789))
) i
ON t.somefield = i.value
ORDER BY rn
Then, for the sample data:
CREATE TABLE TestResult ( somefield, otherfield ) AS
SELECT 2, 'A' FROM DUAL UNION ALL
SELECT 5, 'B' FROM DUAL UNION ALL
SELECT 12, 'C' FROM DUAL UNION ALL
SELECT 37, 'D' FROM DUAL UNION ALL
SELECT 45, 'E' FROM DUAL UNION ALL
SELECT 100, 'F' FROM DUAL UNION ALL
SELECT 445, 'G' FROM DUAL UNION ALL
SELECT 789, 'H' FROM DUAL UNION ALL
SELECT 999, 'I' FROM DUAL;
The output is:
SOMEFIELD
OTHERFIELD
45
E
2
A
445
G
12
C
789
H
fiddle

How to execute stored procedure cursor records parallelly

I have a stored procedure which does following tasks on 2 tables related to each other as below
CREATE TABLE address (adr_id, ver_id, address) AS
SELECT 1, 1, 'newYork' FROM DUAL UNION ALL
SELECT 1, 2, 'newYork' FROM DUAL UNION ALL
SELECT 1, 3, 'newYork' FROM DUAL UNION ALL
SELECT 4, 1, 'Washington' FROM DUAL UNION ALL
SELECT 4, 2, 'Washington' FROM DUAL;
CREATE TABLE employee (emp_id,adr_id,ver_id,) AS
SELECT 100,1, 1 FROM DUAL UNION ALL
SELECT 200,1, 2 FROM DUAL UNION ALL
SELECT 300,1, 3 FROM DUAL UNION ALL
SELECT 400,4, 1 FROM DUAL UNION ALL
SELECT 500,4, 2 FROM DUAL;
Here Following tasks are done in stored procedure and required to be executed in parallel as to increase throughput on billions of records in both table
Note: Foreign key Constraints are relaxed
Find records with distinct address "newYork" and have them in cursor
Loop on Cursor record as follows
and process each address record select * from address where address ="newYork"
Insert a new record in Address table with same address as first record (with ver_id=0) in #3 and take the newly inserted id
Find parent table records based on foreign key in Employee table and update them with newly id mentioned in #4.
delete all records of #3
Since Step 2 to 6 can be executed in parallel just wanted to know how this can be achieved ?
Expected After
Address
adr_id
ver_id
address
11
0
newYork
12
0
Washington
Employee
id
adr_id
ver_id
100
11
1
200
11
2
300
11
3
400
12
1
500
12
2
As a frame challenge, do not update the adr_id and just change the version; that way you do not need to change the referential constraints.
MERGE INTO address dst
USING (
SELECT ROWID AS rid,
COUNT(*) OVER (PARTITION BY adr_id) AS cnt,
ROW_NUMBER() OVER (PARTITION BY adr_id ORDER BY ver_id) AS rn
FROM address
) src
ON (dst.ROWID = src.rid AND src.cnt > 1)
WHEN MATCHED THEN
UPDATE
SET ver_id = 0
DELETE WHERE src.rn > 1
fiddle

Oracle 11g - How to calculate the value of a number in range minimum or max

i need a help to get solution to my problem, Please.
I have a table like this :
ID Number
|6 |20.90 |
|7 |45.00 |
|8 |52.00 |
|9 |68.00 |
|10 |120.00 |
|11 |220.00 |
|12 |250.00 |
The first range is 0 - 20.90.
When the value is in the half, the value id is for the max range.
When i got value 20.91, i want to get "ID = 6".
If the value is 31.00, i want to get "ID = 6"
If the value is
33.95, i want to get "ID = 7".
if the value is 44.99, i want to get ID = 7
How i can do it? Is there a function that will do what I need?
If you want the record with a number that is closest to your input, then you can use this:
select *
from (
select *
from mytable
order by abs(number - my_input_number), id
)
where rownum < 2
The inner query selects all records, but orders them by the distance they have from your input number. This distance can be calculated with number - my_input_number. But that could be negative, so we take the absolute value of that. This result is not output; it is just used to order by. So records with smaller distances will come first.
Now we need just the first of those records, and that is what the outer query does with the typical Oracle reserved word rownum: it represents a sequence number for every record of the final result set (1, 2, 3, ...). The where clause will effectively filter away all records we do not want to see, leaving only one (with smallest distance).
As mathguy suggested in comments, the order by now also has a second value to order by in case the input value is right at the mid point between the two closest records. In that case the record with the lowest id value will be chosen.
This is a good illustration of the power of analytic functions:
with mytable ( id, value ) as (
select 6, 20.90 from dual union all
select 7, 45.00 from dual union all
select 8, 52.00 from dual union all
select 9, 68.00 from dual union all
select 10, 120.00 from dual union all
select 11, 220.00 from dual union all
select 12, 250.00 from dual
),
inputs ( x ) as (
select 0.00 from dual union all
select 20.91 from dual union all
select 31.00 from dual union all
select 33.95 from dual union all
select 44.99 from dual union all
select 68.00 from dual union all
select 32.95 from dual union all
select 400.11 from dual
)
-- End of test data (not part of the solution). SQL query begins BELOW THIS LINE
select val as x, new_id as closest_id
from (
select id, val,
last_value(id ignore nulls) over (order by val desc) as new_id
from (
select id, (value + lead(value) over (order by value))/2 as val
from mytable
union all
select null, x
from inputs
)
)
where id is null
order by x -- if needed
;
Output:
X CLOSEST_ID
------ ----------
0 6
20.91 6
31 6
32.95 6
33.95 7
44.99 7
68 9
400.11 12

Oracle SQL Merge Multiple rows With Same ID But Out of Order Identifiers

I am trying to create a distinct list of parts to do analysis on in a table. The table contains a column of Part IDs and a column of Identifiers. The Identifiers are separated within the same entry by pipes but unfortunately the Identifiers are out of order. I'm not sure if this is possible but any help would be greatly appreciated!
For example (currently Both ID and Identifiers are VARCHAR2)
ID Identifiers
1 |1|2|
1 |2|1|
2 |3|A|1|B|
2 |B|1|3|A|
3 |1|3|2|
3 |1|5|
3 |2|1|3|
4 |AA|BB|1|3A|
4 |1|3A|AA|BB|
and I need the query to return
ID Identifiers
1 |1|2|
2 |3|A|1|B|
3 |1|5|
3 |1|3|2|
4 |1|AA|BB|3A|
It does not matter what specific order the identifiers are ordered in as long as all contents within that identifier are the same. For example, |1|5| or |5|1| doesn't matter but I need to see both entries |1|5| and |1|3|2. My original thought was to create separate out the identifiers into separate columns and then alphabetically concatenate back into one column but i'm not sure...thanks in advance!
Something like this (assuming there are no duplicate rows in the input table - if there are, the solution needs to be modified a bit).
In the solution I build the test_table for testing (it is not part of the solution), and I build another factored subquery in the WITH clause. This works in Oracle 11 and above. For earlier versions of Oracle, the subquery defined as prep needs to be moved as a subquery within the final query instead.
with
test_table ( id, identifiers ) as (
select '1', '|1|2|' from dual union all
select '1', '|2|1|' from dual union all
select '2', '|3|A|1|B|' from dual union all
select '2', '|B|1|3|A|' from dual union all
select '3', '|1|3|2|' from dual union all
select '3', '|1|5|' from dual union all
select '3', '|2|1|3|' from dual union all
select '4', '|AA|BB|1|3A|' from dual union all
select '4', '|1|3A|AA|BB|' from dual
),
prep ( id, identifiers, token ) as (
select id, identifiers, regexp_substr(identifiers, '[^|]+', 1, level)
from test_table
connect by level <= regexp_count(identifiers, '\|') - 1
and prior identifiers = identifiers
and prior sys_guid() is not null
)
select distinct id,
'|' || listagg(token, '|') within group (order by token) || '|'
as identifiers
from prep
group by id, identifiers
order by id, identifiers -- ORDER BY is optional
;
Output:
ID IDENTIFIERS
--- --------------------
1 |1|2|
2 |1|3|A|B|
3 |1|2|3|
3 |1|5|
4 |1|3A|AA|BB|
5 rows selected.

Oracle- Multiple rows in single row with adding column of duplicate value

Id Field_id Field_value
------------------------------
1 10 'A'
1 11 'B'
1 12 'C'
I want to make rows like
Id Field_id Field_value data_1 data_2
--------------------------------------
1 10 'A' 'B' 'C'
Pl help.
Take a look at this:
with t (Id, Field_id, Field_value) as (
select 1, 10, 'A' from dual union all
select 1, 11, 'B' from dual union all
select 1, 12, 'C' from dual
)
select FIELD_ID1, "10_FIELD_ID", "11_FIELD_ID","12_FIELD_ID"
from (select id, field_id, min(field_id) over() field_id1, field_value from t)
pivot (
max(field_value) field_id
for field_id in (10, 11, 12)
)
FIELD_ID1 10_FIELD_ID 11_FIELD_ID 12_FIELD_ID
---------------------------------------------------
10 A B C
Read more about pivot here
Normally, people look for PIVOT query, however, your input data is not at all duplicate as already mentioned in comments.
What you could do is, using subquery factoring, select id, 10 as field_id, field_value, thus making field_it static value as 10. Then use LISTAGG. But, the grouped rows would be a single column as delimeter seperated values.
I hope your requirement is exactly what you stated and not a typo or a mistake while posting.

Resources