Oracle SQL Developer dynamic date table name - sql

I'm hoping to dynamically reference last Fridays date in the weekly sales tables in Oracle SQL Developer i.e. SELECT * FROM Sales_DDMMYY
I can do this in SQL Server (DECLARE / SET / EXECUTE) but haven't had any joy with SQL Developer.
Even the ability to create a date variable to be referenced within the code would be a great start.

Stop!
I strongly suggest you not to do that. That's not the way to create a data model. If you have a table which contains values related to different dates, then date should be a column in that table, such as
create table sales
(id number,
datum date,
amount number
);
Insert rows as
insert into sales (id, datum, amount)
select 1, date '2020-06-01', 100 from dual union all
select 2, date '2020-05-13', 240 from dual union all
select 3, date '2020-05-13', 160 from dual;
and use it as
select sum(amount)
from sales
where datum = date '2020-05-13'
That is the way to do it. Naming columns by dates is ... well, close to a suicide.
Aha, now I see: it is a table name that contains dates. Doesn't really matter, my suggestion still stands. Do not do that. Use a date column within a single table.
If you want - and if you can afford it - partition the table on date value. Note that partitioning option exists in Oracle Enterprise Edition which is quite expensive. So - date column it is.
If there's nothing you can do about it, then dynamic SQL it is. For example:
Sample table:
SQL> create table sales_200620 as select * From dept;
Table created.
Function that accepts ddmmyy value as a parameter, composes table name and returns a refcursor:
SQL> create or replace function f_test (par_ddmmyy in varchar2)
2 return sys_refcursor
3 is
4 l_table_name varchar2(30) := 'sales_' || par_ddmmyy;
5 l_rc sys_refcursor;
6 begin
7 open l_rc for 'select * from ' || dbms_assert.sql_object_name(l_table_name);
8 return l_rc;
9 end;
10 /
Function created.
Testing:
SQL> select f_test('200620') from dual;
F_TEST('200620')
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
Without creating a function: use substitution variable:
SQL> select * From &tn;
Enter value for tn: sales_200620
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>

Related

Can views be created in PL/SQL Functions?

I'm at the beginning of learning PL/SQL. My question is can views be created inside a function?
CREATE OR REPLACE FUNCTION most_sold_item(in_year INT)
RETURN INT
IS
most_shipped_item INT := 0;
BEGIN
CREATE OR REPLACE VIEW Shipped_Items AS
SELECT ITEM_ID,SUM(QUANTITY) AS Total_Quantity
FROM ORDERS, ORDER_ITEMS
WHERE Orders.Order_ID =Order_Items.Order_ID
AND Status=1
AND Order_Year=in_year
GROUP BY Item_ID;
SELECT Item_ID
INTO most_shipped_item
FROM Shipped_Items
WHERE Total_quantity=(
SELECT MAX(Total_Quantity)
FROM Shipped_Items);
return most_shipped_item;
END;
/
Here's my code. There seems to be some kind of error which I just can't find. Can anyone please help me with this?
Thank you.
Here's the snippet of the datatbase that I'm working on
Here's a rough idea of what you might do, if you wish to use a function for this (Added logic to break ties):
-- Estimated structure based on posted image:
CREATE TABLE orders (
order_id int primary key
, client_id int
, order_year int
, status int
);
CREATE TABLE order_items (
order_id int
, item_id int
, quantity int
, ppu int
, primary key (order_id, item_id)
);
-- The adjusted function:
CREATE OR REPLACE FUNCTION most_sold_item(in_year INT)
RETURN INT
IS
most_shipped_item INT := 0;
BEGIN
WITH shipped_items AS (
SELECT ITEM_ID, SUM(QUANTITY) AS Total_Quantity
FROM ORDERS, ORDER_ITEMS
WHERE Orders.Order_ID =Order_Items.Order_ID
AND Status=1
AND Order_Year=in_year
GROUP BY Item_ID
)
SELECT Item_ID
INTO most_shipped_item
FROM Shipped_Items
WHERE Total_quantity=(
SELECT MAX(Total_Quantity)
FROM Shipped_Items
)
ORDER BY item_id
FETCH FIRST 1 ROW ONLY
;
return most_shipped_item;
END;
/
Don't forget to protect for the case where more than one item_id matches the MAX. That's easy to do, but I don't know how you wish to handle that case. I've adjusted the above solution to protect against this issue.
The question edit queue is full. Here's an executable test case (without data) that could be added to the question:
Executable test case, derived from the posted image (without data)
To answer your "original" question:
Can views be created in PL/SQL Functions?
Yes, they can. Should you do it? Definitely not, that's not how Oracle works (in most cases). In Oracle, we create objects at SQL level and use them in SQL or PL/SQL, but we do not create them from PL/SQL.
Anyway, here you are; read comments within code. I used Scott's sample schema as I don't have your tables, but the principle remains the same.
Function:
SQL> create or replace function f_test(par_deptno in number)
2 return number
3 is
4 -- you can't perform DDL in PL/SQL procedures unless they are
5 -- autonomous transactions
6 pragma autonomous_transaction;
7 retval number;
8 begin
9 -- you can't perform DML in a function unless you use dynamic SQL
10 execute immediate
11 'create or replace view v_emp as ' ||
12 ' select ename, sal ' ||
13 ' from emp ' ||
14 ' where deptno = ' || dbms_assert.enquote_literal(par_deptno);
15
16 -- you can't use an ordinary SELECT statement because at time of
17 -- procedure compilation view V_EMP doesn't exist yet, so compile
18 -- would fail
19 execute immediate
20 'select sum(sal) from v_emp ' into retval;
21
22 return retval;
23 end;
24 /
Function created.
Testing:
SQL> select f_test (20) from dual;
F_TEST(20)
----------
10875
What does the view contain?
SQL> select * from v_emp;
ENAME SAL
---------- ----------
SMITH 800
JONES 2975
SCOTT 3000
ADAMS 1100
FORD 3000
SQL>

