Subquery Inside Mega Query - oracle

I have searched, but can find no example that fits what I need. Perhaps I am lost in the many joins of my query...
I am returning data from three Oracle 11g tables - ATE_TESTS, ATE_DATA, and TM_CONDITION_DYNAMIC. The query has other tables to join these. In fact, there are no less than 7 Joins.
ATE_DATA may have multiple records on the Many side of a join, but I want only the last-written row. ATE_DATA has an incremented Primary Key which I would like to use in the Aggregate Function MAX(DATA_PK) within a subquery. I think it should be in the WHERE clause of the main query, but I do not know how to implement this and there may be a better way.
Perhaps I might be educated?
My query is:
SELECT ate_serial, data_data, dyn_value
FROM ate_tests
LEFT JOIN ate_test_procedure
ON ate_tests.ate_pk = ate_test_procedure.proc_ate_test_fk
LEFT JOIN ate_test_data
ON ate_test_procedure.proc_pk = ate_test_data.data_ate_test_procedure_fk
LEFT JOIN tm_test_procedure
ON ate_test_procedure.proc_test_procedure = tm_test_procedure.proc_pk
LEFT JOIN tm_test_specification
ON ate_test_data.data_specification = tm_test_specification.spec_pk
LEFT JOIN tm_test_condition_dynamic
ON tm_test_specification.spec_condition_set_fk = tm_test_condition_dynamic.dyn_condition_set_fk
LEFT JOIN tm_test_sequences
ON ate_tests.ate_sequence_fk = tm_test_sequences.seq_pk
LEFT JOIN lu_tm_products_model
ON tm_test_sequences.seq_model = lu_tm_products_model.lumod_pk
WHERE upper(spec_name) = 'POWER'
AND lumod_model = 'AMP'
AND dyn_value = '136'
AND ate_yield = 1
AND upper(proc_procedure_name) = 'FINAL TEST'
AND proc_report = 1
AND proc_status = 1
ORDER BY ate_serial, dyn_value

... but I want only the last-written row.
It's typical top-n query. Let's say table a contains info about vegetables, table b their historical prices in different shops and s info about these shops. You are interested only in last price,
which you can find using the function row_number() (or max() ... keep dense rank last...).
with a as (select 1 vid, 'Tomato' name from dual union all
select 2 vid, 'Potato' name from dual union all
select 3 vid, 'Garlic' name from dual),
b as (select 1 pid, 1 vid, 1 sid, 11.5 price from dual union all
select 2 pid, 3 vid, 1 sid, 31.8 price from dual union all
select 3 pid, 1 vid, 1 sid, 13.2 price from dual union all
select 4 pid, 1 vid, 2 sid, 12.7 price from dual ),
s as (select 1 sid, 'Best Vegetables' name from dual union all
select 2 sid, 'Organic Products' name from dual)
select a.vid, a.name, s.name as shop, p.price as last_price
from a
left join (select vid, sid, price
from (select vid, sid, price,
row_number() over (partition by vid order by pid desc) rn
from b)
where rn = 1) p
on p.vid = a.vid
left join s on s.sid = p.sid
order by a.vid
Output:
VID Name Shop Price
--- -------- ------------------ -------
1 Tomato Organic Products 12.7
2 Potato
3 Garlic Best Vegetables 31.8

Related

Join Table with oracle sql

