Select statement where one of the column values change dynamically - sql

I am trying to write a query as follows
select trading_day,trading_time,quantity,price from trade where trade_id=A903
Now for this trading_time the logic of extraction is
If the price above matches table trade_sb price and trade_id then that becomes trading time
if there is no match I go to another table trade_mb and search for match of price and trade_id
If there is no match in both above cases the original query holds good.
I tried with rank but couldn't get the result
Please help

it's not clear to me but i think you can create a function for it and use it in the query, see sample code below,
CREATE FUNCTION get_trading_time (p_trade_id VARCHAR2, p_price NUMBER, p_trading_time DATE) RETURN DATE
IS
v_trading_time DATE;
BEGIN
BEGIN
SELECT trading_time
INTO v_trading_time
FROM trade_sb
WHERE trade_id = p_trade_id
AND price = p_price;
RETURN v_trading_time;
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL;
END;
BEGIN
SELECT trading_time
INTO v_trading_time
FROM trade_mb
WHERE trade_id = p_trade_id
AND prioe = p_price;
RETURN v_trading_time;
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL;
END;
IF v_trading_time IS NULL THEN
RETURN p_trading_time;
END IF;
END;
/
select trading_day,trading_time,quantity,price ,
get_trading_time(trade_id, price, trading_time) trading_time2
from trade
where trade_id='A903';

Related

Getting unexpected values for number of days between two dates in oracle

I am writing a SQL code which fetches two dates from the database and calculates the number of days between them. Here is the code:
create table borrower(
roll_no number,
date_of_issue date,
name_of_book varchar(20),
status varchar(10)
);
insert into borrower values(1,to_date('02-JAN-2022'),'dbms','issued');
insert into borrower values(2,to_date('10-JAN-2022'),'cns','issued');
insert into borrower values(3,to_date('17-JAN-2022'),'spos','issued');
insert into borrower values(4,to_date('26-JAN-2022'),'toc','issued');
create table fine(
roll_no number,
current_date date,
amount number
);
insert into fine values(1,to_date('14-FEB-2022'),null);
insert into fine values(2,to_date('14-FEB-2022'),null);
insert into fine values(3,to_date('14-FEB-2022'),null);
insert into fine values(4,to_date('14-FEB-2022'),null);
DECLARE
roll_counter number:=1;
initial_date date;
final_date date;
date_calc number;
BEGIN
loop
select date_of_issue into initial_date from borrower where roll_no=roll_counter;
select current_date into final_date from fine where roll_no=roll_counter;
date_calc:=final_date-initial_date;
dbms_output.put_line(date_calc);
roll_counter:=roll_counter+1;
exit when roll_counter>4;
end loop;
END;
/
drop table borrower;
drop table fine;
I am not getting any error, but instead getting unexpected values for the number of days. Here is the output:
Statement processed.
246.4165625
238.4165625
231.4165625
222.4165625
I was expecting the number of days between the two dates(check the table). Can someone help me sort this out.
CURRENT_DATE is an Oracle keyword that returns the current date. Name your column something that is not an Oracle keyword.
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CURRENT_DATE.html
As #Matthew McPeak pointed out, CURRENT_DATE is a built-in function and that function is being called rather than returning your column value.
If you want the column value then you need to prefix the column name with the table name/alias and use fine.current_date:
DECLARE
roll_counter number:=1;
initial_date date;
final_date date;
date_calc number;
BEGIN
FOR roll_counter IN 1 .. 4 LOOP
select date_of_issue
into initial_date
from borrower
where roll_no=roll_counter;
select fine.current_date
into final_date
from fine
where roll_no=roll_counter;
date_calc:=final_date-initial_date;
dbms_output.put_line(date_calc);
END LOOP;
END;
/
Which, for your sample data, outputs:
43
35
28
19
Or you can use a single query (rather than multiple queries that are called in each loop iteration):
BEGIN
FOR r IN (
SELECT f.current_date - b.date_of_issue AS diff
FROM borrower b
FULL OUTER JOIN fine f
ON (b.roll_no = f.roll_no)
WHERE COALESCE(b.roll_no, f.roll_no) BETWEEN 1 AND 4
ORDER BY COALESCE(b.roll_no, f.roll_no)
) LOOP
dbms_output.put_line(r.diff);
END LOOP;
END;
/
db<>fiddle here

