Count function rather than count(*) - sql

I am practicing for final exams coming up and in my database class we were thrown a question asking to count the rows of a table by creating a function and returning that number.
I know how to create functions but I am stuck on how to go about this.
Can you put Count(*) inside of the function with a Select statement and return it?

In this example, function accepts a parameter (table name) and returns number of rows it contains. DBMS_ASSERT is used to prevent possible SQL injection.
SQL> CREATE OR REPLACE FUNCTION f_cnt (par_table_name IN VARCHAR2)
2 RETURN NUMBER
3 IS
4 retval NUMBER;
5 BEGIN
6 EXECUTE IMMEDIATE
7 'select count(*) from ' || DBMS_ASSERT.sql_object_name (par_table_name)
8 INTO retval;
9
10 RETURN retval;
11 END;
12 /
Function created.
SQL> SELECT f_cnt ('dual') FROM DUAL;
F_CNT('DUAL')
-------------
1
SQL>

Try this.
CREATE OR REPLACE FUNCTION TotalRecords
RETURN number IS
total number;
BEGIN
SELECT count(*) into total
FROM Your_table;
RETURN total;
END;

Related

Store a list of certain numbers globally and use it as the value of parameter in a PL/SQL function

I have certain numbers eg., (1,2,3,7,8) which need to be stored in an object (let's say, l_list) and this l_list will used as the value of the parameter in a function while calling a select query.
Storing data globally:
l_list := (1,2,3,7,8);
Creating a function:
create or replace function f_get_result (p_list number)
return varchar2
as
l_result varchar2(100);
Begin
select l_code
into l_result
from table_1;
return l_result;
end f_get_result;
Calling the function in select query:
select f_get_result(l_list) from dual;
Till now I have tried to define the datatype of the parameter as type table and varray. But, it didn't work.
Here is the code which I used:
-- creating a type ---
CREATE OR REPLACE TYPE num_array AS TABLE OF varchar2(100);
-- the function ---
create or replace function fn_id_exp(p_branch in num_array)
return varchar2
is
txt varchar2(1000);
txt_1 varchar2(1000);
begin
for i in 1..p_branch.count loop
select branch_name into txt_1 from tbl_branch
where branch_code = p_branch(i);
txt := txt ||txt_1||chr(10);
end loop;
return txt;
end;
-- the select query ---
select fn_id_exp(num_array('100','200')) from dual;
expectation: select fn_id_exp(l_list) from dual; , need to use this much, num_array should not be there,
where l_list is an object which is storing certain numbers like (100,200,300).
Please, help me in this, I am bit new in this. Can somebody, give me an an appropriate example regards this?
Note: It doesn't matter where and what datatype these number are getting stored. Requirement is just to call all the numbers(1,2,3,7,8) in this:
select fn_id_exp(l_list) from dual;
Another option, which doesn't require you to use your own type, is to use Oracle's built-in types.
Here's an example; function returns employees that work in departments passed as a collection.
SQL> create or replace function f_test (par_deptno in sys.odcinumberlist)
2 return sys.odcivarchar2list
3 is
4 retval sys.odcivarchar2list;
5 begin
6 select ename
7 bulk collect into retval
8 from emp
9 where deptno in (select * from table(par_deptno));
10
11 return retval;
12 end;
13 /
Function created.
Testing:
SQL> select * from table(f_test(sys.odcinumberlist(10, 20)));
COLUMN_VALUE
--------------------------------------------------------------------------------
MILLER
KING
CLARK
FORD
ADAMS
SCOTT
JONES
SMITH
8 rows selected.
SQL>
Note that such a result might be more convenient than returning a string which "mimics" rows in a table (whose values are separated by line feed character), as you'd still might need to split them into rows if you wanted to further process the result.
You said:
expectation: select fn_id_exp(l_list) from dual; , need to use this much, num_array should not be there
But - why not? What's wrong with num_array (or whichever type name you'd use)? That's the syntax, you should follow it if you want to get the result. Silly requirements, in my opinion.
Anyway: you could pass a string of e.g. comma-separated numeric values such as in this example; you'll have to split them to rows. See if it helps.
SQL> create or replace function f_test (par_deptno in varchar2)
2 return sys.odcivarchar2list
3 is
4 retval sys.odcivarchar2list;
5 begin
6 select ename
7 bulk collect into retval
8 from emp
9 where deptno in (select regexp_substr(par_deptno, '[^,]+', 1, level)
10 from dual
11 connect by level <= regexp_count(par_deptno, ',') + 1
12 );
13
14 return retval;
15 end;
16 /
Function created.
Testing:
SQL> select * from table(f_test('10, 20'));
COLUMN_VALUE
--------------------------------------------------------------------------------
SMITH
JONES
CLARK
SCOTT
KING
ADAMS
FORD
MILLER
8 rows selected.
SQL>
Use a collection:
CREATE TYPE number_array AS TABLE OF NUMBER;
Then:
CREATE FUNCTION fn_id_exp(
p_branch in number_array
) return varchar2
IS
txt varchar2(1000);
BEGIN
SELECT LISTAGG(branch_name, CHR(10)) WITHIN GROUP (ORDER BY rn)
INTO txt
FROM (SELECT ROWNUM AS rn,
COLUMN_VALUE AS id
FROM TABLE(p_branch)) i
INNER JOIN tbl_branch b
ON (i.id = b.branch_code);
RETURN txt;
END;
/
Then:
select fn_id_exp(number_array(100,200)) from dual;
Given the sample data:
CREATE TABLE tbl_branch (branch_code, branch_name) AS
SELECT 100, 'Leafy Stem' FROM DUAL UNION ALL
SELECT 200, 'Big Twig' FROM DUAL;
Outputs:
FN_ID_EXP(NUMBER_ARRAY(100,200))
Leafy StemBig Twig
However
It may be simpler to not use a function and just use a query:
SELECT branch_name
FROM tbl_branch
WHERE branch_code IN (100, 200)
ORDER BY branch_code;
or, if you want to use the array::
SELECT branch_name
FROM tbl_branch
WHERE branch_code MEMBER OF number_array(100, 200)
ORDER BY branch_code;
or, pass in an array as a bind parameter (via JDBC, etc.) then:
SELECT branch_name
FROM tbl_branch
WHERE branch_code MEMBER OF :your_array
ORDER BY branch_code;
Which output:
BRANCH_NAME
Leafy Stem
Big Twig
fiddle
Update
If you want a static array then you can create a package:
CREATE PACKAGE static_data IS
FUNCTION getList RETURN NUMBER_ARRAY;
END;
/
With the body:
CREATE PACKAGE BODY static_data IS
list NUMBER_ARRAY;
FUNCTION getList RETURN NUMBER_ARRAY
IS
BEGIN
RETURN list;
END;
BEGIN
list := NUMBER_ARRAY(100, 200);
END;
/
then you can use it in your query:
select fn_id_exp(static_data.getlist()) from dual;
or
SELECT branch_name
FROM tbl_branch
WHERE branch_code MEMBER OF static_data.getlist()
ORDER BY branch_code;
fiddle

