Joining the top result in Oracle - oracle

I'm using this query:
SELECT *
FROM HISTORY
LEFT JOIN CUSTOMER ON CUSTOMER.CUST_NUMBER = HISTORY.CUST_NUMBER
LEFT JOIN (
Select LOAN_DATE, CUST_NUMBER, ACCOUNT_NUMBER, STOCK_NUMBER, LOC_SALE
From LOAN
WHERE ACCOUNT_NUMBER != 'DD'
ORDER BY LOAN_DATE DESC
) LOAN ON LOAN.CUST_NUMBER = HISTORY.CUST_NUMBER
order by DATE desc
But I want only the top result from the loan table to be joined (Most recent by Loan_date). For some reason, it's getting three records (one for each loan on the customer I'm looking at). I'm sure I'm missing something simple?

If you're after joining the latest loan row per cust_number, then this ought to do the trick:
select *
from history
left join customer on customer.cust_number = history.cust_number
left join (select loan_date,
cust_number,
account_number,
stock_number,
loc_sale
from (select loan_date,
cust_number,
account_number,
stock_number,
loc_sale,
row_number() over (partition by cust_number
order by loan_date desc) rn
from loan
where account_number != 'DD')
where rn = 1) loan on loan.cust_number = history.cust_number
order by date desc;
If there are two rows with the same loan_date per cust_number and you want to retrieve both, then change the row_number() analytic function for rank().
If you only want to retreive one row, then you'd have to make sure you add additional columns into the order by, to make sure that the tied rows always display in the same order, otherwise you could find that sometimes you get different rows returned on subsequent runs of the query.

Related

joining tables to sum the amount of a items ordered

Have the following data tables:
menu_items:
item_id,
item_name,
price,
sales:
item_id,
customer_id,
employee_id,
date
I am attempting to join the tables on item_id. I want to display the item_name, number of item_names sold and date, and group them by the date. How should I adjust the code below to make the query work.
select item_name, count(item_name), date
from menu_items join sales
on item_id = item_id
group by date
As you probably found out, it won't work; all non-aggregated items must be contained in the GROUP BY clause. Also, you should use table aliases, always.
select s.date_col,
i.item_name,
count(*) number_of_items_sold
from menu_items i join sales s on s.item_id = i.item_id
group by s.date_col, i.item_name
order by s.date_col, i.item_name;
If it is not what you wanted, please, post some sample data and desired output; it might be easier to answer, then.

ORACLE Query to find value in other table based on dates

I have two tables, Table A has an ID and an Event Date and Table B has an ID, a Description and an Event Date.
Not all IDs in Table A appear in Table B and some IDs appear multiple times in Table B with different Descriptions for each event.
The Description in Table B is an attribute that can change over time, the Event date in Table B is the date that a given ID's Description changes from its default value (kept in another table) to the new value.
I want to find the Description in Table B that matches the Event Date in Table A so, for example
Table Sample Data
A1234 would return Green and A4567 would return Null
I can't create tables here so I need to be able to this with a query.
This query will select last description from before the event:
SELECT * FROM (
SELECT tabA.id, tabA.event_date, tabB.description,
ROW_NUMBER() OVER(PARTITION BY tabB.id ORDER BY tabB.event_date DESC) rn
FROM Table_A tabA
LEFT JOIN Table_B tabB ON tabA.id = tabB.id AND tabB.event_date <= tabA.event_date
) WHERE rn = 1
If I understand well your need, this could be a way:
select a.id, description
from tableA A
left join
(select id,
description,
event_date from_date,
lead(event_date) over (partition by id order by event_date) -1 as to_date
from tableB
) B
on (A.id = B.id and a.event_date between b.from_date and b.to_date)
The idea here is to evaluate, for each row in tableB the range of dates for which that row, and its description, is valid; given this, a simple join should do the job.
You can left join tables like:
select a.ID , b1.DESCRIPTION
from TABLE_A a
left join TABLE_B b1 on a.ID = b1.id and a.EVENT_DATE > b1.EVENT_DATE
left join TABLE_B b2 on a.ID = b2.id and b1.EVENT_DATE < b2.EVENT_DATE and a.EVENT_DATE > b2.EVENT_DATE
where b1.id is null or b2.EVENT_DATE is null;

How to reduce join operation to a single row in Oracle?