How to create a function to add new record into a table? ORACLE

I am not sure if using the create or replace function is the right way to do this but i am trying to figure out how to ONLY add record to the table when the count of employee ID where month of read date = month of sysdate is not more than 10.
I have a employee_read table using primary key, reading_ID, foreign key emp_id and an attribute of read_date.
The information i found online shows return value. but how do i add into table instead of return? is it do-able?
Thank you for your help!
CREATE OR REPLACE FUNCTION AddNewReadRecord
(varEmpID IN NUMBER. varReadDate IN DATE, varWaterMeterID IN CHAR,
varCuReading IN NUMBER, varPrevReading IN Number)
RETURN
BEGIN
END
You can obviously add DML in the function but using PRAGMA AUTONOMOUS_TRANSACTION as follows:
CREATE OR REPLACE FUNCTION AddNewReadRecord
(varEmpID IN NUMBER. varReadDate IN DATE, varWaterMeterID IN CHAR, varCuReading IN NUMBER, varPrevReading IN Number)
RETURN number IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
Insert into your_table (...) -- column list
Select ... -- variable list
From your_table
Where emp_id = varEmpID
And read_date = varReadDate
Group by emp_id
Having count(1) < 10;
Commit;
Return 1; -- add logic for return using exception
END;
/

Returned my Cursor in my oracle PL/SLQ function but not all rows are being returned. Can you only return 1 row in a Oracle pl/sql function?

Here is my code I have 2 rows that share the same name, reservation date, and hotel Id. I don't understand why when I execute this function it gives me the error "exact fetch returns more than requested number of rows" instead of returning my both rows in my Reservation Table.
I have returned the cursor correctly I assume, so it should work?
CREATE OR REPLACE FUNCTION findres(cname IN reservation.cust_name%type,
hotelID IN reservation.hotel_id%type,
resdate IN reservation.reserve_date%type)
RETURN reservation.reserve_id%type is
resid reservation.reserve_id%type;
BEGIN
SELECT reserve_id
INTO resid
FROM reservation
WHERE Cust_name = cname
AND Hotel_id = hotelID
AND reserve_date = resdate;
RETURN resid;
EXCEPTION
WHEN no_data_found THEN
dbms_output.put_line('No reservation found');
END;
/
From the documentation for definition of into_clause : the SELECT INTO statement retrieves one or more columns from a single row and stores them in either one or more scalar variables or one record variable
Then the current SELECT statement should be replaced against the cases of returning more than one row. The following queries might be alternatives for your current SQL Select statement
SELECT reserve_id
INTO resid
FROM
( SELECT r.*,
ROW_NUMBER() OVER (ORDER BY 0) AS rn
FROM reservation
WHERE Cust_name = cname
AND Hotel_id = hotelID
AND reserve_date = resdate
)
WHERE rn = 1;
If DB version is 12+, then use
SELECT reserve_id
INTO resid
FROM reservation
WHERE Cust_name = cname
AND Hotel_id = hotelID
AND reserve_date = resdate
FETCH NEXT 1 ROW ONLY;
without a subquery in order to return one row only, considering you only get duplicates for those columns with no ordering rules for the data. Through use of these queries, no need to handle no_data_found or too_many_rows exceptions.
Update : If your aim is to return all the rows even there are more than one row at once, then you can use SYS_REFCURSOR such as
CREATE OR REPLACE FUNCTION findres(cname reservation.cust_name%type,
hotelID reservation.hotel_id%type,
resdate reservation.reserve_date%type)
RETURN SYS_REFCURSOR IS
recordset SYS_REFCURSOR;
BEGIN
OPEN recordset FOR
SELECT reserve_id
FROM reservation
WHERE Cust_name = cname
AND Hotel_id = hotelID
AND reserve_date = resdate;
RETURN recordset;
END;
/
and call in such a way that
VAR v_rc REFCURSOR
EXEC :v_rc := findres('Avoras',111,date'2020-12-06');
PRINT v_rc
from the SQL Developer's console.