how to make a select query with functions pl sql

Function 1:
create or replace function get_books (l_id in number)
return varchar
is l_return varchar2(100);
begin
select books into l_return from people where id=l_id;
return l_return;
end
/
Function 2:
create or replace function get_author (l_id in number)
return varchar
is l_return varchar2(100);
begin
select author in l_return from authors where id=l_id;
return l_return;
end
/
I want to make a select with 2 functions, I want to display books and authors.
is it possible?
Yes, it is possible. For example:
select get_books (1) book,
get_author(1) author
from dual;
Presumably, both functions return a single value. If any of those selects (used in functions) returns more than one row, it'll fail.
As of the procedure: code you posted is invalid so I fixed it.
SQL> create or replace procedure get_categories (l_categories in varchar2,
2 l_id in number,
3 l_boolean out boolean,
4 l_error out varchar2)
5 is
6 begin
7 -- INSERT INTO categories (id, categories)
8 -- VALUES (l_categories, l_id);
9
10 l_boolean := true;
11 commit;
12 exception
13 when others
14 then
15 l_boolean := false;
16 l_error := sqlerrm;
17 end;
18 /
Procedure created.
As it returns 2 OUT parameters, you have to declare variables to accept those values (v_boolean and v_error). Furthermore, as you can't directly display Boolean value, use CASE and display a string instead.
Procedure can't be called as a function within the SQL SELECT statement so you have to use another PL/SQL piece of code; an anonymous PL/SQL block is OK.
SQL> set serveroutput on
SQL> declare
2 v_categories varchar2(10) := 'ABC';
3 v_id number := 1;
4 v_boolean boolean;
5 v_error varchar2(200);
6 begin
7 get_categories(l_categories => v_categories,
8 l_id => v_id,
9 l_boolean => v_boolean,
10 l_error => v_error
11 );
12
13 dbms_output.put_line(case when v_boolean then 'true' else 'false' end ||' - '||
14 v_error
15 );
16 end;
17 /
true -
PL/SQL procedure successfully completed.
SQL>

