Is it better to declare a commonly used lookup query as a package function or as a function (either returning rows or values or returning a sysref cursor)?
Whats the difference?
What are the best practices?
What are the other considerations?
Or is there another better way?
1) using a package cursor
cursor getLicenseStatus_cur (in_license_no varchar2) is
SELECT status, status_dt from tbl_license where licence_no=in_license_no;
--use:
OPEN getLicenseStatus_cur('123');
fetch getLicenseStatus_cur into l_status, l_status_dt;
EXIT WHEN sql%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('l_status= ' || l_status || ', l_status_dt= ' || l_status_dt);
close getLicenseStatus_cur('123');
2) or using a function and passing a rowtype
create or replace function getLicenseStatus(in_license_no varchar2(10))
RETURN tbl_license%ROWTYPE
as
output_rec tbl_license%ROWTYPE;
begin
SELECT * into output_rec
from tbl_license where licence_no=in_license_no;
return output_rec;
end;
--use
lic_rec users%ROWTYPE;
lic_rec := getLicenseStatus('123');
DBMS_OUTPUT.PUT_LINE('l_status= ' || lic_rec.status || ', l_status_dt= ' || lic_rec .status_dt);
3) or using a function and passing a sys refcursor
create or replace function getLicenseStatus(in_license_no varchar2(10))
return sys_refcursor as
v_curs sys_refcursor;
begin
open v_curs for SELECT status, status_dt from tbl_license where licence_no=in_license_no;
return v_curs;
end;
--use:
v_rc := getLicenseStatus('123');
fetch v_rc into l_status , l_status_dt;
exit when sql%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('l_status= ' || l_status || ', l_status_dt= ' || l_status_dt);
close v_rc ;
Since you know that the query should always return exactly 1 row, a function returning a %ROWTYPE makes much more sense. That takes care of throwing an exception if 0 rows are returned or if more than 1 row is returned which sounds to be the correct behavior. That makes the calling code simpler as well since you're not dealing with iterating over a cursor.
Related
I have a statement I'm trying to construct in Oracle 18c. The following line works fine:
Select JSON_VALUE(l_resp, '$.items[0].volumeInfo.industryIdentifiers[1].type')
into l_temp_var
from dual;
However, I have to vary the second index by a variable. The second index currently contains [1]. I tried using [i] defined as a numeric or varchar, but that doesn't work. How can I construct a Select JSON_VALUE statement so that it uses a variable?
Thanks for looking at this.
Use string concatination to build your index string. For example:
BEGIN
FOR i IN 1..10 LOOP
Select JSON_VALUE(l_resp, '$.items[0].volumeInfo.industryIdentifiers[' || i || '].type')
into l_temp_var
from dual;
-- Do something with the value in l_temp_var here
END LOOP;
END:
I couldn't make the concatenation work. I tried another approach. I had to put the "type" and "identifier" into a Json table.
--Obtain the NVP values of "industryIdentifiers" e.g. ISBN_10, ISBN_13 .
For rowz in
(select *
from json_table(l_resp, '$.items[0].volumeInfo.industryIdentifiers[*]'
columns (ii_type varchar2(512) path '$.type',
ii_identifier varchar2(512) path '$.identifier'
)
) j_ii_tab
)
Loop
/*
If rowz.ii_type = 'ISBN_10' Then
:P133_ISBN_10 := rowz.ii_identifier;
Elsif rowz.ii_type = 'ISBN_13' Then
:P133_ISBN_13 := rowz.ii_identifier;
End If ;
*/
dbms_output.put_line('ii_type: ' || rowz.ii_type);
dbms_output.put_line('ii_identifier: ' || rowz.ii_identifier);
End Loop rowz;
It might be helpful to see the Json data at: https://www.googleapis.com/books/v1/volumes?q=isbn:9781484204856
As I have mentioned in my question title below Mysql function returns null always :
CREATE DEFINER=`root`#`localhost` FUNCTION `nextCode`(tbl_name VARCHAR(30), prv_code VARCHAR(30)) RETURNS varchar(30) CHARSET utf8
READS SQL DATA
BEGIN
DECLARE nxtCode VARCHAR(30);
SELECT ds.prefix, ds.suffix, ds.is_used, ds.next_number, CHAR_LENGTH(ds.pattern)
INTO #prefix, #suffix, #isUsed, #nxtNum, #pLength
FROM ths_inventory.doc_sequnce ds WHERE ds.`table_name` = tbl_name;
SET nxtCode = CONCAT(#prefix, LPAD((CASE WHEN #isUsed
THEN
(ExtractNumber(prv_code) + 1)
ELSE
(#nxtNum)
END
), #pLength,'0'), #suffix);
RETURN nxtCode;
END
But once I change the below line :
CONCAT(#prefix, LPAD((CASE WHEN #isUsed
THEN
(ExtractNumber(prv_code) + 1)
ELSE
(#nxtNum)
END
), #pLength,'0'), #suffix)
To some static values like below :
CONCAT('PR', LPAD((CASE WHEN true
THEN
(ExtractNumber(prv_code) + 1)
ELSE
(5)
END
), 6,'0'), '')
function start returning values accordingly.
Here is how I call my function :
nextCode('item','PR000002');
UPDATE:
I defined this function to get the next possible code for Item table :
According to my requirement the next possible code should be PR000000005.
But instead of getting it, I always get empty result .
SELECT nextCode('item',(SELECT `code` FROM item ORDER BY id DESC LIMIT 1)) AS next_code;
Any help would be appreciable.
Run a query that uses the function, and then...
SELECT #prefix, #suffix, #isUsed, #nxtNum, #pLength;
...to inspect the values. The # prefix means these are user-defined variables, so they have session scope, not program scope, and will still hold their values after the funcfion executes.
This should help pinpoint your problem.
But, you have two other problems you will need to solve after that.
SELECT ... INTO does not set the target variables when no row matches the query, so once you fix your issue, you will get very wrong results if you pass in arguments that don't match anything.
To resolve this, the function needs to set all these variables to null before the SELECT ... INTO query.
SET #prefix = NULL, #suffix = NULL, #isUsed = NULL, #nxtNum = NULL, #pLength = NULL;
See https://dba.stackexchange.com/a/35207/11651.
Also, your function does not handle concurrency, so two threads trying to find the "next" value for the same table, concurrently, will produce the same answer, so you will need to insure that your code handles this correctly with unique constraints and transactions or other appropriate locks.
This is driving me crazy. I want to do simple comparison of a column and a variable but it just doesn't work. The following line always counts all of the tuples while I only need those as conditioned by the where clause.
SELECT count(*) INTO cnt from class where class.fid = fid;
It looks sooooo simple but I've been working on this for hours. The complete sql proc is
The big confusing thing is that if I replace fid with some hard coded ID (like 105) it gives a correct answer), but when I use fid it just doesn't work any more and returns count of all classes. For some reason, always class.fid = fid. When I use >, < or <>, 0 count is returned!
create or replace PROCEDURE pro_report2
AS
CURSOR c_dept IS select deptid, dname from department;
TYPE cur_typ IS REF CURSOR;
c1 cur_typ;
query_str1 VARCHAR2(200);
fid faculty.fid%type := 102;
fname faculty.fname%type;
cnt NUMBER;
BEGIN
FOR dept_row in c_dept LOOP
DBMS_OUTPUT.PUT_LINE('Dept.Name: ' || dept_row.dname);
DBMS_OUTPUT.PUT_LINE('Faculty Name' || chr(9)|| chr(9) || '0 Class' || chr(9) || chr(9) || '1 Class' || chr(9) || chr(9) || '2 Classes' || chr(9) || '>2 Classes');
DBMS_OUTPUT.PUT_LINE('-------------------------------------------------------------------------------');
--find all faculty in this department
query_str1 := 'select fid, fname from faculty where faculty.deptid = ' || to_char(dept_row.deptid);
open c1 for query_str1;
LOOP
FETCH c1 into fid, fname;
exit when c1%notfound;
DBMS_OUTPUT.PUT_LINE(fname);
SELECT count(*) INTO cnt from class where class.fid = fid;
DBMS_OUTPUT.PUT_LINE(to_char(cnt) || ' ' || to_char(fid));
END LOOP;
-- Spaces between departments
DBMS_OUTPUT.PUT_LINE(chr(10));
DBMS_OUTPUT.PUT_LINE(chr(10));
END LOOP;
END;
Thanks
I believe you need to rename or prefix your local variable fid as it unfortunately matches the column name in the table you are querying. The SQL engine is simply comparing fid = fid for each row, which will always be true (excepting nulls, but that's another story). Plus, it's harder to read your code when you have variables named the same as a column.
In PL/SQL there tends to be a convention to prefix local variables with a l_ (for local) so it's clear what the purpose is. However, any name other than a column name will suffice. Try:
l_fid faculty.fid%type := 102;
And then...
SELECT count(*) INTO cnt from class where class.fid = l_fid;
(Plus other appropriate replacements.)
I need to find a way to dump key/value pairs of PL/pgSQL function input parameters:
CREATE OR REPLACE FUNCTION _test(A text, B text)
...
raise info 'Params dump: %', _x;
...
when executed:
select _text('HELLO', 'WORLD');
the function raises info as follows:
'A = HELLO, B = WORLD'
Is there a way to dump input parameter key/value pairs into a variable?
It's possible if you can make the function VARIADIC with uniform argument types, and can print the array. You don't get argument names, since they don't have names, but you do get argument positions.
Otherwise no, it is not possible in PL/PgSQL, though it should be in other PLs like PL/Perl, PL/Python, etc.
It'd be quite nice to be able to get a RECORD with all the function arguments in it, so you could print it, feed it to the hstore extension, etc, but this isn't currently possible.
There is an awkward way of dumping input parameters :
create or replace function _tester(
_txt text,
_int int
) returns void
language 'plpgsql' as
$$
declare
_out text = '';
_rec record;
_func_name text = '_tester';
begin
for _rec in SELECT parameters.ordinal_position as _pos, parameters.parameter_name as _nm
FROM information_schema.routines
JOIN information_schema.parameters
ON routines.specific_name=parameters.specific_name
WHERE routines.routine_name = _func_name
ORDER BY parameters.ordinal_position
loop
if _rec._pos = 1 then
_out = _out || _rec._nm || ' = ' || $1::text || chr(10);
elsif _rec._pos = 2 then
_out = _out || _rec._nm || ' = ' || $2::text || chr(10);
end if;
end loop;
raise notice '%', _out;
end;
$$;
select _tester('A','1');
NOTICE: _txt = A
_int = 1
Notice that must add as many if/elsif as there are input parameters. Not sure if that part can be more concise.
I like to return only the first number of a text stored in a column of a database table.
User have put in page ranges into a field like 'p.2-5' or 'page 2 to 5' or '2 - 5'.
I am interested in the '2' here.
I tried to
SELECT SUBSTR(the_field, LOCATE('2', the_field, 1)) AS 'the_number'
FROM the_table
and it works. But how to get ANY number?
I tried
SELECT SUBSTR(the_field, LOCATE(REGEXP '[0-9], the_field, 1)) AS 'the_number'
FROM the_table
but this time I get an error.
Any ideas?
Just use REGEXP_SUBSTR():
SELECT REGEXP_SUBSTR(`the_field`,'^[0-9]+') AS `the_number` FROM `the_table`;
Notes:
I'm using MySQL Server v8.0.
This pattern assumes that the_field is trimmed. Otherwise, use TRIM() first.
REGEXP is not a function in MySQL, but something of an operator. Returns 1 if field matches the regular expression, or 0 if it does not. You cannot use it to figure out a position in a string.
Usage:
mysql> SELECT 'Monty!' REGEXP '.*';
-> 1
As for answer to the question: I don't think there is a simple way to do that using MySQL only. You would be better off processing that field in the code, or extract values before inserting.
For the specific case in the question. Where the String is {number}{string}{number}
there is a simple solution to get the first number. In our case we had numbers like 1/2,3
4-10
1,2
and we were looking for the first number in each row.
It turned out that for this case one can use convert function to convert it into number. MySQL will return the first number
select convert(the_field ,SIGNED) as the_first_number from the_table
or more hard core will be
SELECT
the_field,
#num := CONVERT(the_field, SIGNED) AS cast_num,
SUBSTRING(the_field, 1, LOCATE(#num, the_field) + LENGTH(#num) - 1) AS num_part,
SUBSTRING(the_field, LOCATE(#num, the_field) + LENGTH(#num)) AS txt_part
FROM the_table;
This was original post at source by Eamon Daly
What does it do?
#num := CONVERT(the_field, SIGNED) AS cast_num # try to convert it into a number
SUBSTRING(the_field, 1, LOCATE(#num, the_field) + LENGTH(#num) - 1) # gets the number by using the length and the location of #num in field
SUBSTRING(the_field, LOCATE(#num, the_field) + LENGTH(#num)) # finds the rest of the string after the number.
Some thoughts for future use
Its worth keeping another column which will hold the first number after you parsed it before insert it to the database. Actually this is what we are doing these days.
Edit
Just saw that you have text like p.2-5 and etc.. which means the above cannot work as if the string does not start with a number convert return zero
There's no built-in way that I know of, but here's a Mysql function you can define, this will do it (I didn't code for minus-signs or non-integers, but those could of course be added).
Once created, you can use it like any other function:
SELECT firstNumber(the_field) from the_table;
Here's the code:
DELIMITER //
CREATE FUNCTION firstNumber(s TEXT)
RETURNS INTEGER
COMMENT 'Returns the first integer found in a string'
DETERMINISTIC
BEGIN
DECLARE token TEXT DEFAULT '';
DECLARE len INTEGER DEFAULT 0;
DECLARE ind INTEGER DEFAULT 0;
DECLARE thisChar CHAR(1) DEFAULT ' ';
SET len = CHAR_LENGTH(s);
SET ind = 1;
WHILE ind <= len DO
SET thisChar = SUBSTRING(s, ind, 1);
IF (ORD(thisChar) >= 48 AND ORD(thisChar) <= 57) THEN
SET token = CONCAT(token, thisChar);
ELSEIF token <> '' THEN
SET ind = len + 1;
END IF;
SET ind = ind + 1;
END WHILE;
IF token = '' THEN
RETURN 0;
END IF;
RETURN token;
END //
DELIMITER ;