Pl/SQL assign the string to a variable and print the value each time

Create a function named 'display_player_skill' which accepts an input parameter. The input parameter is 'player_id' with number as its data type.This function should return the skil_id for the given 'player_id'.
I had tried
create or replace function display_player_skill (player_id in number)
return varchar2
is
skill_name varchar2(100);
begin
select s.name into skill_name from k_skill s
inner join k_player p on p.skill_id=s.id
where p.skill_id= player_id;
case skill_name
when 'Raider' then return 'Player is a Raider';
when 'All rounder' then return 'Player is a All Rounder';
end case;
exception
when case_not_found then return 'Player is a Defender';
when no_data_found then return 'No Such Player';
end;
/
but I had to return the skill_id for the given player_id and display skill_name. By assign the string to a variable and print the value each time.
try it:
select s.name, s.id into skill_name,skill_id from k_skill s
inner join k_player p on p.skill_id=s.id
where p.player_id= p_player_id;
-- dbms_output.put_line('Skill name is a '||skill_name);
return skill_id;
Seems where clause is not correct (it's quite strange to compare skill_id with player_id).
where p.skill_id= player_id;
Besides, if you have column player_id in k_player, Oracle will treat player_id as column name. So you need to name your parameter something like p_player_id (p_ for parameter)
So I'd expect your code looks something like:
create or replace function display_player_skill (p_player_id in number)
return varchar2
is
skill_name varchar2(100);
skill_id number;
BEGIN
select s.name, s.id into skill_name, skill_id from k_skill s
inner join k_player p on p.skill_id=s.id
where p.player_id= p_player_id;
dbms_output.put_line('Skill name is a '||skill_name);
return skill_id;
EXCEPTION
when no_data_found then return 'No Such Player';
END;
/
I don't know your tables structure so some column names may require correction.

Query to find whether a table was updated within a specific time period

Table1
Emp_no|| end_date || col2 || Sent date
This table will send the details of a particular employee and the sent date column will be updated as sysdate whenever the detail is entered in this table.
Now for the second run ill have to check that was the mail sent for the same employee within 15 days ? if yes then the row will not be updated in the table1. i.e. i have to compare the latest sent date and sysdate. For this i am using a function
create or replace Function last_mail_sent(p_emp_no number,pay_period_end_date date)
return number
is
begin
select trunc(sysdate)-trunc(sent_date)
into
l_days
from table1
where emp_no=p_emp_no
and sent_date=(select max(sent_date) from tabl1
where emp_no=p_emp_no
and trunc(end_date)=trunc(pay_period_end_date );
exception when no_data_found
then
l_days :=0;
else
when others then
l_days:=-1;
end;
return l_days;
end;
/
The logic i was trying to use in the package where i have used this funcn to decide whether or not to insert was :
l_last_mail_sent := last_mail_sent(pass the parameter by fetching cols from cursor)
if l_last_mail_sent=0 (That is no row is fetched from the function)
or l_last_mail_sent>15
then
insert into Table1
else
--do not insert
end if;
But the problem with the above query is . that for example if the table was updated today the sent_date column was updated with todays date then also the function will return 0. Is there a better logic that can be formed ? P.S :-I have to create a function to deal with this as this will be reusable.
select trunc(sysdate) - trunc(max(sent_date))
into l_days
from table1
where emp_no = p_emp_no
and end_date between trunc(pay_period_end_date) and trunc(pay_period_end_date) + 1 - 1/24/60/60;
(This query won't raise any exceptions so you don't need to catch anything)
I've changed the condition a bit so you can use an index on an end_date (if any)
And then
if l_last_mail_sent is null or l_last_mail_sent > 15 then
--insert into Table1
else
--do not insert
end if;