Return multiple values in varchar2

I want to return the values of several columns (With a function) in a varchar2 but I get an error when I choose several columns in select.
FUNCTION FU_PRUEBAS (P_IDNUM MAC.IDNUM%TYPE) RETURN VARCHAR2 IS REGISTRO VARCHAR2(100);
BEGIN
select NOMBRES, FECHANACIMIENTO INTO REGISTRO
from MAC WHERE IDNUM = P_IDNUM;
RETURN REGISTRO;
Error:
Error(17,6): PL/SQL: SQL Statement ignored
Error(18,6): PL/SQL: ORA-00947: there are not enough values
Two relatively simple options - if they suit your needs. You didn't really explain what you expect to get as a result so - this might, or might not be applicable to your situation.
Anyway, here you go:
First option uses listagg function which returns a concatenated string (as you wanted to return varchar2). Drawback is that it won't work for the result longer than 4000 characters, but - in that case - you can use XMLAGG.
SQL> create or replace function fu_pruebas (par_deptno in number)
2 return varchar2
3 is
4 retval varchar2(200);
5 begin
6 select listagg(ename, ', ') within group (order by ename)
7 into retval
8 from emp
9 where deptno = par_deptno;
10 return retval;
11 end;
12 /
Function created.
SQL> select fu_pruebas(10) result from dual;
RESULT
------------------------------------------------------------------------------
CLARK, KING, MILLER
SQL>
Another one concatenates different columns into a string; you might need to use e.g. to_char or to_date functions in certain cases, but - generally - it works as follows:
SQL> create or replace function fu_pruebas_2 (par_empno in number)
2 return varchar2
3 is
4 retval varchar2(200);
5 begin
6 select ename ||' - '|| job ||' - '|| sal
7 into retval
8 from emp
9 where empno = par_empno;
10 return retval;
11 end;
12 /
Function created.
SQL> select fu_pruebas_2(7654) result from dual;
RESULT
------------------------------------------------------------------------------
MARTIN - SALESMAN - 1250
SQL>
if you need a multiple values retunr you need a proper object
create or replace
type your_obj as object
( val_1 varchar2(200),
val_2 varchar2(200)
);
then
FUNCTION FU_PRUEBAS (P_IDNUM MAC.IDNUM%TYPE) RETURN VARCHAR2 IS REGISTRO VARCHAR2(100);
BEGIN
select NOMBRES, FECHANACIMIENTO INTO your_var1, your_var2
from MAC WHERE IDNUM = P_IDNUM
RETURN your_obj(your_var1, your_var2)
END;

PL / SQL Function to return varchar2 / numbers

I have this PL / SQL function that accepts the name of a student (f_name). The function then displays all of the information for the given student from a premade table called students. The table contains 5 columns, 2 number type, and 3 varchar2 type. If the name isn't found in the table an error message is returned. My code so far is
CREATE OR REPLACE FUNCTION studentName(
f_name IN VARCHAR2)
RETURN
IS
v_test students%rowtype;
CURSOR c1
IS
SELECT * FROM students WHERE first_name = f_name;
BEGIN
OPEN c1;
FETCH c1 INTO v_test;
IF c1%notfound THEN
v_test := NULL;
END IF;
CLOSE c1;
RETURN v_test;
END;
I keep getting:
PLS-00382: expression is of wrong type
I believe from my initial return varchar2 statement. How do I allow the return to accept both varchar2 type and number type?
RETURN varchar2
You need to return the rowtype, but you are returning a scalar. VARCHAR2 cannot hold a row, it can hold only a string value.
Modify it to:
RETURN students%rowtype;
Demo using standard EMP table:
SQL> CREATE OR REPLACE FUNCTION studentName(
2 f_name IN VARCHAR2)
3 RETURN emp%rowtype
4 IS
5 v_test emp%rowtype;
6 CURSOR c1
7 IS
8 SELECT * FROM emp WHERE ename = f_name;
9 BEGIN
10 OPEN c1;
11 FETCH c1 INTO v_test;
12 IF c1%notfound THEN
13 v_test := NULL;
14 END IF;
15 CLOSE c1;
16 RETURN v_test;
17 END;
18 /
Function created.
SQL> sho err
No errors.
NOTE : %ROWTYPE implies PL/SQL record type and PL/SQL types are not known to SQL. So you won't be able to use the function directly in plain SQL. You need to use SQL object type. Else you will get:
ORA-06553: PLS-801: internal error [55018]
Workaround to use it in SQL:
SQL> create or replace
2 type student_obj_type
3 as object(
4 student_id number,
5 stu_name varchar2(20),
6 dept varchar2(20)
7 )
8 /
Type created.
Use student_obj_type instead of students%rowtype to use the function in SQL.