I am newbie for Oracle SQL. I wish to join 2 tables.
In TABLE 1 two tables had been join with the below presentation
Here is the sql script for TABLE 1:
select a.ID,
a.CLASSIFICATION_CODE,
a. CATEGORY_CODE
from a, b
where locality = 'xxx'
and a.DIV = b.DIV
and a.TYPE = b.TYPE
and a.DIST = b.DIST
and a.BS = b.BS
and a.LOT = b.LOT;
TABLE 2
Here is the sql script for TABLE 2:
select CODE_TYPE,CODE_1,CODE_DESC from PUBCODE01
where PUBCODE01.code_type IN ('LCL','LCA');
TABLE 3 (the table for final result after join 3 tables)
I need help on how could I combined the two sql script in order to produce the TABLE 3 presentation.
You may need to join twice the same table, based on the code_ty; for example:
SQL> with query1(id, classification_code, category_code) as(
2 select 123, 1, 3 from dual union all
3 select 456, 2, 4 from dual union all
4 select 789, 1, 3 from dual
5 ),
6 query2(code_type, code_1, code_desc) as (
7 select 'LCL', 1, 'TOMATOES' from dual union all
8 select 'LCL', 2, 'DURIAN' from dual union all
9 select 'LCA', 3, 'VEGETABLE' from dual union all
10 select 'LCA', 4, 'FRUITS' from dual
11 )
12 select id, LCL.code_desc, LCA.code_desc
13 from query1
14 inner join query2 LCL
15 on(LCL.code_1 = query1.classification_code)
16 inner join query2 LCA
17 on(LCA.code_1 = query1.category_code);
ID CODE_DESC CODE_DESC
---------- --------- ---------
123 TOMATOES VEGETABLE
789 TOMATOES VEGETABLE
456 DURIAN FRUITS
or even the following, depending on what your data can be:
select id, LCL.code_desc, LCA.code_desc
from query1
inner join query2 LCL
on(LCL.code_1 = query1.classification_code
and LCL.code_type = 'LCL')
inner join query2 LCA
on(LCA.code_1 = query1.category_code
and LCA.code_type = 'LCA')
As an aside, you should better use ANSI join; your query should be:
select a.ID,
a.CLASSIFICATION_CODE,
a. CATEGORY_CODE
from a
inner join b
on(
a.DIV = b.DIV
and a.TYPE = b.TYPE
and a.DIST = b.DIST
and a.BS = b.BS
and a.LOT = b.LOT
)
where locality = 'xxx'
Tomatoes are a fruit, please research your produce before posting.

select statement should return count as zero if no row return using group by clause

I have a table student_info, it has column "status", status can be P (present), A (absent), S (ill), T ( transfer), L (left).
I am looking for expected output as below.
status count(*)
P 12
S 1
A 2
T 0
L 0
But output is coming like as below:
Status Count(*)
P 12
S 1
A 2
we need rows against status T and L as well with count zero though no record exist in DB.
#mkuligowski's approach is close, but you need an outer join between the CTE providing all of the possible status values, and then you need to count the entries that actually match:
-- CTE to generate all possible status values
with stored_statuses (status) as (
select 'A' from dual
union all select 'L' from dual
union all select 'P' from dual
union all select 'S' from dual
union all select 'T' from dual
)
select ss.status, count(si.status)
from stored_statuses ss
left join student_info si on si.status = ss.status
group by ss.status;
STATUS COUNT(SI.STATUS)
------ ----------------
P 12
A 2
T 0
S 1
L 0
The CTE acts as a dummy table holding the five statuses you want to count. That is then outer joined to your real table - the outer join means the rows from the CTE are still included even if there is no match - and then the rows that are matched in your table are counted. That allows the zero counts to be included.
You could also do this with a collection:
select ss.status, count(si.status)
from (
select column_value as status from table(sys.odcivarchar2list('A','L','P','S','T'))
) ss
left join student_info si on si.status = ss.status
group by ss.status;
It would be preferable to have a physical table which holds those values (and their descriptions); you could also then have a primary/foreign key relationship to enforce the allowed values in your existing table.
If all the status values actually appear in your table, but you have a filter which happens to exclude all rows for some of them, then you could get the list of all (used) values from the table itself instead of hard-coding it.
If your initial query was something like this, with a completely made-up filter:
select si.status, count(*)
from student_info si
where si.some_condition = 'true'
group by si.status;
then you could use a subquery to get all the distinct values from the unfiltered table, outer join from that to the same table, and apply the filter as part of the outer join condition:
select ss.status, count(si.status)
from (
select distinct status from student_info
) ss
left join student_info si on si.status = ss.status
and si.some_condition = 'true'
group by ss.status;
It can't stay as a where clause (at least here, where it's applying to the right-hand-side of the outer join) because that would override the outer join and effectively turn it back into an inner join.
You should store somewhere your statuses (pherhaps in another table). Otherwise, you list them using subquery:
with stored_statuses as (
select 'P' code, 'present' description from dual
union all
select 'A' code, 'absent' description from dual
union all
select 'S' code, 'ill' description from dual
union all
select 'T' code, 'transfer' description from dual
union all
select 'L' code, 'left' description from dual
)
select ss.code, count(*) from student_info si
left join stored_statuses ss on ss.code = si.status
group by ss.code

