I'm rewriting a program from pure PHP to Laravel and I have a problem with executing a stored procedure (I didn't write it).
When I try
$sheetLines = DB::select("exec XXXXXXX".dbo.PRICELIST '?'", [$id]);
It keeps on going while PHP hasn't reached max memory. (increasing memory only makes it run longer)
Meanwhile in the old program it takes about 3 seconds and sends the response.
$tsql = "exec XXXXXXX.".dbo.PRICELIST '".$id."'";
Also when I'm calling other stored procedures from other modules everything works fine.
I have noticed that something like this also happens if I try for example:
DB::select(count(price) from orders group by price);
// would work with: count(price) as price_count
I have searched this problem a lot but have found no solution.
I'll be thankful for any help
I would access the underlying PDO driver to execute the stored procedure.
Try
$db = DB::getPdo();
$stmt = $db->prepare("EXEC XXXXXXX.dbo.PRICELIST :id");
$stmt->bindValue(':id', $id);
$result = $stmt->execute();
If that fails to work you could try the query method;
$query = DB::getPdo()->query("EXEC XXXXXXX.dbo.PRICELIST $id");
Regarding your count issue, in Laravel's Eloquent you can do the following;
$count = DB::table('orders')->groupBy('price')->count('price');
I've little experience with PHP, but seen:
set nocount on
Cause oddball behavior in some MSSQL drivers. Especially a stored procedure returns a single recordset, but doesn't output a "(x row(s) affected)" type messages. (Messages can be seen when manually executing a query in SQL Server Management Studio.)
My rule of thumb for complex stored procedure is to add...
set nocount on
...at the start of a stored procedure and conclude with...
set nocount off
....just before the final output.
Example:
create proc spTester
as
begin
set nocount off
-- {... do lots of crazy processing ...}
-- ok, ready to return the final output
set nocount off
select 1 as colA, 2 as colB
end
Related
We're building a production PHP-MySQL application, and want MySQL stored procedures to be the central bullet-proof gateway to the database. Duplicate keys, table not found, server instance going down, etc all and any kind of error needs to be trapped and conveyed to the calling PHP web-based UI, and transaction rolled back in the stored proc upon such errors.
I am using PHP mysqli and calling a stored procedure as follows:
$stmt = mysqli_prepare($db, "call my_stored_proc(?, ?, ?, #ptid)");
if ($stmt && mysqli_stmt_bind_param($stmt, "sss", 'p1', 'p2', 'p3') &&
mysqli_stmt_execute($stmt) && mysqli_stmt_close($stmt)) {
echo "All fine!"
} else {
echo mysqli_error($db);
db_disconnect($db);
exit;
}
The stored procedure does some basic validation and signals a user-defined condition if the validation fails. And sure enough, my PHP code is able to catch and see those non-database (eg. formatting) validation errors arising from the stored procedure. After the non-database validations pass, the stored procedure goes on to do a database-related validation and if those pass it inserts a row in a table, and passes the ID in the last OUT parameter.
My problem is that if this insert fails (say, bcoz duplicate key error, or table not found error), my PHP code is simply not catching the error! It prints "All fine"!
Why is that? What am I missing?
I want my invocation of the stored proc to be bullet-proof, and all errors raised by the stored proc should be trappable in PHP.
FYI: If I call the stored proc from a mysql client (like MySQL Workbench or the mysql client on Linux), the errors are correctly reported.
LATER EDITS:
FYI, the stored procedure code is simply:
delimiter $$
drop procedure if exists my_stored_proc $$
create procedure my_stored_proc
(
in p_name VARCHAR(31),
in p_notes VARCHAR(510),
in p_created_by VARCHAR(31),
out p_pt_id INT
)
begin
declare custom_exception condition for sqlstate '45000';
declare l_retval boolean;
declare l_right_now datetime default now();
select p_name regexp '^[[:space:]]*$' into l_retval;
if l_retval then
signal custom_exception set message_text = 'NAME cannot be blank.';
end if;
select p_name regexp '[^0-9_]' into l_retval;
if l_retval then
signal custom_exception set message_text = 'Invalid NAME.';
end if;
call validate_user_in_db(p_created_by, true, l_retval);
if not l_retval then
signal custom_exception set message_text = 'Invalid CREATED_BY user.';
end if;
insert into some_table
(
NAME, NOTES,
CREATED_BY, CREATED_ON
) values
(
p_name, p_notes,
p_created_by, l_right_now
);
set p_pt_id = last_insert_id();
end $$
delimiter ;
EVEN LATER UPDATE:
The weird thing is, if I comment out the call to validate_user_in_db in the above stored proc, things work fine and errors are correctly trapped (eg. duplicate key, etc) in PHP.
FYI: validate_user_in_db does the following:
create procedure validate_user_in_db (in p_user VARCHAR(127),
in p_active_only boolean, out p_retval boolean)
begin
set p_retval = false;
if p_active_only then
select sql_calc_found_rows 'x'
from SOME_USERS_TABLE
where username = p_user
and active = true
limit 1;
else
select sql_calc_found_rows 'x'
from SOME_USERS_TABLE
where username = p_user
limit 1;
end if;
set #l_num_rows = found_rows() ;
if #l_num_rows = 1 then
set p_retval = true;
end if;
end $$
Sorry for the long post. But I thought I'd give the full picture.
What am I missing? Why is my PHP code not getting back errors if the call to validate_user_in_db is enabled? Is validate_user_in_db changing some state permanently? Is the sql_calc_found_rows keyword messing things up?
FYI: This is PHP 7.3 and MySQL 5.6
Aah, after breaking my head against it for long and a lot googling, I found the problem! It is closely related to How to call a stored procedure within another stored procedure (PHP and mysqli)
Basically I had a case of PHP calling SP1, which in turn called SP2, and everything working fine in a mysql client but breaking when called by PHP!
It turns out the problem is that SP2 was SELECTing a result set (ie. SELECT without an INTO clause).
I re-wrote SP2 to necessarily do a SELECT INTO and that fixed the problem.
I think the ability to SELECT a result set without doing a SELECT INTO is a crappy feature in MySQL. Come to think of it, quite a few things crappy about MySQL (stored functions, exception propagation up the stack, poor compilation and syntax error pinpointing in stored procedures, bad concept of transaction boundaries, etc).
I think SELECTing a result set in a stored routine should be avoided at all costs.
PHP reports errors from Stored Procedures. The problem here is that calling Stored Procedures through mysqli is not an easy task. It's best to avoid Stored Procedures and mysqli if you can.
All errors can be reported by mysqli if you enable mysqli error reporting. Simply add this line before opening a new connection:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
Note: until recently mysqli had plenty of bugs that either crashed PHP or didn't report errors properly. Keep your PHP up-to-date to avoid such problems.
Your main problem is that your stored procedure is producing results, which you are not reading in PHP. Results from MySQL are fetched sequentially, even if the queries are executed asynchronously. When an error happens after SELECT in your stored procedure then PHP will not throw the error immediately. You must fetch each result, even if you don't need them in PHP.
You can utilize a simple do-while loop to fetch all results.
$stmt = $mysqli->prepare('CALL SP1()');
echo $stmt->execute();
do {
$stmt->get_result();
} while ($y = $stmt->next_result());
I have a stored procedure which I run from PHP using:
//Request does not change
$sql = 'BEGIN SP_GET_MY_DATA(:POP, :SEG, :DUR, :VIEW, :PAGE, :OUTPUT_CUR); END;';
//Statement does not change
$stmt = oci_parse($conn,$sql);
oci_bind_by_name($stmt,':POP',$pop);
oci_bind_by_name($stmt,':SEG',$seg);
oci_bind_by_name($stmt,':DUR',$dur);
oci_bind_by_name($stmt,':VIEW',$view);
oci_bind_by_name($stmt,':PAGE',$page);
//But BEFORE statement, Create your cursor
$cursor = oci_new_cursor($conn)
// On your code add the latest parameter to bind the cursor resource to the Oracle argument
oci_bind_by_name($stmt,":OUTPUT_CUR", $cursor,-1,OCI_B_CURSOR);
// Execute the statement as in your first try
oci_execute($stmt);
// and now, execute the cursor
oci_execute($cursor);
// Use OCIFetchinto in the same way as you would with SELECT
while ($data = oci_fetch_assoc($cursor, OCI_RETURN_LOBS )) {
print_r($data}
}
The problem is I have millions of rows and complex logic in stored procedure. When I execute SP_GET_MY_DATA through SQL developer, it takes around 2 hours to complete it.
PHP is timing out when I do it. I cannot increase the max_execution_time in PHP as well.
How can I run this on Oracle or using PHP without timing out? Please help.
I answered how to use Oracle Scheduler to run a long running procedure asynchronously pretty comprehensively in this answer on the DBA stack exchange. See https://dba.stackexchange.com/a/67913/38772
TL;DR is
-- submit this as a background job
BEGIN
dbms_scheduler.create_job (
job_name => 'MY_BACKGROUND_JOB'
, job_type => 'STORED_PROCEDURE'
, job_action => 'SP_GET_MY_DATA'
, enabled => TRUE
, auto_drop => TRUE
);
END;
You'll have to do a little more work if you want to pass in parameters to the procedure. You may find this answer helpful https://dba.stackexchange.com/q/42119/38772/
For additional reference with all the gory details, the relevant chapter from Oracle's documentation is at https://docs.oracle.com/database/121/ADMIN/scheduse.htm
Don't increase max_execution_time, set it to 0, allow it to run indefinitely. If you are going to return loads of rows, make sure to either increase memory (ini_set) or allow immediate buffer flushing so that it can output directly to client.
The latter will also prevent clients from prematurely disconnecting because they didn't get any data. (ob_implicit_flush(true);)
This question relates to:
PHP Version 5.3.6
Microsoft Drivers for PHP for SQL Server
I am trying to properly retrieve data from a stored procedure.
This question assumes the existence of the following stored procedure:
CREATE PROCEDURE test_procedure
AS
BEGIN
SET NOCOUNT ON
--A bunch of SQL statements
--More SQL statements
SELECT 'Doctor Who Rules!'
END
I've tried the following code which runs through all of my commands but does not return the data from the final SELECT.
$sql = "EXEC test_procedure;";
$result = sqlsrv_query($conn,$sql);
$next_result = sqlsrv_next_result($result); // returns a boolean
$row = sqlsrv_fetch_array($result);
Using sqlsrv_execute does not work with the above code either.
How can I return the data geneated by the stored procedure above via PHP?
Thank you.
Addendum #1 (Classic ASP Counterpart)
sqlCMD = "EXEC test_procedure;"
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open _ConnectionString_Variable_
Set rs = conn.Execute(sqlCMD)
I would get back a recordset with one row that has one field with the data "Doctor Who Rules!"
I just hit the same problem a few minutes ago, and this thread pointed me towards a working solution (I'm not sure that it is a "right"solution, but it works).
Actually, the problem was not being caused by "SET NOCOUNT ON". In my case, it was a couple of PRINT statements that were left there after debugging.
Looks like any output from the SP other than the actual recordset, causes the problem.
I figured out the problem.
The PHP code in my example will work properly if SET NOCOUNT ON is ommited from the Stored Procedure. With SET NOCOUNT ON in the procedure, there is no data stream back to PHP and thus, the results of the last SELECT never makes it back.
Therfore, this Stored procedure...
CREATE PROCEDURE test_procedure
#foo varchar(25)
AS
BEGIN
--A bunch of SQL statements
--More SQL statements
SELECT #foo
END
...will work perfectly with this PHP...
$sql = "EXEC test_procedure 'Dr. Who Rules!';";
$result = sqlsrv_query($conn,$sql);
$next_result = sqlsrv_next_result($result);
$row = sqlsrv_fetch_array($result);
You will end up with a recordset with the string "Doctor Who Rules!"
Call procedure works all right in MySQL terminal, but in PHP, caused Commands out of sync; you can't run this command nowCommands out of sync; you can't run this command now
My procedure is
delimiter $$
create procedure getMostSimilar (IN vU_ID INT, IN voffset INT, IN vsize INT)
BEGIN
set #offset = voffset;
set #size = vsize;
set #uid = vU_ID;
prepare SimilarStmt from
"SELECT U_ID, getSimilarity(U_ID, ?) AS similar FROM Answer WHERE U_ID != ? GROUP BY U_ID ORDER BY similar DESC LIMIT ?, ?";
execute SimilarStmt using #uid, #uid, #offset, #size;
deallocate prepare SimilarStmt;
END
$$
where getSimilarity is a function.
In PHP:
function getMostSimilar($U_ID, $offset, $size){
$query = sprintf("CALL getMostSimilar(%s, %s, %s)",
$U_ID, $offset, $size);
$result = mysql_query($query);
print mysql_error();
if (!$result){
return $query;
}
$ans = array();
$len = 0;
while($row = mysql_fetch_assoc($result)){
$ans[$len] = $row;
$len++;
}
return $ans;
}
What should I do now? Thanks!
There seems to be a nasty bug (or feature) that is manifested when calling a stored procedure that returns a result set.. I.e. a stored procedure that ends with a select statement without an INTO clause (see example below).
The mysqli driver (probably) returns 2 result sets. The first is the one returned from the stored procedure and the second a dummy, empty result set. It is like a multiple query command was issued. One solution to this (that does not break on usual (e.g. SELECT) queries), is to consume this dummy result set after processing the legit one (the first).
Example PHP code
function do_query($con, $sql)
{
$result = mysqli_query($con, $sql);
if ($result === true) {
return true;
}
while ($row = mysqli_fetch_assoc($result)) {
// process rows
}
// Hack for procedures returning second dummy result set
while (mysqli_more_results($con)) {
mysqli_next_result($con);
// echo "* DUMMY RS \n";
}
}
Example stored procedure:
CREATE PROCEDURE selectStaleHeaders()
NOT DETERMINISTIC
SELECT TT.*
FROM one_pretty_table AS TT
LEFT JOIN another AS AN on TT.fk_id = AN.id
WHERE TT.id IS NULL;
C.5.2.14. Commands out of sync If you
get Commands out of sync; you can't
run this command now in your client
code, you are calling client functions
in the wrong order.
This can happen, for example, if you
are using mysql_use_result() and try
to execute a new query before you have
called mysql_free_result(). It can
also happen if you try to execute two
queries that return data without
calling mysql_use_result() or
mysql_store_result() in between.
http://dev.mysql.com/doc/refman/5.0/en/commands-out-of-sync.html
I think you need to rewrite the getMostSimilar stored procedure, instead of using prepare and execute (which I thinks is fooling mysql) if you use the parameters in the procedure like in this example I think your error will be fixed.
This "bug" was happening to me with extremely simple procedure even inside phpmyadmin. The reason was that I was declaring general variables (without the # prefix). I changed my variables to user-defined variables prefixed with # and it was solved.
i know it is late and there is already an answer, but i was getting the same message for a whole different reason, so i will leave my solution here:
it was actually a very silly error. It was just a typo in a parameter name.
My function had a parameter named preferable:
create function call_client (pereferable int, client_id int) returns int
in the function body, i was using the parameter preferable with the wrong name:
if prefered = 1 then
...
end if;
once i changed prefered for preferable it started working again.
Given the following code:
// Connect to MySQL up here
$example_query = $database->prepare('SELECT * FROM table2');
if ($example_query === false) die('prepare failed');
$query = $database->prepare('SELECT * FROM table1');
$query->execute();
while ($results = $query->fetch())
{
$example_query = $database->prepare('SELECT * FROM table2');
if ($example_query === false) die('prepare failed'); //it will die here
}
I obviously attempt to prepare statements to SELET everything from table2 twice. The second one (the one in the WHILE loop) always fails and causes an error.
This is only on my production server, developing locally I have no issues so it must be some kind of setting somewhere.
My immediate thought is that MySQL has some kind of max_connections setting that is set to 1 and a connection is kept open until the WHILE loop is completed so when I try to prepare a new query it says "nope too many connected already" and shits out.
Any ideas?
EDIT: Yes I know there's no need to do it twice, in my actual code it only gets prepared in the WHILE loop, but like I said that fails on my production server so after some testing I discovered that a simple SELECT * query fails in the WHILE loop but not out of it. The example code I gave is obviously very watered down to just illustrate the issue.
Problem was that I couldn't do something while an unbuffered query was running (which it was in the WHILE loop)
Solution was to add the following line to ensure buffered queries were being used:
$database->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true);
There shouldn't be a need to prepare $example_query more than once. Simply execute the query inside your loop.
EDIT: If you must prepare a new query in each loop iteration, an explicit $example_query->closeCursor() at the end of the loop should free any resources associated with the statement object.