How can I write this procedure differently

I want to write the following procedure differently so i can call it to return data as if it were a table by doing: SELECT * FROM table(package.get7DayCapacityDemandProv(1, sysdate))
Procesdure:
PROCEDURE get7DayCapacityDemandProv(p_H_id IN work_entity_data.H_id%TYPE
,p_date IN DATE
,p_capacity_day_1 OUT NUMBER
,p_demand_day_1 OUT NUMBER
,p_capacity_day_2 OUT NUMBER
,p_demand_day_2 OUT NUMBER
,p_capacity_day_3 OUT NUMBER
,p_demand_day_3 OUT NUMBER
,p_capacity_day_4 OUT NUMBER
,p_demand_day_4 OUT NUMBER
,p_capacity_day_5 OUT NUMBER
,p_demand_day_5 OUT NUMBER
,p_capacity_day_6 OUT NUMBER
,p_demand_day_6 OUT NUMBER
,p_capacity_day_7 OUT NUMBER
,p_demand_day_7 OUT NUMBER
)
IS
BEGIN
getCapacityDemandOnDayProvider(p_H_id
,p_date
,p_capacity_day_1
,p_demand_day_1
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 1
,p_capacity_day_2
,p_demand_day_2
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 2
,p_capacity_day_3
,p_demand_day_3
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 3
,p_capacity_day_4
,p_demand_day_4
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 4
,p_capacity_day_5
,p_demand_day_5
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 5
,p_capacity_day_6
,p_demand_day_6
);
getCapacityDemandOnDayProvider(p_H_id
,p_date + 6
,p_capacity_day_7
,p_demand_day_7
);
END get7DayCapacityDemandProv;
You want to (1) convert this to a function that returns a record, and then (2) convert that to a pipelined function.
Here's an example. I've left out the first parameter just so I could easily run it, but you can just add that back in.
create or replace package test
as
type theRecordType is record (
day date,
capacity number,
demand number
);
type theTableType is table of theRecordType;
function getData(p_date DATE) return theTableType pipelined;
end test;
/
create or replace package body test
as
function getData(p_date DATE) return theTableType pipelined
as
theRecord theRecordType;
begin
for i in 0..6 loop
theRecord.date := p_date + i;
theRecord.capacity := i;
theRecord.demand := i+1;
--
-- you would have a call to your procedure instead of the above two lines
-- getCapacityDemandOnDayProvider(p_H_id
-- ,theRecord.date
-- ,theRecord.capacity
-- ,theRecord.demand
-- );
--
pipe row (theRecord);
end loop;
return;
end getData;
end test;
/
You can now select from the function and get one row for each day.
select * from table(test.getData(SYSDATE));
I made it a package so the types could be declared within the package header. Alternatively, you could keep it a standalone function and declare the types within the schema using CREATE TYPE.
This is off the cuff, so it is not going to be 100% syntactically correct, but it will be conceptually correct.
create or replace package bingo IS
TYPE bingoCursor is REF CURSOR;
function get7Days(
bingoId IN bingoTable.bingoId%TYPE,
bingoDate IN date)
return bingoCursor;
end bingo;
create or replace package body bingo IS
function get7Days(
bingoId IN bingoTable.bingoId%TYPE,
bingoDate IN date)
return bingoCursor IS
sevenDaysContent bingoCursor;
begin
open sevenDaysContent for
select day 1 stuff;
union all
select day 2 stuff;
union all
... select and union all days 3 - day 7;
return sevenDaysContent;
end get7Days;
end bingo;
Sounds like you want a function or a view. A procedure's returned data can be caught and used within a SQL script but it requires dumping it into a temp or variable table first.
If your requirement is to be able to just do a SELECT * FROM something then you probably want a function or a view.
You could create a function that returns a sys_refcursor. An overly simple example:
create or replace function BLAH(somevar in varchar2) return sys_refcursor IS
v_result_cur sys_refcursor;
v_query varchar2(4000);
...
begin
...
v_query := 'select field1, field2 from blah';
...
open v_result_cur for v_query;
return v_result_cur;
...
exception
...
end;
But I will say this isn't typical. I would probably use a view (or materialized view), or turn those inner procedures into functions and simply select them, like:
select
FN_getCapacityDemandOnDayProvider(someVars) as Day1Val,
FN_getCapacityDemandOnDayProvider(someVars2) as Day2Val
from dual;