How to use the union operator in oracle - oracle

I know that the union operator is used to (for example) return all rows from two tables after eliminating duplicates. Example:
SELECT a_id
FROM a
UNION
SELECT b_id
FROM b;
The result of listing of all elements in A and B eliminating duplicates is {1,2,3,4,5,6,7,8}.
If you joined A and B you would get only {4,5}. You would have to perform a full outer join to get the full list of 1-8. My question is if I wanted to use the union operator to display from a table called employees, the employee_id and job_id ( employee id being a number data type, and job_id being a VARCHAR2 data type) How would I go about doing this?
Would it be something like this: This does not run in oracle obviously,
SELECT employee_id
UNION
SELECT job_id
FROM employees;

If you really wanted to union together all the EMPLOYEE_IDs followed by all the JOB_IDs you'd use
SELECT TO_CHAR(EMPLOYEE_ID) FROM EMPLOYEES
UNION ALL
SELECT JOB_ID FROM EMPLOYEES
If you had rows with EMPLOYEE_IDs of 1, 2, and 3, and those same rows had JOB_IDs of 1, 11, and 111 you'd get a result set of six rows with a single column which would have values of
1
2
3
1
11
111
By using UNION ALL Oracle will allow the duplicates to pass through.
Share and enjoy.

Related

Compare table content

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

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.

How to write a query to select only groups where certain values are present in all rows?

I need to write a query for Oracle 11g to select only the groups which are highlighted in the image here. Specifically, there are 3 columns -- A, B, and C. Group 1 contains a null value in column C, group 2 contains several nulls in column C.Group 3 does not contain any null values in column C. Group 4 yes contains some null values in column C, and Group 5 does NOT contain any null values in column C.
I need to selects only the groups of rows that do NOT contain any null values in column C -- that would be group 3 rows and group 5 rows. I only need the group number, like return only a 3 and a 5 What SQL functions can I use to write this query? What does this query look like?
something like (pseudo code)
select colA, count(*) As cnt From tblX join to itself on something
Where ...something ...
Group By colA
(or we could skip the grouping and just select 3 and 5 or the three 3's and the three 5's)
The aggregate function COUNT(colname) would only count non-NULL values, so something like this might work:
select A from yourtable
group by A
having count(A) = count(C)
assuming A is never NULL.
You can use a not in clause and subquery
select colA, count(*) As cnt
From tblX
Where colA not in (select distinct colA from tblx where colC is null)
Group By colA

how to combine multiple rows into one row in table in oracle and store result in other table

sample data: table consists of 96000 rows and i want to combine them into 16000 rows
row 1:1255467861 40825825 IDF+0149502016010615210300000396000026+0000651+ 00000000000000+|
row 2:000000000+0000000+0000000+000000677+02 YY 0444410100000 00 0001000000054+10001EB4200002+00000+0000052+0000000+0000000+|
row 3:00000 00000+00000+0000000+0000000+0000000+00000 00000+00000+0000000+0000000+0000000+00000 00000+00000+0000000+0000000+|
row 4:0000000+00000 00000+00000+0000000+0000000+0000000+00 004 1 000000000000 0000000000 M5|
row 5: 00000000 +00000000000000000000000000000000000000+00000000001011 Y 000000000+|
row 6:0000000+0000000+0000000+AB0002210000000000FIABMM81 15067195 0000000000009403228870|
Assuming that you know what the criteria (column1, column2 in the example) for grouping your rows is, I suggest you to use aggregate functions functions such as SUM(), MAX(), and COUNT() to aggregate data, Group by the criteria, and insert into the target table.
E.G.
insert into my_target_table
Select column1, column2, sum(column3), max(column4), count(1) as column5
from my_source_table
group by column_1, column2
Assuming "row 1, row 2, row 3" has some meaning, you must have another column somewhere that shows the order. In my setup below, I assume you have an "id" (not necessarily consecutive numbers starting from 1; it can be number, or it can be date, etc. - for illustration I use some random numbers, they can also be fractional, or negative etc. - I build "row numbers" form that). If you don't have ANY kind of ordering, in the "order by" clause for the row_number() function you can simply "order by 1". However, I assume you also want to capture the minimum value of the "id" from each group of six (again, the id may be a date or timestamp or whatever).
The strategy is to create "row numbers". It works better if everything starts from 0, so I subtract 1 from the row_number() values. Then group by trunc(rn/6) and order by mod(rn, 6).
To concatenate the strings use listagg. I assume you wanted the strings concatenated with NO separator between them; if you do want them separated, for example, by #, then change the second argument to listagg from '' to '#'.
Query (including setup data, I shortened the input strings and I added a few more to show how everything works):
with test_data(id, input_str) as (
select 1, '12554861 40825825 IDF+0140000000+|' from dual union all
select 2, '0000000+00052+0000000+0000000+|' from dual union all
select 4, '00000 000+00000+0+00000+000+0000000+|' from dual union all
select 9, '00000+0000000+000000000000 M5|' from dual union all
select 14, '000000 +000000000011 Y 000000000+|' from dual union all
select 23, '000000IABMM81 15067195 0000003228870|' from dual union all
select 31, '125508825 IDF+0143333300000+|' from dual union all
select 32, '0000+052+0000AXZC000+0000000+|' from dual union all
select 37, '000MMDOQ000+0+00000+0000000+0000000+|' from dual union all
select 45, '000000 +00000000001011 Y 000000000+|' from dual union all
select 46, '00000+0000FIAB1 15067195 000CCV70|' from dual
),
has_rn (id, input_str, rn) as (
select id, input_str, row_number() over (order by id) - 1
from test_data
)
select min(id) as id,
listagg(input_str, '') within group (order by mod(rn, 6)) as output_str
from has_rn
group by trunc(rn/6);
Output (using the sample inputs in the test_data cte):
ID OUTPUT_STR
-- -------------------------------------------------------------------------------------------------------------------
1 12554861 40825825 IDF+0140000000+|0000000+00052+0000000+0000000+|00000 000+00000+0+00000+000+0000000+|00000+0000000+000000000000 M5|000000 +000000000011 Y 000000000+|000000IABMM81 15067195 0000003228870|
31 125508825 IDF+0143333300000+|0000+052+0000AXZC000+0000000+|000MMDOQ000+0+00000+0000000+0000000+|000000 +00000000001011 Y 000000000+|00000+0000FIAB1 15067195 000CCV70|

Get the results sorted in the same order as values inside the in() field of select

Is there is a way to order by the order of the values in an IN() clause?
I have a select query:
Select * from abc where xyz in (a list of values).
I want the result to be sorted in the same order as the list inside the bracket.
One way is that I can put the values in a temp table with an increasing sequence and then join the 2 tables, and then order by the sequence, but this is not a good way.
Is there a way to do this?
No need for a temp table (but not really pretty either)
with list_values (seqnr, id) as (
select 1, 42 from dual
union all
select 2, 43 from dual
union all
select 3, 44 from dual
-- you get the picture
)
select *
from abc
join list_values lv on abc.xyz = lv.id
order by lv.seqnr
One ugly option is to use DECODE:
Select * from abc
WHERE xyz in (a list of values)
ORDER BY DECODE(xyz, 'val1', 1, 'val2', 2, ...)
Thanks for all the answers.
There is another approach, similar to a_horse_with_no_name's approach:
with t as
(select t.*, rownum r from table (sys.odcinumberlist(val1, val2, val3...)) t)
select * from abc ac, t where ac.id = column_value and is_active = 'Y' order by r
This should work too. Which one do you guys think is the best and optimal way to do this?

Resources