Count(*) in Group By - Returns 1, instead of '0'

I am using the below oracle query to get the count of rows.
SELECT T.ID,T.NAME,COUNT(*) AS NO_OF_STUDENTS FROM STUDENT S RIGHT JOIN
TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID,T.NAME ORDER BY T.ID
Actual Result Should be:
TEACHER 1 - 10 STUDENTS
TEACHER 2 - 5 STUDENTS
TEACHER 3 - 0 STUDENT
The Result what i am getting is:
TEACHER 1 - 10 STUDENTS
TEACHER 2 - 5 STUDENTS
TEACHER 3 - 1 STUDENT
Since TEACHER 3 is not having any student, the result should be 0 Student. But i am getting the result as 1 Student.
You need to count a specific column (not use *, which includes nulls) in the table being outer joined to - since that is the one that might not have matching data. So:
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM STUDENT S
RIGHT JOIN TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID
The only difference is COUNT(S.ID) instead of COUNT(*).
Simple demo with made-up data provided via CTEs:
with teacher (id, name) as (
select 1, 'Teacher 1' from dual
union all select 2, 'Teacher 2' from dual
union all select 3, 'Teacher 3' from dual
),
student (id, teacher_id) as (
select level, 1 from dual connect by level <= 10
union all
select level + 10, 2 from dual connect by level <= 5
)
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM STUDENT S
RIGHT JOIN TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID;
ID NAME NO_OF_STUDENTS
---------- --------- --------------
1 Teacher 1 10
2 Teacher 2 5
3 Teacher 3 0
You could also do this as a left join, which I find more intuitive:
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM TEACHER T
LEFT JOIN STUDENT S ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID
which gets the same result.

How to breakdown data by month and showing zero for months with no data?

Using information in Table A, how can I produce results in Table B below?
Table A:
CASE_ID DATE_EFF COPAY STATUS
1 11/04/2016 10 A
1 11/20/2016 5 A
1 11/23/2016 5 R
1 12/01/2016 1 A
1 12/10/2016 2 A
1 12/12/2016 10 A
1 12/31/2016 50 R
For the above CASE_ID, we have dates in Nov 2016 and Dec 2016 only, however, I want to produce a breakdown of this CASE_ID for a period of 6 months as below where for each month the copays are summed where applicable as per the DATE_EFF and for the months that are not within the above dates, a zero is entered. Also, only records with copays with a status of 'A' are summed for any month -- so those with status of 'R' are ignored in the summation. For example, based on data in Table A above, the intended results are as follow:
Table B:
CASE_ID MONTH TOTAL_COPAY
1 01/2017 0
1 12/2016 13
1 11/2016 15
1 10/2016 0
1 09/2016 0
1 08/2016 0
I have below as a possible solution[using a with clause], but can this be achieved without the use of the 'with' clause?
Possible Solution:
WITH
XRF AS
( SELECT CASE_ID, COPAY, DATE_EFF
FROM Table_A WHERE STATUS = 'A'
)
SELECT F.CASE_ID, ST, NVL(SUM(F.COPAY),0) TOTAL_COPAY FROM XRF F PARTITION BY (F.CASE_ID)
RIGHT OUTER JOIN (SELECT '12/2016' ST FROM DUAL UNION ALL
SELECT '11/2016' FROM DUAL UNION ALL
SELECT '10/2016' FROM DUAL UNION ALL
SELECT '09/2016' FROM DUAL UNION ALL
SELECT '08/2016' FROM DUAL UNION ALL
SELECT '07/2016' FROM DUAL) STS
ON (TO_CHAR(LAST_DAY((F.DATE_EFF)),'MM/YYYY') = STS.ST)
GROUP BY F.CASE_ID, ST ORDER BY F.CASE_ID, ST DESC
;
UPDATE AND SOLUTION:
Using the above query, I believe I am have answered my own question by implementing it as below -- not sure though if using this method is expensive when you have millions of records of such CASE_IDs. Any thoughts?
SELECT F.CASE_ID, ST, NVL(SUM(F.COPAY),0) TOTAL_COPAY FROM (SELECT CASE_ID, COPAY, DATE_EFF FROM TABLE_A WHERE STATUS = 'A') F PARTITION BY (F.CASE_ID)
RIGHT OUTER JOIN (SELECT '12/2016' ST FROM DUAL UNION ALL
SELECT '11/2016' FROM DUAL UNION ALL
SELECT '10/2016' FROM DUAL UNION ALL
SELECT '09/2016' FROM DUAL UNION ALL
SELECT '08/2016' FROM DUAL UNION ALL
SELECT '07/2016' FROM DUAL) STS
ON (TO_CHAR(LAST_DAY((F.DATE_EFF)),'MM/YYYY') = STS.ST)
GROUP BY F.CASE_ID, ST ORDER BY F.CASE_ID, ST DESC
;

