Stored Procedures (SP) and User-Defined Functions (UDF)
^ MySQL
Calling MySQL Stored Procedures from JDBC | http://www.mysqltutorial.org/calling-mysql-stored-procedures-from-jdbc/ |
PHP MySQL: Call MySQL Stored Procedures | http://www.mysqltutorial.org/php-calling-mysql-stored-procedures/ |
Python MySQL – Call Stored Procedures in Python | http://www.mysqltutorial.org/calling-mysql-stored-procedures-python/ |
MySQL - Returning Single Value from Stored Procedure | https://wiki.ispirer.com/sqlways/mysql/techniques/return-value-from-procedure |
Google: mysql function return table | You cannot return table from MySQL function. The function
can return string, integer, char etc. To return table from MySQL,
use stored procedure, not function. https://www.tutorialspoint.com/how-to-return-table-from-mysql-function |
^ Prerequisites for testing
DDL of data tables used in MySQL examples below:
CREATE TABLE `_projects` ( `p_id` int NOT NULL AUTO_INCREMENT, `p_name` varchar(45) NOT NULL, PRIMARY KEY (`p_id`) ); CREATE TABLE `_tests` ( `t_id` int NOT NULL AUTO_INCREMENT, `t_date` datetime NOT NULL, `t_name` varchar(45) NOT NULL, `t_rating` float NOT NULL, `p_id` int NOT NULL, PRIMARY KEY (`t_id`), KEY `fk_project_tests_idx` (`p_id`), CONSTRAINT `fk_project_tests` FOREIGN KEY (`p_id`) REFERENCES `_projects` (`p_id`) );
It is impossible to obtain JDBC meta-data of Result-Set returned by something like 'call get_some_table(?, ?)'. As workaround, code generator obtains field info from XML declaration of DTO class:
<dto-class name="Test" ref="tests" />
or
<dto-class name="Test" ref="" > <field column="t_id" type="int" /> <field column="t_date" type="java.util.Date" /> <field column="t_name" type="String" /> <field column="t_rating" type="java.math.BigDecimal" /> </dto-class>
Also, you may need to specify explicitly the types of method parameters and return values:
<query method="get_test_rating(int t_id)" return-type="java.math.BigDecimal" ref="select get_test_rating(?)" />
^ MySQL > UDF returning a scalar value
CREATE FUNCTION `get_test_rating`(pt_id int) RETURNS float DETERMINISTIC BEGIN DECLARE res numeric(10, 2); select t_rating into res from _tests where t_id = pt_id; RETURN res; END
<query method="get_test_rating(int t_id)" return-type="java.math.BigDecimal" ref="select get_test_rating(?)" />
PHP |
print_r($dao->get_test_rating(314)); |
Java |
out.println(dao.get_test_rating(314)); |
Python |
print(dao.get_test_rating(314)) |
Go |
r := tDao.udfGetTestRating(3) fmt.Println(r) |
^ MySQL > SP returning single Result-Set
CREATE PROCEDURE `get_tests_by_rating`(in p_rating float) BEGIN select * from _tests where t_rating >= p_rating; END
<query-dto-list method="get_tests_by_rating(double t_rating)" dto="Test" ref="call get_tests_by_rating(?)" />
PHP |
foreach ($dao->get_tests_by_rating(2) as $t) { // ... } |
Java |
for (Test t : dao.get_tests_by_rating(2)) { // ... } |
Python |
for test in dao.get_tests_by_rating(2): # ... |
Go |
tests := tDao.spGetTestsByRating(2) for _, t := range tests { // ... } |
^ MySQL > SP returning multiple Result-Sets
CREATE PROCEDURE `sp_get_all`(rating float) BEGIN select distinct p.* from _projects p inner join _tests t on t.p_id = p.p_id where t.t_rating > rating; select * from _tests where t_rating > rating order by t_rating; END
<exec-dml method="sp_get_all(float rating, [on_project:Project, on_test:Test])" ref="call sp_get_all(?)" />
PHP |
$dao->sp_get_all(3, function($project) { print_r($project->getP_name()); }, function($test) { print_r($test->getT_name()); }); |
Java |
dao.sp_get_all(3, project -> { out.println(project.getP_name()); }, test -> { out.println(test.getT_rating()); }); |
Python |
dao.sp_get_all(3, lambda p: print('Project: {}\t\t{}'.format(p.p_id, p.p_name)), lambda t: print('Task: {}\t\t{}\t\t{:10.2f}\t\t{}'.format(t.t_id, t.t_date, t.t_rating, t.t_name)),) |
Go |
tDao.spGetAll(3, func(p *Projects) { projects = append(projects, p) }, func(t *Test) { tests = append(tests, t) } |
^ MySQL > SP to change some data
CREATE PROCEDURE `set_test_rating`(in p_t_id int, in p_rating float) BEGIN update _tests set t_rating = p_rating where t_id = p_t_id; END
<exec-dml method="set_test_rating(int t_id, double t_rating)" ref="call set_test_rating(?, ?)" />
PHP |
$dao->set_test_rating(314, 5); |
Java |
dao.set_test_rating(314, 5); |
Python |
dao.set_test_rating(314, 5) |
Go |
tDao.spSetTestRating(314, 5) |
^ MySQL > OUT and INOUT parameters
CREATE PROCEDURE `sp_get_film_title`(p_film_id int, out p_title varchar(200)) BEGIN select title into p_title from film where film_id = p_film_id; END
CREATE PROCEDURE `sp_test_in_out_params`(inout res int) BEGIN set res = res * 10; END
<exec-dml method="sp_get_film_title(film_id, title)" ref="call sp_get_film_title(?, ?)" />
<exec-dml method="sp_test_in_out_params(res)" ref="call sp_test_in_out_params(?)" />
PHP |
$res1 = new OutParam(); $dao->sp_get_film_title(1, $res1); print_r($res1->value); $res2 = new InOutParam(10); $dao->sp_test_in_out_params($res2); print_r($res2->value); |
Java |
OutParam title = new OutParam(Types.NCHAR, String.class); dao.sp_get_film_title(1, title); out.println(title.getValue()); |
Python |
""" COMMENT FROM SOURCES OF MySQL Connector => cursor.py: For OUT and INOUT parameters the user should provide the type of the parameter as well. The argument should be a tuple with first item as the value of the parameter to pass and second argument the type of the argument. """ title = OutParam() title.value = (None, 'CHAR') # ...but just None is also OK in here dao.sp_get_film_title(1, title) print(title.value) |
Go |
<exec-dml method="SpGetFilmTitleInit(int32 film_id)" ref="set @film_id=?"/> <exec-dml method="SpGetFilmTitle()" ref="call sp_get_film_title(@film_id, @title)"/> <query method="SpGetFilmTitleQuery()" ref="select @title" return-type="string"/> <exec-dml method="SpTestInOutParamsInit(int32 res)" ref="set @res=?"/> <exec-dml method="SpTestInOutParams()" ref="call sp_test_in_out_params(@res)"/> <query method="SpTestInOutParamsQuery()" ref="select @res" return-type="int64"/> tDao.SpGetFilmTitleInit(5) tDao.SpGetFilmTitle() res := tDao.SpGetFilmTitleQuery() fmt.Println(res)/> tDao.SpTestInOutParamsInit(123) tDao.SpTestInOutParams() title := tDao.SpTestInOutParamsQuery() fmt.Println(title) |
^ Oracle 12c+
Oracle: return a »table« from a function | https://renenyffenegger.ch/notes/development/databases/Oracle/PL-SQL/collection-types/return-table-from-function/index |
Oracle 12c: Creating users without container database | https://jaykhimani.blogspot.com/2015/12/oracle-12c-creating-users-without.html |
Using Python With Oracle Database 11g | https://www.oracle.com/ocom/groups/public/@otn/documents/webcontent/1521398.htm |
Oracle Database for Python Developers, Part 1: cx_Oracle Quick Start | https://developer.oracle.com/databases/database-for-python-developers-1.html |
Using Ref Cursors To Return Recordsets | https://oracle-base.com/articles/misc/using-ref-cursors-to-return-recordsets |
Implicit Statement Results in Oracle Database 12c Release 1 (12.1) | https://oracle-base.com/articles/12c/implicit-statement-results-12cr1 |
Using PHP and ORACLE 12c Implicit Result Sets | https://blogs.oracle.com/opal/using-php-and-oracle-database-12c-implicit-result-sets |
Showing Equipment Records by Using a REF CURSOR | https://docs.oracle.com/cd/E17781_01/appdev.112/e18555/ch_six_ref_cur.htm#TDPPH160 |
Python Oracle (cx-oracle) | https://www.oracletutorial.com/python-oracle/ |
Using Bind Variables (cx-oracle) | https://cx-oracle.readthedocs.io/en/latest/user_guide/bind.html |
PL/SQL Execution (cx-oracle) | https://cx-oracle.readthedocs.io/en/latest/user_guide/plsql_execution.html |
Procedures of Oracle in php with PDO | https://stackoverflow.com/questions/8121244/procedures-of-oracle-in-php-with-pdo |
Calling the REF CURSOR in PHP (OCI) | https://docs.oracle.com/cd/E17781_01/appdev.112/e18555/ch_six_ref_cur.htm#TDPPH162 |
PHP + OCI: Binding a REF CURSOR in an Oracle stored procedure call | https://www.php.net/manual/en/function.oci-new-cursor.php |
How to return rows from a declare/begin/end block in Oracle? | https://stackoverflow.com/questions/3371408/how-to-return-rows-from-a-declare-begin-end-block-in-oracle |
PHP + PDO + Oracle | https://blogs.oracle.com/opal/converting-ref-cursor-to-pipe-for-performance-in-php-oci8-and-pdooci ==> https://www.oracle.com/database/technologies/underground-php-oracle-manual.html ==> https://www.oracle.com/technetwork/database/database-technologies/php/201212-ug-php-oracle-1884760.pdf Oracle does not contribute to PDO_OCI. The PHP community has let the PDO project languish and Oracle recommends using OCI8 instead whenever possible because of its better feature set, performance, reliability and stability. Use of PDO_OCI for general purpose applications is not recommended. However PDO is used by some frameworks and higher level packages, such as content management systems so you may need to use it. |
^ Prerequisites for testing
CREATE TABLE "PROJECTS" ( "P_ID" NUMBER GENERATED ALWAYS AS IDENTITY, "P_NAME" VARCHAR2(200) NOT NULL, CONSTRAINT "PROJECTS_PK" PRIMARY KEY ("P_ID") ); CREATE TABLE "TESTS" ( "T_ID" NUMBER GENERATED ALWAYS AS IDENTITY, "T_DATE" DATE NOT NULL, "T_NAME" VARCHAR2(200) NOT NULL, "T_RATING" NUMBER(10,2) NOT NULL, "P_ID" INT NOT NULL, CONSTRAINT "TEST_PK" PRIMARY KEY ("T_ID") ); CREATE OR REPLACE NONEDITIONABLE TYPE TYPE_OF_TEST AS OBJECT ( T_ID INT, T_DATE DATE, T_NAME VARCHAR2(200), T_RATING NUMERIC(10,2) ); CREATE OR REPLACE NONEDITIONABLE TYPE TYPE_OF_TEST_TABLE AS TABLE OF TYPE_OF_TEST;
^ Oracle > UDF returning a scalar value
CREATE OR REPLACE NONEDITIONABLE FUNCTION GET_TEST_RATING(P_T_ID IN INT) RETURN NUMBER AS RES NUMERIC(10,2); BEGIN SELECT T_RATING INTO RES FROM TESTS WHERE T_ID = P_T_ID; RETURN RES; END GET_TEST_RATING;
Python + cx_Oracle |
<query method="get_test_rating(t_id)" ref="select GET_TEST_RATING(?) from dual" /> print(dao.get_test_rating(642)) |
Go |
<query method="get_test_rating(t_id)" ref="select GET_TEST_RATING(?) from dual" /> r := cxDao.get_test_rating(47) fmt.Println(r) |
^ Oracle > UDF returning TABLE
1) How to get single DTO from UDF returning TABLE
CREATE OR REPLACE NONEDITIONABLE FUNCTION GET_TEST(P_T_ID IN INT) RETURN TYPE_OF_TEST_TABLE AS RES TYPE_OF_TEST_TABLE; BEGIN RES := TYPE_OF_TEST_TABLE(); SELECT TYPE_OF_TEST(T_ID, T_DATE, T_NAME, T_RATING) BULK COLLECT INTO RES FROM TESTS WHERE T_ID = P_T_ID; RETURN RES; END GET_TEST;
<query-dto method="get_test(t_id)" dto="Test" ref="select * from GET_TEST(?)" />
Python + cx_Oracle |
t = Test() dao.get_test(642, t) print(t.t_rating) |
Go |
t := dao.getTest(642) print(t.TTRating) |
2) How to get result of UDF as a list of DTO:
CREATE OR REPLACE NONEDITIONABLE FUNCTION GET_TESTS_BY_RATING(P_T_RATING IN NUMERIC) RETURN TYPE_OF_TEST_TABLE AS RES TYPE_OF_TEST_TABLE; BEGIN RES := TYPE_OF_TEST_TABLE(); SELECT TYPE_OF_TEST(T_ID, T_DATE, T_NAME, T_RATING) BULK COLLECT INTO RES FROM TESTS WHERE T_RATING >= P_T_RATING; RETURN RES; END GET_TESTS_BY_RATING;
<query-dto-list method="get_tests_by_rating(rating)" dto="Test" ref="select * from get_tests_by_rating(?)" />
Python + cx_Oracle |
for p in dao.get_tests_by_rating(2): # ... |
Go |
tests := cxDao.getTestsByRating(2) for _, r := range tests { // ... } |
^ Oracle > UDF returning SYS_REFCURSOR
CREATE OR REPLACE FUNCTION GET_RC(P_RATING IN NUMBER) RETURN SYS_REFCURSOR AS my_cursor SYS_REFCURSOR; BEGIN OPEN my_cursor FOR SELECT * FROM tests WHERE t_rating >= p_rating ORDER BY t_rating; RETURN my_cursor; END GET_RC;
<exec-dml method="get_rc(on_test:Test, p_rating)" ref="BEGIN ?:= GET_RC(?); END;" />
PHP + OCI8 |
$on_test = function($test) { // process DTO }; $dao->get_rc($on_test, 2); |
Java |
RecordHandler<Test> on_test = new RecordHandler<Test>() { @Override public void handle(Test t) throws Exception { // process DTO } }; dao.get_rc(on_test, 2); |
Python + cx_Oracle |
def on_test(test): # process DTO dao.get_rc(on_test, 2) |
Go |
onTest := func(t *Test) { // ... } dao.getRc(onTest, 2) |
^ Oracle > SYS_REFCURSOR-s as OUT parameters
CREATE OR REPLACE PROCEDURE SP_GET_RS_TEST (P_RATING IN NUMBER, P_RECORDSET OUT SYS_REFCURSOR) AS BEGIN OPEN P_RECORDSET FOR SELECT * FROM TESTS WHERE T_RATING >= P_RATING ORDER BY T_RATING; END SP_GET_RS_TEST;
CREATE OR REPLACE NONEDITIONABLE PROCEDURE SP_2_RC(p_rating IN NUMBER, P_TASKS OUT SYS_REFCURSOR, P_PROJECTS OUT SYS_REFCURSOR) AS rc1 SYS_REFCURSOR; rc2 SYS_REFCURSOR; BEGIN OPEN rc1 FOR SELECT * FROM Tests WHERE t_rating >= p_rating ORDER BY t_rating; P_TASKS := rc1; OPEN rc2 FOR SELECT * FROM projects; P_PROJECTS := rc2; END SP_2_RC;
<exec-dml method="sp_get_rs_test(p_rating, on_test:Test)" ref="begin SP_GET_RS_TEST(?, ?); end;" />
<exec-dml method="sp_2_rc(p_rating, on_test:Test, on_project:Project)" ref="BEGIN SP_2_RC(?, ?, ?); END;"/>
PHP + OCI8 |
$on_test = function($test) { // process DTO }; $dao->sp_get_rs_test(2, $on_test); $on_project = function($project) { // process DTO $project }; $dao->sp_2_rc(2, $on_test, $on_project); |
Java |
RecordHandler<Test> on_test = new RecordHandler<Test>() { @Override public void handle(Test t) throws Exception { // process DTO } }; dao.sp_get_rs_test(2, on_test); RecordHandler<Project> on_project = new RecordHandler<Project>() { @Override public void handle(Project project) throws Exception { // process DTO project } }; dao.sp_2_rc(2, on_test, on_project); |
Python + cx_Oracle |
def on_test(test): # process DTO $dao.sp_get_rs_test(2, on_test) def on_project(project): # process DTO project dao.sp_2_rc(2, on_test, on_project) |
Go |
onTest := func(t *Test) { // ... } onProject := func(t *Project) { // ... } dao.get2Rc(2, onTest, onProject) |
^ Oracle > Implicit SYS_REFCURSOR-s
Starting from Oracle 12c1, you can use SYS_REFCURSOR-s implicitly:
CREATE OR REPLACE NONEDITIONABLE PROCEDURE SP_IMPLICIT_RC (P_RATING IN NUMBER) AS rc SYS_REFCURSOR; BEGIN OPEN rc FOR SELECT * FROM TESTS WHERE T_RATING >= P_RATING ORDER BY T_RATING; DBMS_SQL.RETURN_RESULT(rc); END SP_IMPLICIT_RC;
CREATE OR REPLACE NONEDITIONABLE PROCEDURE SP_IMPLICIT_2_RC (P_P_ID IN NUMBER) AS rc1 SYS_REFCURSOR; rc2 SYS_REFCURSOR; BEGIN OPEN rc1 FOR SELECT * FROM TESTS WHERE P_ID = P_P_ID ORDER BY T_RATING; DBMS_SQL.RETURN_RESULT(rc1); OPEN rc2 FOR SELECT * FROM PROJECTS WHERE P_ID = P_P_ID; DBMS_SQL.RETURN_RESULT(rc2); END SP_IMPLICIT_2_RC;
To fetch implicit SYS_REFCURSOR-s, use the syntax like this:
<exec-dml method="sp_implicit_rc(p_id, [on_test:Test])" ref="begin SP_IMPLICIT_RC(?); end;" />
<exec-dml method="sp_implicit_2_rc(p_id, [on_test:Test, on_project:Project])" ref="begin SP_IMPLICIT_2_RC(?); end;" />
PHP + OCI8 |
$on_test = function($test) { // process DTO }; $dao->sp_implicit_rc(2, $on_test); $on_project = function($project) { // process DTO }; $dao->sp_implicit_2_rc(2, $on_test, $on_project); |
Java |
To use implicit SYS_REFCURSOR in Java, ensure that ojdbc7.jar conforms Oracle 12c1+. dao.sp_implicit_rc(2, test -> { // process DTO }); dao.sp_implicit_2_rc(2, test -> { // process DTO }, project -> { // process DTO }); |
Python + cx_Oracle |
def on_test(test): # process DTO dao.sp_implicit_rc(2, on_test) def on_project(project): # process DTO dao.sp_implicit_2_rc(2, on_test, on_project) |
Go |
onTest := func(test *Test) { // process DTO } onProject := func(project *Project) { // process DTO } dao.SpImplicitRc(2, onTest, onProject) NOTE. Multiple implicit RC are not available yet. |
^ Oracle > SP to change some data
CREATE OR REPLACE NONEDITIONABLE PROCEDURE SET_TEST_RATING (P_T_ID IN INT, P_T_RATING IN NUMBER) AS BEGIN UPDATE TESTS SET T_RATING = P_T_RATING WHERE T_ID = P_T_ID; END SET_TEST_RATING;
Python + cx_Oracle
<exec-dml method="set_test_rating(t_id, rating)" ref="begin SET_TEST_RATING(?, ?); end;" />
dao.set_test_rating(642, 5)
^ Oracle > OUT and INOUT parameters
create or replace NONEDITIONABLE PROCEDURE SP_TEST_OUT_PARAMS(P_ID IN NUMBER, P_RATING OUT NUMBER) AS id number; BEGIN id := P_ID; -- failed while using WHERE t_id = P_ID SELECT t_rating INTO P_RATING FROM tests WHERE t_id = id; END SP_TEST_OUT_PARAMS;
create or replace PROCEDURE SP_TEST_INOUT_PARAMS(PARAM1 IN OUT NUMBER) AS BEGIN PARAM1 := PARAM1 * 10; END SP_TEST_INOUT_PARAMS;
<exec-dml method="sp_test_inout_params(res)" ref="begin SP_TEST_INOUT_PARAMS(?); end;"/> <exec-dml method="sp_test_out_params(t_id, out_rating)" ref="begin SP_TEST_OUT_PARAMS(?, ?); end;"/>
PHP + PDO |
// There isn't any PDO::PARAM for decimals / floats, you'll have to use PDO::PARAM_STR. // https://stackoverflow.com/questions/2718628/pdoparam-for-type-decimal $res1 = new OutParam(PDO::PARAM_STR, "10"); $dao->sp_test_inout_params($res1); print_r($res1->value); // OUT is 100 $res2 = new OutParam(PDO::PARAM_STR, "0"); // out parameters must be initialized anyway $dao->sp_test_out_params(642, $res2); print_r($res2->value); |
PHP + OCI8 |
$res1 = new OutParam(SQLT_CHR, 10); // IN is 10 $dao->sp_test_inout_params($res1); print_r($res1->value); $res2 = new OutParam(); // no need to init OUT param with a value $dao->sp_test_out_params(642, $res2); print_r($res2->value); |
Java |
OutParam in_out_param = new OutParam(OracleTypes.NUMBER, BigDecimal.class, 10); // IN is 10 dao.sp_test_inout_params(in_out_param); out.println(in_out_param.getValue()); // prints 100 OutParam out_rating = new OutParam(OracleTypes.NUMBER, BigDecimal.class); // no need to init OUT param with a value dao.sp_test_out_params(642, out_rating); out.println(out_rating.getValue()); |
Python + cx_Oracle |
res2 = OutParam(int, 10) # IN is 10 dao.sp_test_inout_params(res2) print(res2.param_value) res1 = OutParam(float) # no need to init OUT param with a value dao.sp_test_out_params(642, res1) print(res1.param_value) |
Go |
<exec-dml method="SpTestInoutParams(InOutParam inOut)" ref="begin SP_TEST_INOUT_PARAMS(?); end;"/> <exec-dml method="SpTestOutParams(int64 tId, *float64 outRating)" ref="begin SP_TEST_OUT_PARAMS(?, ?); end;"/> <exec-dml method="SpTestOutParams2(int64 tId, OutParam outRating)" ref="begin SP_TEST_OUT_PARAMS(?, ?); end;"/> inOut := 123.0 cxDao.SpTestInoutParams(InOutParam{Dest: &inOut}) fmt.Println(inOut) var outRating float64 cxDao.SpTestOutParams(47, &outRating) // cxDao.SpTestOutParams2(47, OutParam{Dest: &outRating}) // This one works too. fmt.Println(outRating) |
^ PostgreSQL
^ Notes
NOTE 1. Both stored procedures and user-defined functions are created with CREATE FUNCTION statement in PostgreSQL.
NOTE 2. PostgreSQL JDBC Driver is able to detect the types of SQL parameters.
^ Prerequisites for testing
DDL of data table used in PostgreSQL examples below:
CREATE TABLE IF NOT EXISTS public.projects ( p_id serial NOT NULL, p_name character varying(200), CONSTRAINT projects_pkey PRIMARY KEY (p_id) ); CREATE TABLE IF NOT EXISTS public.tests ( t_id serial NOT NULL, t_date timestamp without time zone NOT NULL, t_name character varying(200), t_rating numeric(10,2) NOT NULL, p_id integer NOT NULL, CONSTRAINT tests_pkey PRIMARY KEY (t_id) );
^ PostgreSQL > UDF returning a scalar value
<query method="get_test_rating(t_id)" return-type="java.math.BigDecimal" ref="select get_test_rating(?)" />
CREATE OR REPLACE FUNCTION public.get_test_rating(p_t_id integer) RETURNS numeric LANGUAGE 'plpgsql' AS $BODY$DECLARE res NUMERIC; BEGIN select t_rating into res from tests where t_id = p_t_id; RETURN res; END;$BODY$;
Python |
t = Test() dao.get_test(642, t) print(t.t_rating) |
Go |
t := dao.getTest(642) print(t.TTRating) |
^ PostgreSQL > UDF returning TABLE
CREATE OR REPLACE FUNCTION public.get_tests_by_rating(p_t_rating numeric) RETURNS TABLE (r_id integer, r_date date, r_name character varying, r_rating numeric) LANGUAGE 'plpgsql' AS $BODY$ DECLARE var_r record; BEGIN FOR var_r IN(SELECT t_id, t_date, t_name, t_rating FROM tests WHERE t_rating >= p_t_rating) LOOP r_id := var_r.t_id; r_date := var_r.t_date; r_name := var_r.t_name; r_rating := var_r.t_rating; RETURN NEXT; END LOOP; END; $BODY$;
<dto-class name="Test3" ref="select * from get_tests_by_rating(?)" />
<query-dto-list method="get_tests_by_rating(t_rating)" dto="Test3" ref="select * from get_tests_by_rating(?)" />
PHP |
foreach ($dao->get_tests_by_rating(2) as $t) { // ... } |
Java |
for (Test3 t : dao.get_tests_by_rating(new BigDecimal(2))) { // ... } |
Python |
for p in dao.get_tests_by_rating(2): # ... |
Go |
tests := spDao.get_tests_by_rating_table(2) for _, r := range tests { // ... } |
^ PostgreSQL > UDF returning SETOF TABLE
CREATE OR REPLACE FUNCTION public.get_tests_setof_table(p_rating numeric) RETURNS SETOF tests LANGUAGE 'plpgsql' AS $BODY$ DECLARE r record; BEGIN for r in SELECT t_id, t_date, concat('1. ', t_name), t_rating, p_id FROM tests WHERE t_rating >= p_rating order by t_rating loop return next r; end loop; for r in SELECT t_id, t_date, concat('2. ', t_name), t_rating, p_id FROM tests WHERE t_rating < p_rating order by t_rating loop return next r; end loop; return; END; $BODY$;
<query-dto-list method="get_tests_setof_table(t_rating)" dto="Test" ref="select * from get_tests_setof_table(?)" />
PHP |
foreach ($dao->get_tests_setof_table(2) as $t) { // process ... } |
Java |
for (Test t : dao.get_tests_setof_table(new BigDecimal(2))) { // process ... } |
Python |
for p in dao.get_tests_setof_table(2): # process ... |
Go |
tests4 := spDao.getTestsSetOfTable(3) for _, r := range tests4 { // ... } |
^ PostgreSQL > UDF returning REFCURSOR
CREATE OR REPLACE FUNCTION public.get_tests_by_rating_rc(p_rating numeric) RETURNS REFCURSOR LANGUAGE 'plpgsql' AS $BODY$ DECLARE ref REFCURSOR; BEGIN OPEN ref FOR SELECT t_id, p_id, t_date, t_name, t_rating FROM tests WHERE t_rating >= p_rating; RETURN ref; END;$BODY$;
NOTE 1. To call such UDF, you need transactions.
NOTE 2. You cannot create something like this
CREATE FUNCTION public.get_rc_and_out_param(IN p_rating numeric, OUT p_avg_rating NUMERIC) RETURNS REFCURSOR ...
or this
CREATE FUNCTION public.get_rc_and_out_param(IN p_rating numeric, INOUT p_avg_rating NUMERIC) RETURNS REFCURSOR ...
because of ERROR: function result type must be numeric because of OUT parameters.
PHP |
<query method="get_tests_by_rating_rc(rating)" ref="select * from get_tests_by_rating_rc(?)" /> <query-dto-list method="get_tests()" external-sql="true" dto="Test" ref="" /> $ds->beginTransaction(); // ensure transaction // ... $cursor = $dao->get_tests_by_rating_rc(3); foreach ($dao->get_tests($ds->pg_fetch(cursor))) as $tt) { // ... } |
Java |
<query-dto-list method="get_tests_by_rating_rc(t_rating)" dto="Test" ref="select * from get_tests_by_rating_rc(?)" /> // ensure transaction for (Test t : dao.get_tests_by_rating_rc(new BigDecimal(1))) { // ... } |
Python |
<query method="get_tests_by_rating_rc(rating)" ref="select * from get_tests_by_rating_rc(?)" /> <query-dto-list method="get_tests()" external-sql="true" dto="Test" ref="" /> # ensure transaction cursor = dao.get_tests_by_rating_rc(4) for p in dao.get_tests(ds.pg_fetch(cursor)): # ... |
Go |
<query method="getTestsByRatingRc(rating)" ref="select * from get_tests_by_rating_rc(?)" /> <query-dto-list method="getTests()" external-sql="true" dto="Test" ref="" /> ds.begin() // ensure transaction defer ds.commit() // ... cursor := spDao.getTestsByRatingRc(2) tests := spDao.getTests(ds.pgFetch(cursor)) for _, t := range tests { // ... } |
^ PostgreSQL > UDF returning SETOF REFCURSOR
NOTE. To call such UDF, you need transactions.
CREATE OR REPLACE FUNCTION public.get_tests_multiple(p_rating integer) RETURNS SETOF REFCURSOR LANGUAGE 'plpgsql' AS $BODY$DECLARE ref1 REFCURSOR; ref2 REFCURSOR; BEGIN OPEN ref1 FOR SELECT *, 1 as sign FROM tests WHERE t_rating >= p_rating ORDER by t_rating; RETURN NEXT ref1; OPEN ref2 FOR SELECT *, 2 as sign FROM tests WHERE t_rating < p_rating ORDER by t_rating; RETURN NEXT ref2; END;$BODY$;
PHP |
<dto-class name="Test5" ref="tests" > <field java-type="Integer" column="sign" /> </dto-class/> <query-list method="get_tests_multiple(p_rating)" ref="select * from get_tests_multiple(?)" /> <query-dto-list dto="Test5" ref="" external-sql="true" method="get_tests()" /> // ensure transaction $cursors = $dao->get_tests_multiple(3); foreach ($dao->get_tests($ds->pg_fetch($cursors[0]) as $tt) { // ... } foreach ($dao->get_tests(ds.pg_fetch($cursors[1]) as $tt) { // ... } |
Java |
Problem 1. This line <query-list method="get_tests_multiple(p_rating)" ref="select * from get_tests_multiple(?)" /> produces this public List<java.sql.ResultSet> get_tests_multiple(Integer p_rating) throws Exception { String sql = "select * from get_tests_multiple(?)"; return ds.queryList(java.sql.ResultSet.class, sql, p_rating); } and you need to deal with JDBC directly. Problem 2. With this, <dto-class name="Test5" ref="tests" > <field java-type="Integer" column="sign" /> </dto-class/> <query-dto-list method="get_tests_multiple(p_rating)" dto="Test5" ref="select * from get_tests_multiple(?)" /> you obtain all Result-Sets in single list: for (Test5 t : dao.get_tests_multiple(3)) { // ... } Workaround. If you need separate lists (with even different types of DTO), look towards named REFCURSOR-s: http://www.sqlines.com/postgresql/how-to/return_result_set_from_stored_procedure CREATE FUNCTION public.get_set_of_named_ref_cursors(IN p_rating numeric, IN p_ref_1 refcursor, IN p_ref_2 refcursor) RETURNS SETOF REFCURSOR LANGUAGE 'plpgsql' AS $BODY$ BEGIN OPEN p_ref_1 FOR SELECT *, 1 as sign1 FROM tests WHERE t_rating <= p_rating ORDER BY t_rating; RETURN NEXT p_ref_1; OPEN p_ref_2 FOR SELECT *, 2 as sign2 FROM tests WHERE t_rating > p_rating ORDER BY t_rating; RETURN NEXT p_ref_2; END$BODY$; <dto-class name="Test1" ref="tests" > <field java-type="Integer" column="sign1" /> </dto-class/> <dto-class name="Test2" ref="tests" > <field java-type="Integer" column="sign2" /> </dto-class/> <query method="get_set_of_named_ref_cursors(p_rating)" ref="select get_set_of_named_ref_cursors(?, 'p_ref_1', 'p_ref_2');" return-type="Void" /> <query-dto-list dto="Test1" ref="fetch all from ref_1" method="fetch_ref_1()" /> <query-dto-list dto="Test2" ref="fetch all from ref_2" method="fetch_ref_2()" /> NOTE. ^^ Ensure return-type="Void" to prevent processing of ResultSet. // ensure transaction dao.get_set_of_named_ref_cursors(new BigDecimal(3)); for (Test1 t : dao.fetch_ref_1()) { // ... } for (Test2 t : dao.fetch_ref_2()) { // ... } |
Python |
<query-list method="get_tests_multiple(p_rating)" ref="select * from get_tests_multiple(?)" /> <query-dto-list dto="Test5" ref="" external-sql="true" method="get_tests()" /> # ensure transaction cursors = dao.get_tests_multiple(3) for t5 in dao.get_tests(ds.pg_fetch(cursors[0])): # ... for t5 in dao.get_tests(ds.pg_fetch(cursors[1])): # ... |
Go |
<query-list method="getTestsMultiple(p_rating)" ref="select * from get_tests_multiple(?)" /> <query-dto-list dto="Test5" ref="" external-sql="true" method="getTests()" /> ds.begin() // begin transaction to use ref-cursors defer ds.commit() // ... cursors := spDao.getTestsMultiple(2) tests1 := spDao.getTests(ds.pgFetch(cursors[0])) for _, t := range tests1 { // ... } tests2 := spDao.getTests(ds.pgFetch(cursors[1])) for _, t := range tests2 { // ... } |
^ PostgreSQL > REFCURSOR-s as INOUT parameters
CREATE OR REPLACE PROCEDURE public.sp_out_ref_cursor(p_rating NUMERIC, INOUT p_ref REFCURSOR) LANGUAGE 'plpgsql' AS $BODY$ BEGIN OPEN p_ref FOR SELECT * FROM tests WHERE t_rating >= p_rating ORDER BY t_rating; END$BODY$;
<exec-dml method="sp_out_ref_cursor(p_rating)" ref="CALL sp_out_ref_cursor(?, 'ref1')" /> <query-dto-list dto="Test" ref="fetch all from ref1" method="fetch_out_ref_cursor()" />
NOTE 1. ^^ Ensure no {} in SQL.
NOTE 2. To call such UDF, you need transactions.
PHP |
$dao->sp_out_ref_cursor(3); foreach ($dao->fetch_out_ref_cursor() as $tt) { // ... } |
Java |
dao.sp_out_ref_cursor(new BigDecimal(3)); for (Test t : dao.fetch_out_ref_cursor()) { // ... } |
Python |
dao.sp_out_ref_cursor(3) for t in dao.fetch_out_ref_cursor(): # ... |
Go |
dao.spOutRefCursor(3) for _, r := range spDao.fetchOutRefCursor() { // ... } |
^ PostgreSQL > SP to change some data
<exec-dml method="set_test_rating(t_id, t_rating)" ref="call set_test_rating(?, ?)" />
CREATE OR REPLACE PROCEDURE public.set_test_rating( p_t_id integer, p_t_rating numeric) LANGUAGE 'plpgsql' AS $BODY$BEGIN update tests set t_rating = p_t_rating where t_id = p_t_id; END;$BODY$;
^ PostgreSQL > OUT and INOUT parameters
CREATE OR REPLACE PROCEDURE public.sp_test_in_out_param(INOUT res int) LANGUAGE 'plpgsql' AS $BODY$begin res := res * 10; end;$BODY$;
<exec-dml method="sp_test_in_out_param(Object res)" ref="call sp_test_in_out_param(?)" />
^^ 1) No {} in SQL. 2) Redefine the type of res to Object for Java.
CREATE OR REPLACE PROCEDURE public.sp_test_out_string_param(INOUT res CHARACTER VARYING) LANGUAGE 'plpgsql' AS $BODY$begin res := 'Hello from sp_test_out_string_param!'; end$BODY$;
<exec-dml method="sp_test_out_string_param(Object res)" ref="call sp_test_out_string_param(?)" />
^^ 1) No {} in SQL. 2) Redefine the type of res to Object for Java.
NOTE 1. You cannot create something like this
CREATE OR REPLACE PROCEDURE public.sp_out_ref_cursor(p_rating numeric, INOUT p_ref REFCURSOR, OUT p_avg NUMERIC)
because of 'ERROR: procedures cannot have OUT arguments. HINT: INOUT arguments are permitted.'.
This one is OK:
CREATE OR REPLACE PROCEDURE public.sp_out_ref_cursor2(p_rating NUMERIC, INOUT p_ref REFCURSOR, INOUT p_avg NUMERIC) LANGUAGE 'plpgsql' AS $BODY$ BEGIN OPEN p_ref FOR SELECT * FROM tests WHERE t_rating >= p_rating ORDER BY t_rating; SELECT AVG(t_rating) INTO p_avg FROM tests WHERE t_rating >= p_rating; END$BODY$;
<!-- don't use "exec-dml" with out params /> --> <!-- <exec-dml method="sp_out_ref_cursor2(p_rating, ref1, p_avg)" ref="CALL sp_out_ref_cursor(?, ?, ?)" /> --> <query method="sp_out_ref_cursor2(p_rating, ref1, p_avg)" ref="CALL sp_out_ref_cursor(?, ?, ?)" /> <query-dto-list dto="Test" ref="" external-sql="true" method="fetch_out_ref_cursor()" />
NOTE 2. Trying to use RETURNS REFCURSOR with OUT/INOUT parameters produces 'ERROR: function result type must be numeric because of OUT parameters':
CREATE FUNCTION public.get_rc_and_out_param(IN p_rating numeric, OUT p_avg_rating NUMERIC) RETURNS REFCURSOR ...
CREATE FUNCTION public.get_rc_and_out_param(IN p_rating numeric, INOUT p_avg_rating NUMERIC) RETURNS REFCURSOR ...
PHP |
(starting from DataStore.php + PDO + PostgreSQL v.1.110) $res1 = new OutParam(PDO::PARAM_INT, 123); $dao->sp_test_in_out_param($res1); print_r($res1->value); $res2 = new OutParam(PDO::PARAM_STR); $dao->sp_test_out_string_param($res2); print_r($res2->value); // There isn't any PDO::PARAM for decimals / floats, you'll have to use PDO::PARAM_STR. // https://stackoverflow.com/questions/2718628/pdoparam-for-type-decimal $avg = new OutParam(PDO::PARAM_STR); $dao->sp_out_ref_cursor2(3, new InOutParam(PDO::PARAM_STR, 'ref1'), $avg); foreach ($dao->fetch_out_ref_cursor2('fetch all from ref1') as $tt) { // ... } print_r($avg->value); |
Java |
OutParam res = new OutParam(Types.INTEGER, Integer.class, 10); dao.sp_test_in_out_param(res); out.println(res.getValue()); OutParam out_param = new OutParam(Types.VARCHAR, String.class, ""); dao.sp_test_out_string_param(out_param); System.out.println(out_param.getValue()); |
Python |
inout = OutParam(10) dao.sp_test_in_out_param(inout) print(inout.value) out = OutParam("") dao.sp_test_out_string_param(out) print(out.value) avg = OutParam(0) dao.sp_out_ref_cursor2(2, OutParam('ref1'), avg) for task in dao.fetch_out_ref_cursor('fetch all from ref1'): # ... print(avg.value) |
Go |
<query method="SpTestOutStringParam(*string outStr)" ref="call SpTestOutStringParam(?)"/> var outStr string _, err := spDao.SpTestOutStringParam(&outStr) if err != nil { // ... } fmt.Println(outStr) <query method="SpTestInOutParam(*int64 value)" ref="call SpTestInOutParam(?)"/> inoutInt64 := int64(123) _, err := spDao.SpTestInOutParam(&inoutInt64) if err != nil { // ... } fmt.Println(inoutInt64) Alternatively, use DTO (preferable because of auto-detection of types/names): <dto-class name="sqlx-OutParams" ref="call sp_test_out_string_param('')"/> <query-dto dto="sqlx-OutParams" method="SpTestOutStringParam2()"/> res, err := spDao.SpTestOutStringParam2(txCtx) if err != nil { // ... } fmt.Println(res.Res) |
^ SQL Server
SQL Server Table-valued Functions | https://www.sqlservertutorial.net/sql-server-user-defined-functions/sql-server-table-valued-functions/ |
Google: sql server stored procedure return table ==> SQL Server: Returning TABLE/VALUE From A Stored Procedure – Querychat | https://www.got-it.ai/solutions/sqlquerychat/sql-help/data-query/sql-server-returning-table-value-from-a-stored-procedure/ |
PHP: Example Application (PDO_SQLSRV Driver) | https://docs.microsoft.com/en-us/sql/connect/php/example-application-pdo-sqlsrv-driver?view=sql-server-ver15 |
Python: Calling Stored Procedures | https://github.com/mkleehammer/pyodbc/wiki/Calling-Stored-Procedures |
^ SQL Server > UDF returning a scalar value
<query method="ufnLeadingZeros(value)" ref="select dbo.ufnLeadingZeros(?)" />
ALTER FUNCTION [dbo].[ufnLeadingZeros](@Value int) RETURNS varchar(8) WITH SCHEMABINDING AS BEGIN DECLARE @ReturnValue varchar(8); SET @ReturnValue = CONVERT(varchar(8), @Value); SET @ReturnValue = REPLICATE('0', 8 - DATALENGTH(@ReturnValue)) + @ReturnValue; RETURN (@ReturnValue); END;
^ SQL Server > UDF returning a list of strings
<query-list method="ufnDepartmentGroupNames" ref="select * from HumanResources.ufnDepartmentGroupNames()" />
NOTE. Ensure '()' in the end of SQL statement for UDF without parameters.
ALTER FUNCTION [HumanResources].[ufnDepartmentGroupNames] () RETURNS TABLE AS RETURN SELECT DISTINCT GroupName FROM HumanResources.Department
^ SQL Server > UDF that RETURNS TABLE
<dto-class name="Department2" ref="select * from HumanResources.ufnDepartmentsByGroup(?)" />
NOTE. With SQL Server, select from UDF in declarations of DTO classes is OK.
<query-dto-list method="ufnDepartmentsByGroup(groupName)" dto="Department2" ref="select * from HumanResources.ufnDepartmentsByGroup(?)" />
NOTE. Ensure '()' in the end of SQL statement for UDF without parameters.
ALTER FUNCTION [HumanResources].[ufnDepartmentsByGroup] (@GroupName varchar(100)) RETURNS TABLE AS RETURN SELECT DepartmentID, Name FROM HumanResources.Department WHERE GroupName = @GroupName
^ SQL Server > SP returning Result-Set
ALTER PROCEDURE [HumanResources].[uspDepartments] AS SELECT DepartmentID, Name, GroupName FROM HumanResources.Department ORDER BY Name
1) How to call SP and obtain a list of 'Integer':
<query-list method="get_department_ids()" ref="{call HumanResources.uspDepartments()}" />
2) How to call SP to obtain a list of DTO:
<query-dto-list method="get_departments()" dto="Department" ref="{call HumanResources.uspDepartments()}" />
Syntax for Go + SQL Server (github.com/denisenkom/go-mssqldb) differs:
<query-dto-list method="get_departments()" dto="Department" ref="exec HumanResources.uspDepartments" />
^ SQL Server > SP to change some data
<exec-dml method="uspUpdateEmployeePersonalInfo(BusinessEntityID, NationalIDNumber, BirthDate, MaritalStatus, Gender)" ref="{CALL HumanResources.uspUpdateEmployeePersonalInfo(?, ?, ?, ?, ?)}" />
^^ Ensure enclosing {} for 'CALL' statement.
Syntax for Go + SQL Server (github.com/denisenkom/go-mssqldb) differs:
<exec-dml method="uspUpdateEmployeePersonalInfo(BusinessEntityID, NationalIDNumber, BirthDate, MaritalStatus, Gender)" ref="exec HumanResources.uspUpdateEmployeePersonalInfo ?, ?, ?, ?, ?" />
ALTER PROCEDURE [HumanResources].[uspUpdateEmployeePersonalInfo] @BusinessEntityID [int], @NationalIDNumber [nvarchar](15), @BirthDate [datetime], @MaritalStatus [nchar](1), @Gender [nchar](1) WITH EXECUTE AS CALLER AS BEGIN SET NOCOUNT ON; BEGIN TRY UPDATE [HumanResources].[Employee] SET [NationalIDNumber] = @NationalIDNumber, [BirthDate] = @BirthDate, [MaritalStatus] = @MaritalStatus, [Gender] = @Gender WHERE [BusinessEntityID] = @BusinessEntityID; END TRY BEGIN CATCH EXECUTE [dbo].[uspLogError]; END CATCH; END;
^ SQL Server > SP with Return-Value
<query method="test_sp_return_value" ref=" DECLARE @rv int; EXEC @rv = [dbo].[test_sp_return_value]; SELECT @rv AS return_value;"/>
ALTER PROCEDURE [dbo].test_sp_return_value AS BEGIN SET NOCOUNT ON; RETURN 123; END
^ SQL Server > OUT and INOUT parameters
ALTER PROCEDURE [dbo].[sp_test_inout_params] @Param1 int out AS BEGIN SET NOCOUNT ON; SET @Param1 = @Param1 * 10; END
NOTE. If you declare a parameter as OUTPUT, it acts as both Input and Output.
PHP |
<exec-dml method="sp_test_inout_params(res)" ref="{CALL [dbo].[sp_test_inout_params](?)}" /> NOTES. 1) Use 'exec-dml'. 2) Do use {} in ref. $res = new OutParam(PDO::PARAM_STR, "10"); // use PDO::PARAM_STR for OUT parameters $dao->sp_test_inout_params($res); print_r($res->value); |
Java |
<exec-dml method="sp_test_inout_params(Object res)" ref="{call dbo.sp_test_inout_params(?)}" /> NOTES. 1) Use 'exec-dml'. 2) Do use {} in ref. 2) Define the type of res as DataStore.OutParam or just as Object. OutParam res = new OutParam(Types.INTEGER, Integer.class, 10); dao.sp_test_inout_params(res); System.out.println(res.getValue()); |
Python + pyodbc |
Use <query... for single out/inout param: <query method="sp_test_inout_params(inout)" ref=" DECLARE @res int; SET @res = ?; EXEC [dbo].[sp_test_inout_params] @res OUT; SELECT @res;"/> res = dao.sp_test_inout_params(10) print(res) Use <query-list... for multiple out/inout params: <query-list method="sp_test_out_params2(in_param)" ref=" DECLARE @value int; DECLARE @name varchar(200); SET @value = ?; EXEC [dbo].[sp_test_out_params2] @value OUT, @name OUT; SELECT @value; SELECT @name;"/> op := dao.sp_test_out_params2(123) print(f"value={op[0]}\tname={op[1]}") |
Go |
Use <query... for single out/inout param: <query method="spTestInoutParams(inout)" ref=" DECLARE @res int; SET @res = ?; EXEC [dbo].[sp_test_inout_params] @res OUT; SELECT @res;"/> res := spDao.spTestInoutParams(123) fmt.Println(res) For multiple out/inout params:
<query-list method="spTestInoutParams2(inParam)" return-type="interface{}" ref=" DECLARE @value int; DECLARE @name varchar(200); SET @value = ?; EXEC [dbo].[sp_test_out_params2] @value OUT, @name OUT; SELECT @value; SELECT @name;"/> Alternatively:
<query method="spTestInoutParams2(inParam)" return-type="[]interface{}" ref=" DECLARE @value int; DECLARE @name varchar(200); SET @value = ?; EXEC [dbo].[sp_test_out_params2] @value OUT, @name OUT; SELECT @value, @name;"/> outs := spDao.spTestInoutParams2(123) fmt.Println(outs) (Also, it is possible to use something like <dto-class name="MyOutParams" ref=""... + <query-dto dto="MyOutParams"... + labels for fetched variables: SELECT @value as value; @name as name;). |