Can this query be written - what am I missing? - oracle

I am feeling stupid today but I have a query that I want to write but I am sensing that it might be out of my reach.
I will try to simplify the problem just for the sake of simplicity.
I have a table like this
[Table A]
ID | Class | Name
0 A Sarah
1 B Tom
2 C Bob
3 A Jen
4 A John
5 A Jack
6 B Name1
7 B Jack
8 B Bob
I need to get all the records (the actual records not grouped by) for all the people that share the same class, but with the following restrictions:
Only get me the records where Jen, Jack & John share the same class (only those 3 no others and no duplicates).
That means the records will come as sets of 3.
So in the above example I would get
3 A Jen
4 A John
5 A Jack
So far I've gotten:
SELECT Class, Name, COUNT(*)
FROM TableA
GROUP BY Class, Name
HAVING COUNT(*) = 3
But in addition to this not working correctly, I am unable to specify that the records must have the names Jen, Jack and John and nothing else.
EDIT:
I was able to do it using inner query but it is a bit slow..If anyone knows a more optimal way please let me know:
SELECT ....
FROM TableA t1
join TableA t2 on t1.class = t2.class
WHERE t1.name = 'Jen' AND t2.name = 'John'
AND t1.class IN(SELECT class FROM TableA t3 WHERE t3.name = 'Jack')))
I could probably have included t3 with the original query but I prefer this way for readability.
Not sure if this is the exact answer but the query I was looking for worked using this style and I tried to write the simple one using the same method for anyone stuck like I was.

If you're after the classes where your three people all shared the same class, even if there were other people attending, then you were pretty close with your query, except you were missing the WHERE clause to restrict the rows to the names that you were interested in.
Plus, seeing as you didn't want to collapse the rows ("the actual records, not grouped by"), then you're after an analytic COUNT, not an aggregate.
Therefore, what you're after would look something like:
WITH table_a AS (SELECT 0 ID, 'A' CLASS, 'Sarah' NAME FROM dual UNION ALL
SELECT 1 ID, 'B' CLASS, 'Tom' NAME FROM dual UNION ALL
SELECT 2 ID, 'C' CLASS, 'Bob' NAME FROM dual UNION ALL
SELECT 3 ID, 'A' CLASS, 'Jen' NAME FROM dual UNION ALL
SELECT 4 ID, 'A' CLASS, 'John' NAME FROM dual UNION ALL
SELECT 5 ID, 'A' CLASS, 'Jack' NAME FROM dual UNION ALL
SELECT 6 ID, 'B' CLASS, 'Name1' NAME FROM dual UNION ALL
SELECT 7 ID, 'B' CLASS, 'Jack' NAME FROM dual UNION ALL
SELECT 8 ID, 'B' CLASS, 'Bob' NAME FROM dual)
-- End of mimicking your table_a with data in it
-- See query below:
SELECT ID,
CLASS,
NAME
FROM (SELECT ID,
CLASS,
NAME,
COUNT(NAME) OVER (PARTITION BY CLASS) name_cnt
FROM table_a
WHERE NAME IN ('Jen', 'Jack', 'John'))
WHERE name_cnt = 3;
You'll note that analytic functions don't have an equivalent HAVING clause that aggregate functions do, which means that you have to do the filtering in an outer query. Hopefully you can see that my query is similar to yours, though?

Related

Write a query to display the student id and address for the student "David". Note : The student name can be in any case

data in student table:
STUDENT_NAME STUDENT_ID ADDRESS
------------ ----------- --------
Anandhi 1 4th street
Anitha 4 Cross cut
david 7 Main Cross Street
Kokila 8 Rao Street
Mithali 9 OMR road
I have tried the query:
select student_id,address from student where student_name='david';
Expected Output:
STDENT_ID ADDRESS
---------- ----------
7 Main Cross Street
I have tried the following code, it executes successfully and gives the desired result, however it fails to clear one test case and IDK why?
You may try lowercasing the student name column, then compare it to david in all lowercase.
SELECT ADDRESS
FROM student
WHERE LOWER(STUDENT_NAME) = 'david';
Are you sure all student names are stored in LOWERCASE?
Second, is it all rows in your question present in the table or is it a subset?
If the answer is NO and it is a subset
then try
SELECT student_id,address FROM student WHERE LOWER(student_name) = 'david';
And if you are not sure about leading and trailing spaces
then try
SELECT student_id,address FROM student WHERE TRIM(LOWER(student_name)) = 'david';
Actual Oracle versions currently supported (12.1.0.2 is EOL already) have collate clause, so since Oracle 12.2 you can use collate clause on different levels: Column level, table level, schema level, database level, session level, query level:
https://oracle-base.com/articles/12c/column-level-collation-and-case-insensitive-database-12cr2
For example, for your case:
SELECT student_id,address FROM student WHERE student_name collate BINARY_AI = 'david';
Full test case with test data and results:
--test data:
with STUDENT(STUDENT_NAME,STUDENT_ID,ADDRESS) as
(
select 'Anandhi' , 1, '4th street' from dual union all
select 'Anitha' , 4, 'Cross cut' from dual union all
select 'david' , 7, 'Main Cross Street' from dual union all
select 'Kokila' , 8, 'Rao Street' from dual union all
select 'Mithali' , 9, 'OMR road' from dual
) -- end of test data
-- main query:
SELECT student_id,address FROM student WHERE student_name collate BINARY_AI = 'david';
-- Results:
STUDENT_ID ADDRESS
---------- -----------------
7 Main Cross Street

BULK COLLECT INTO a UNION query into a table of objects

How can I collect into a table of objects, the values produced by a query that has a union in it as shown below
Select customer_name
from customer
where customer_id = 'xxx'
BULK COLLECT INTO customer_obj
UNION
Select customer_name
from customer
where customer_name like '%adam%'
the constraints above are completely made up.
The bulk collect clause comes right after the (first) select clause, before the (first) from clause. You have it in the wrong place.
It is not clear why you are using a union (although that by itself will not result in an error). Perhaps as an unintended consequence, you will get a list of distinct names, because that is what union does (as opposed to union all).
Other than that, as has been pointed out in a Comment already, you don't need union - you need an or in the where clause. But even if you modify your query that way, you still must move bulk collect to its proper place.
Another option would be to put your UNION into an inline view. For example,
SELECT cust.customer_name
BULK COLLECT
INTO customer_obj
FROM (
SELECT customer_name
FROM customer
WHERE customer_id = 'xxx'
UNION
SELECT customer_name
FROM customer
WHERE customer_name LIKE '%adam%'
) cust

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 use the union operator in 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.

Resources