trigger in PL/SQL

There are two tables given:
1)
employee(eno,ename,basic,da,gross)
da=basic*(5.0/100)
gross = basic+da
2)
sal_hist(eno, sys_dt, old_basic)
How to write a trigger to update the 'da' and 'gross' whenever I am updating basic salary of the employee?
PL/SQL tag suggests that you use Oracle database.
You said that there's yet another table, sal_hist, but - you didn't say what to do with it. I presume you'd want to save the old basic salary.
In that case, trigger would look like this:
SQL> create or replace trigger trg_biu_emp
2 before insert or update on employee
3 for each row
4 begin
5 insert into sal_hist(eno, sys_dt, old_basic) values
6 (:new.eno, sysdate, :old.basic);
7
8 :new.da := :new.basic * 5 / 100;
9 :new.gross := :new.basic + :new.da;
10 end;
11 /
Trigger created.
Let's see how it works:
SQL> select * From employee;
ENO ENAME BASIC DA GROSS
---------- ----- ---------- ---------- ----------
1 Scott 100 0 0
2 Tiger 500 0 0
SQL> select * From sal_hist;
no rows selected
SQL> update employee set basic = 200 where eno = 1;
1 row updated.
SQL> insert into employee (eno, ename, basic) values (3, 'King', 1000);
1 row created.
SQL> select * From employee;
ENO ENAME BASIC DA GROSS
---------- ----- ---------- ---------- ----------
1 Scott 200 10 210
2 Tiger 500 0 0
3 King 1000 50 1050
SQL> select * From sal_hist;
ENO SYS_DT OLD_BASIC
---------- ------------------- ----------
1 06.06.2020 11:10:49 100
3 06.06.2020 11:12:07
SQL>
CREATE DEFINER=`root`#`localhost` TRIGGER `TRIGGERNAME` AFTER UPDATE ON `employee` FOR EACH ROW
BEGIN
SET employee.da = employee.basic*(5.0/100)
SET employee.gross = employee.basic + (employee.basic*(5.0/100))
END
The way you have them defined both da and gross are derivable directly from base. However as a standard column I can update either of them directly. If this is not desirable, then you can declare them as virtual columns.
alter table employee drop (da, gross);
alter table employee add (
da generated always as base * 0.05 virtual
, gross generated always as base * 1.05 virtual
);
Now neither column can be updated independently and both are automatically updated when base is updated. You use them as normal columns of select, but DO NOT reference then in Insert or Update. And there is NO trigger needed.
See fiddle. Note: You did not specify what Oracle version you are using. This requires 11gR1 or higher.

Learning SQL in Free Time

