私は単純なMySQLストアドプロシージャを作成しており、PDOを使用してPHPから呼び出します。
これは、私が抱えている問題を説明するのに十分単純です。
このプロシージャtest_procedure_2
はエラーをスローしません。
CREATE PROCEDURE `test_procedure_1`()
BEGIN
select 'gary';
CALL raise_error;
END
結果セットを返します。
[0] => Array
(
[0] => Array
(
[gary] => gary
)
)
しかしながら...
CREATE PROCEDURE `test_procedure_2`()
BEGIN
CALL raise_error;
select 'gary';
END
...このプロシージャtest_procedure_2
が呼び出されると、実際には"SQLSTATE[42000]: Syntax error or access violation: 1305 PROCEDURE vjs_admin_dev.raise_error does not exist"
というメッセージとともにPDOExceptionがスローされます。また、select 'gary'
クエリは実行されません。私はすきです。
明らかにエラーが含まれているのに、最初のプロシージャがエラーをスローしなかったのはなぜですか?私はそれのどこかにエラーがあるときはいつでも、それがエラーを投げるストアドプロシージャを思いつきたいと思います。ご協力いただきありがとうございます!
私は使っています
編集:私はこれをmysqliとPDOで試しましたが、同じ結果が得られます。ここに私の問題を示すいくつかの簡単なコード例があります:
Mysqliバージョン: https://Pastebin.com/rATMST4H
PDOバージョン: https://Pastebin.com/Wiq3yFmQ
Mysqliバージョンの出力は次のとおりです。
Running test_procedure_1
CREATE PROCEDURE `test_procedure_1`()BEGIN select 'gary'; CALL raise_error;END
RESULTS:Array ( [gary] => gary )
----------
Running test_procedure_2
CREATE PROCEDURE `test_procedure_2`()BEGIN CALL raise_error; select 'gary';END
CALL failed: (1305) PROCEDURE vjs_admin_dev.raise_error does not exist
----------
PDOバージョンの出力は次のとおりです。
Running test_procedure_1
CREATE PROCEDURE `test_procedure_1`()BEGIN select 'gary'; CALL raise_error;END
RESULTS:Array ( [gary] => gary [0] => gary )
----------
Running test_procedure_2
CREATE PROCEDURE `test_procedure_2`()BEGIN CALL raise_error; select 'gary';END
CALL failed: (42000) Array ( [0] => 42000 [1] => 1305 [2] => PROCEDURE vjs_admin_dev.raise_error does not exist )
----------
うわー、それは関連する問題です(プロシージャのエラーを特定できないため、これはいくつかの有効な結果の横に非表示を渡します!あなたの質問はもっと人気があると思います。
多くの試行錯誤の末、私はそれを解決する方法を見つけました。使用可能な結果がなくなるまで、プロシージャの結果セットをループ処理することを期待します( PHP手動の方法 )。ただし、以前の有効な結果の後のエラーをキャッチする別の制御フローが見つかりました。
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
$sql="CALL procedureWithErrors()";
$conn=new mysqli($Host,$user,$pass,$db);
/*The usual way (does not work)*/
$conn->multi_query($sql);
do{
$result=$conn->store_result();
if($result){
while($row=$result->fetch_assoc()){
print_r($row);
}
$result->free();
}
}while($conn->more_results() && $conn->next_result());
/*This way works*/
$conn->multi_query($sql);
while(($result=$conn->store_result())!==FALSE){
if($result){
while($row=$result->fetch_assoc()){
print_r($row);
}
$result->free();
}
$conn->next_result();
}
/*Just closing and error handling, not relevant code from here*/
$conn->close();
}catch(mysqli_sql_exception | Exception $e){
$error="Error #".$e->getCode()." ".$e->getMessage().PHP_EOL;
if(isset($conn) && get_class($e)=="mysqli_sql_exception")
$error.="SQLSTATE #".$conn->sqlstate." Statement: $sql".PHP_EOL;
$error.=$e->getTraceAsString();
echo(nl2br($error));
}finally{
if(isset($result) && $result instanceof mysql_result) $result->free();
if(isset($conn) && is_resource($conn) && get_resource_type($conn)==='mysql link') $conn->close();
}
?>
問題はそのようですmysqli::more_results
およびmysql::next_result
エラーが発生した場合にFALSEを返すため、エラーとそれ以上の結果の欠如を区別できません。
それは可能ではありません。後続のステートメントがストアドプロシージャでエラーをスローするかどうかに基づいて、ステートメントの実行を妨げることはできません。
同様のことを達成する唯一の方法は、次のようなものです。
drop procedure if exists test_procedure_3;
DELIMITER $$
CREATE PROCEDURE `test_procedure_3`()
BEGIN
if exists (select 1 from information_schema.routines where routine_name = 'raise_error' and routine_schema = database())
then
select 'gary';
CALL raise_error;
end if;
END$$
DELIMITER ;
call test_procedure_3;
したがって、手順が事前に存在するかどうかを確認するだけです。他に方法はありません(トランザクション、シグナル、準備されたステートメントなどで試しました)。