This example is invented for the purpose of the question.
SELECT
PR.PROVINCE_NAME
,CO.COUNTRY_NAME
FROM
PROVINCE PR
JOIN COUNTRY CO ON CO.COUNTRY_ID=PR.COUNTRY_ID
WHERE
PR.PROVINCE_ID IN (1,2)
Let's assume that COUNTRY_ID is not the Primary Key in the Country table and the above join on Country table returns potentially multiple rows. We don't know how many rows and we don't care why there are multiple ones. We only want to join on one of them, so we get one row per Province.
I tried subquery for the join but can't pass in PR.COUNTRY_ID for Oracle 11.2. Are there any other ways that this can be achieved?
A typical safe approach of handling tables without PK is to extend the duplicated column with a unique index (row_numer of the duplicated row)
In your case this would be:
with COUNTRY_UNIQUE as (
select COUNTRY_ID,
row_number() over (partition by COUNTRY_ID order by COUNTRY_NAME) rn,
COUNTRY_NAME
from country)
select * from COUNTRY_UNIQUE
order by COUNTRY_ID, rn;
leading to
COUNTRY_ID RN COUNTRY_NAME
---------- ---------- ------------
1 1 C1
2 1 C2
2 2 C3
The combination of COUNTRY_IDand RN is unique, so if you constraint only RN = 1 the COUNTRY_ID is unique.
You may define the order of the duplicated records and control with it the selection - in our case we choose the smalest COUNTRY_NAME.
The whole join used this subquery and constraints the countries on RN = 1
with COUNTRY_UNIQUE as (
select COUNTRY_ID,
row_number() over (partition by COUNTRY_ID order by COUNTRY_NAME) rn,
COUNTRY_NAME
from country)
SELECT
PR.PROVINCE_NAME
,CO.COUNTRY_NAME
FROM
PROVINCE PR
JOIN COUNTRY_UNIQUE CO ON CO.COUNTRY_ID=PR.COUNTRY_ID
WHERE
PR.PROVINCE_ID IN (1,2)
AND CO.RN = 1; /* consider only unique countries */
If you have Oracle 12c, you can use a LATERAL view in the join. Like this:
SELECT
PR.PROVINCE_NAME
,CO.COUNTRY_NAME
FROM
PROVINCE PR
CROSS JOIN LATERAL (
SELECT * FROM COUNTRY CO
WHERE CO.COUNTRY_ID=PR.COUNTRY_ID
FETCH FIRST 1 ROWS ONLY) CO
WHERE
PR.PROVINCE_ID IN (1,2)
Update for Oracle 11.2
In Oracle 11.2, you can use something along these lines. Depending on the size of COUNTRY and how many duplicates there are per COUNTRY_ID, it could perform as well or better than the 12c approach. (Fewer buffer gets but more memory required).
SELECT pr.province_name,
co.country_name
FROM province pr
INNER JOIN (SELECT *
FROM (SELECT co.*,
ROW_NUMBER () OVER (PARTITION BY co.country_id ORDER BY co.country_name) rn
FROM country co)
WHERE rn = 1) co
ON co.country_id = pr.country_id
WHERE pr.province_id IN (1, 2)

Oracle - select top transaction for each user_id

I have a select statement that gets user_id and a list of transactions for the day such as this:
select user_id, sale_amount, date, product from transactions
I want to be able to select each user_id (there are many) along with their top sale_amount, date and product. If there is a tie, I want it to just select one. How is this possible? Rownum or rank seem to be close but not quite there?
I m not ifo computer but this should work. Let me know
select * from (select user_id, sale_amount, date, product,row_number() over (partition by user_id order by sales_amount desc) as maxsale from transactions) l where maxsale=1

How to delete the recent (in date) records from a table in oracle?

i want to delete the last recent record from my table.
Code to select the last record:
SELECT * FROM
( select ID, NAME,
createdon
from Person where age= 30
order by createdon DESC)
WHERE ROWNUM <= 1;
i tried this to delete the last records:
DELETE * FROM
( select ID, NAME,
createdon
from Person where age= 30
order by createdon DESC)
WHERE ROWNUM <= 1;
It 's not working.
ERROR: "invalid table name"
Any help please, Thank you
As OracleUser said, you can't delete from an in-line view. You can use your original inner query to delete based on all three columns it selects, assuming they will uniquely identify a single row:
delete from person
where (id, name, createdon) = (
select * from (
select id, name, createdon
from person
where age = 30
order by createdon desc
)
where rownum <= 1
);
SQL Fiddle.
If ID is a primary or unique key you can do this instead:
delete from person
where id =
(
select id
from (
select id, row_number() over (order by createdon desc) as rn
from person where age = 30
)
where rn = 1
);
If it is not then you can use rowid instead, replacing both references to id in the statement.
The innermost query is basically the same as the inner query in your select, but I'm using the row_number analytic function to assign the equivalent of rownum. The next layer out filters out everything except the most recent, based on the row_number calculation.
You can use rank() instead but could get two record with exactly the same times, and would need to decide whether both should be deleted, or if you should have some additional way to break the tie - e.g. order by createdon desc, id desc. As I've shown it here, row_number() also breaks the tie arbitrarily but can have the explicitly extra ordering to control that, as would rownum. And there's also dense_rank().
SQL Fiddle.
This is a bit simpler but would also delete multiple rows, if you had more than one with the same createdon value:
delete from person
where createdon =
(
select max(createdon)
from person
where age = 30
);

Resources