So i have the following table
EMPNO ENAME DEPTN JOB HIREDDATE SALARY
111 Narayan R22 Electrical 26-DEC-99 5000
108 Horen P69 PWD 10-DEC-95 10000
130 Roy A13 Security 15-SEP-01 25000
420 Roy D12 IT 15-SEP-99 2500
100 Allu A13 Security 26-JAN-15 15000
With datatypes
EMPNO NOT NULL NUMBER(38),
ENAME NOT NULL VARCHAR2(20),
DEPTNO VARCHAR2(5),
JOB VARCHAR2(20),
HIREDDATE DATE,
SALARY NUMBER(38)
I want to try to create a stored procedure
a) Changing the hire date of any employee.
b) Performing DML commands (Insert, Update and Delete)
c) Multiplying two numbers
I tried to use this code
CREATE PROCEDURE ChangeHired #EmpNo int, #Hired date
AS
ALTER TABLE Employee01 MODIFY HireDate = #Hired WHERE EmpNo = #EmpNo
GO;
But it doesn't work in Oracle 10g. Need some help.
Edit: UPDATE Employee01 SET HireDate = #Hired WHERE EmpNo = #EmpNo should be used that was a mistake from my side but still the code is not executed.
Please use below query,
update Employee01 set HireDate = #Hired WHERE EmpNo = #EmpNo
You have just created the procedure, to make it work you have to call it, Call the procedure using,
execute procedure_name(parameter1, parameter2);
Alter statement should be used only when modifying your structure of the table. While working with data, please use only DML's.
Syntax you used (or Jim suggested) isn't Oracle. Should be something like this (including that "multiplying" example you asked for):
Sample data (only minimum columns needed for the procedure to work):
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> select * From employee1;
EMPNO HIREDDATE
---------- ----------
111 08.06.2020
Procedure:
SQL> create or replace procedure changehired(par_empno in int, par_hired in date)
2 is
3 a number := 2;
4 b number := 3;
5 result number;
6 begin
7 update employee1 set
8 hireddate = par_hired
9 where empno = par_empno;
10
11 result := a * b;
12 dbms_output.put_line(a || ' * '|| b || ' = ' || result);
13 end;
14 /
Procedure created.
Testing: once the procedure is created, you have to execute it. In order to see result of dbms_output.put_line, enable serveroutput in tool you use (for SQL*Plus or SQL Developer, execute set serveroutput on).
SQL> set serveroutput on;
SQL> begin
2 changehired(111, date '2020-06-08');
3 end;
4 /
2 * 3 = 6
PL/SQL procedure successfully completed.
SQL> select * from employee1;
EMPNO HIREDDATE
---------- ----------
111 08.06.2020
SQL>

Select records from table where table name come from another table