Oracle sql retrive records based on maximum time

i have below data.
table A
id
1
2
3
table B
id name data1 data2 datetime
1 cash 12345.00 12/12/2012 11:10:12
1 quantity 222.12 14/12/2012 11:10:12
1 date 20/12/2012 12/12/2012 11:10:12
1 date 19/12/2012 13/12/2012 11:10:12
1 date 13/12/2012 14/12/2012 11:10:12
1 quantity 330.10 17/12/2012 11:10:12
I want to retrieve data in one row like below:
tableA.id tableB.cash tableB.date tableB.quantity
1 12345.00 13/12/2012 330.10
I want to retrieve based on max(datetime).
The data model appears to be insane-- it makes no sense to join an ORDER_ID to a CUSTOMER_ID. It makes no sense to store dates in a VARCHAR2 column. It makes no sense to have no relationship between a CUSTOMER and an ORDER. It makes no sense to have two rows in the ORDER table with the same ORDER_ID. ORDER is also a reserved word so you cannot use that as a table name. My best guess is that you want something like
select *
from customer c
join (select order_id,
rank() over (partition by order_id
order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
from order) o on (c.customer_id=o.order_id)
where o.rnk = 1
If that is not what you want, please (as I asked a few times in the comments) post the expected output.
These are the results I get with my query and your sample data (fixing the name of the ORDER table so that it is actually valid)
SQL> ed
Wrote file afiedt.buf
1 with orders as (
2 select 1 order_id, 'iphone' order_name, '20121201 12:20:23' order_time from dual union all
3 select 1, 'iphone', '20121201 12:22:23' from dual union all
4 select 2, 'nokia', '20110101 13:20:20' from dual ),
5 customer as (
6 select 1 customer_id, 'paul' customer_name from dual union all
7 select 2, 'stuart' from dual union all
8 select 3, 'mike' from dual
9 )
10 select *
11 from customer c
12 join (select order_id,
13 rank() over (partition by order_id
14 order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
15 from orders) o on (c.customer_id=o.order_id)
16* where o.rnk = 1
SQL> /
CUSTOMER_ID CUSTOM ORDER_ID RNK
----------- ------ ---------- ----------
1 paul 1 1
2 stuart 2 1
Try something like
SELECT *
FROM CUSTOMER c
INNER JOIN ORDER o
ON (o.CUSTOMER_ID = c.CUSTOMER_ID)
WHERE TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS') =
(SELECT MAX(TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS')) FROM ORDER)
Share and enjoy.

Resources