We generate tables dynamically Eg. Table T_1, T_2, T_3, etc & we can get that table names from another table by following query.
SELECT CONCAT('T_', T_ID) AS T_NAME FROM T_NAMES WHERE T_KEY = 'ABC';
Now I want to get records from this retrieved table name. What can I do ?
I'm doing like following but that's not working :
SELECT * FROM (SELECT CONCAT('T_', T_ID) AS T_NAME FROM T_NAMES WHERE T_KEY = 'ABC')
FYI : I'm hitting two individual queries as of now though I want to eliminate one and I can not follow cursor/procedure approach due to some limitations.
A procedure which utilizes refcursor seems to be the most appropriate to me. Here's an example:
SQL> -- creating test case (your T_NAMES table and T_1 which looks like Scott's DEPT)
SQL> create table t_names (t_id number, t_key varchar2(3));
Table created.
SQL> insert into t_names values (1, 'ABC');
1 row created.
SQL> create table t_1 as select * from dept;
Table created.
SQL> -- a procedure; accepts KEY and returns refcursor
SQL> create or replace procedure p_test
2 (par_key in varchar2, par_out out sys_refcursor)
3 as
4 l_t_name varchar2(30);
5 begin
6 select 'T_' || t_id
7 into l_t_name
8 from t_names
9 where t_key = par_key;
10
11 open par_out for 'select * from ' || l_t_name;
12 end;
13 /
Procedure created.
OK, let's test it:
SQL> var l_out refcursor
SQL> exec p_test('ABC', :l_out)
PL/SQL procedure successfully completed.
SQL> print l_out
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
I could propose to you Dynamic SQL.
First of all, you need to create a cursor. The cursor will iterate by the dynamic tables. Then you could use dynamic SQL to create a query and then execute it.
So example:
https://livesql.oracle.com/apex/livesql/file/content_C81136WLRFYZF8ION6Q57GWE1.html - detailed cursor example.
https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/dynamic.htm#i13057 - dynamic SQL in Oracle

Alternate of using NVL with IN clause in Oracle 11g

I have a workplace problem to which I am looking for an easy solution.
I am trying to replicate it in a smaller scenario.
Problem in short
I want to use nvl inside an in clause. Currently I have an input string which consists of a name. It is used in a where clause like below
and column_n = nvl(in_parameter,column_n)
Now I want to pass multiple comma separated values in same input parameter. So if I replace = with in, and transpose the input comma separated string as rows, I cannot use the nvl clause with it.
Problem in Detail
Lets consider an Employee table emp1.
Emp1
+-------+-------+
| empno | ename |
+-------+-------+
| 7839 | KING |
| 7698 | BLAKE |
| 7782 | CLARK |
+-------+-------+
Now this is a simple version of an existing stored procedure
create or replace procedure emp_poc(in_names IN varchar2)
as
cnt integer;
begin
select count(*)
into cnt
from emp1
where
ename = nvl(in_names,ename); --This is one of the condition where we will make the change.
dbms_output.put_line(cnt);
end;
So this will give the count of number of employees passed as Input Parameter. But if we pass null, it will return the whole count of employee becuase of the nvl.
So these procedure calls will render the given outputs.
Procedure Call Output
exec emp_poc('KING') 1
exec emp_poc('JOHN') 0
exec emp_poc(null) 3
Now what I want to achieve is to add another functionality. So exec emp_poc('KING,BLAKE') should give me 2. So I figured a way to split the comma separated string to rows and used that in the procedure.
So if I change the where clause as below to in
create or replace procedure emp_poc2(in_names IN varchar2)
as
cnt integer;
begin
select count(*)
into cnt
from emp1
where
ename in (select trim(regexp_substr(in_names, '[^,]+', 1, level))
from dual
connect by instr(in_names, ',', 1, level - 1) > 0
);
dbms_output.put_line(cnt);
end;
So exec emp_poc2('KING','BLAKE') gives me 2. But passing null will give result as 0. However I want to get 3 like the case with emp_proc
And I cannot use nvl with in as it expect the subquery to return a single value.
1 way I can think of is rebuilding the whole query in a variable, based in input paramteter, and then use execute immediate. But I am using some variables to collect the value and it would be difficult to achieve it with execute immediate.
I am again emphasizing that this is a simple version of a complex procedure where we are capturing many variables and it joins many tables and has multiple AND conditions in where clause.
Any ideas on how to make this work.
This may help you
CREATE OR REPLACE PROCEDURE emp_poc2(in_names IN varchar2)
AS
cnt integer;
BEGIN
SELECT COUNT(*) INTO cnt
FROM emp1
WHERE
in_names IS NULL
OR ename IN (
SELECT TRIM(REGEXP_SUBSTR(in_names, '[^,]+', 1, level))
FROM dual
CONNECT BY INSTR(in_names, ',', 1, level - 1) > 0
);
dbms_output.put_line(cnt);
END;
Other way could be use IF ELSE or UNION ALL
If your real code is much more complex then your code readability might be greatly enhanced by using a proper collection type instead.
In the example below I have created an user defined type str_list_t that is a real collection of strings.
I also use common table expression (CTE) in the sql query to enhance the readability. In this simple example the CTE benefits for readability are not obvious but for all non-trivial queries it's a valuable tool.
Test data
create table emp1(empno number, empname varchar2(10));
insert into emp1 values(5437, 'GATES');
insert into emp1 values(5438, 'JOBS');
insert into emp1 values(5439, 'BEZOS');
insert into emp1 values(5440, 'MUSK');
insert into emp1 values(5441, 'CUBAN');
insert into emp1 values(5442, 'HERJAVEC');
commit;
Supporting data type
create or replace type str_list_t is table of varchar2(4000 byte);
/
Subprogram
create or replace function emp_count(p_emps in str_list_t) return number is
v_count number;
v_is_null_container constant number :=
case
when p_emps is null then 1
else 0
end;
begin
-- you can also test for empty collection (that's different thing than a null collection)
with
-- common table expression (CTE) gives you no benefit in this simple example
emps(empname) as (
select * from table(p_emps)
)
select count(*)
into v_count
from emp1
where v_is_null_container = 1
or empname in (select empname from emps)
;
return v_count;
end;
/
show errors
Example run
SQL> select 2 as expected, emp_count(str_list_t('BALLMER', 'CUBAN', 'JOBS')) as emp_count from dual
union all
select 0, emp_count(str_list_t()) from dual
union all
select 6, emp_count(null) from dual
;
EXPECTED EMP_COUNT
---------- ----------
2 2
0 0
6 6