簡體   English   中英

如果使用參數綁定,PHP 中的查詢無法在 SQL Server 中查看臨時表

[英]Query in PHP fails to see temp table in SQL Server if parameter binding is used

以下代碼按預期工作(假設變量存在):

$connectionInfo = ['Database'=>$dbName, 'UID'=>$username, 'PWD'=>$pwd, 'ReturnDatesAsStrings'=>true, 'CharacterSet'=>'UTF-8'];
$conn           = sqlsrv_connect($server, $connectionInfo);

$select  = sqlsrv_query($conn, 'SELECT * INTO #mytable_temp FROM mytable WHERE myfield = \'myvalue\'', []);
$select2 = sqlsrv_query($conn, 'SELECT * FROM #mytable_temp ', []);

if (!$select2) {
    $errors = sqlsrv_errors();
    var_dump($errors);
} else {
    $res = sqlsrv_fetch_array($select2, SQLSRV_FETCH_ASSOC);
    var_dump($res);
}

但是,如果我將 $select 更改為以下內容,它將不起作用:

$select  = sqlsrv_query($conn, 'SELECT * INTO #mytable_temp FROM mytable WHERE myfield = ?', ['myvalue']);

運行第二條語句“無效的對象名稱 '#mytable_temp”時出現錯誤。 為什么使用參數綁定會導致臨時表不可見?

我知道如果我在同一個 sqlsrv_query() 語句中包含這兩個語句,我可以讓它工作,但這不是我的用例的選項。 我也知道如果使用全局 (##mytable_temp) 表它可以工作,但這也不是一個選項。

我正在運行 PHP 5.4.12 並嘗試了 SQL Server 11.0.3 (2012 SP1) 和 10.50.4000 (2008 SP2) 上的代碼。

這是我對使用參數的SELECT INTO查詢后為什么看不到臨時表的解釋。

考慮這個 T-SQL 代碼(使用MyTable創建和填充,如下所示):

DECLARE @stmt nvarchar(max) = 'SELECT * INTO #mytable_temp FROM mytable WHERE myfield = @P1';
EXECUTE sp_executesql @stmt, N'@P1 varchar(50)', @P1 = 'Value1';

如果您在 SSMS 中運行它,它運行良好,並且消息窗口中的輸出顯示:

(2 row(s) affected)

嘗試在同一個 SSMS 窗口中將上述代碼添加一行並運行整個批處理:

DECLARE @stmt nvarchar(max) = 'SELECT * INTO #mytable_temp FROM mytable WHERE myfield = @P1';
EXECUTE sp_executesql @stmt, N'@P1 varchar(50)', @P1 = 'Value1';
SELECT * FROM #mytable_temp;

輸出是:

(2 row(s) affected)
Msg 208, Level 16, State 0, Line 3
Invalid object name '#mytable_temp'.

原因是帶有參數的語句是由sp_executesql在嵌套存儲過程的范圍內執行的,並且在存儲過程中創建的臨時表對於該存儲過程的調用者是不可見的。

執行 sp_executeSql for select...into #table 但無法選擇出臨時表數據

https://msdn.microsoft.com/en-us/library/ms174979.aspx

當存儲過程完成時,在存儲過程中創建的本地臨時表會自動刪除。 該表可以被創建該表的存儲過程執行的任何嵌套存儲過程引用。 調用創建該表的存儲過程的進程不能引用該表。

當您准備帶有參數的 SQL 語句時,PHP 最終會調用sp_executesql (很可能,盡管我沒有跟蹤它)。 你會得到這個記錄在案的行為——在這個存儲過程中創建一個臨時表作為查詢的一部分,並在sp_executesql返回時立即刪除。 當您運行不帶參數的 SQL 語句時,PHP 不使用sp_executesql將其發送到服務器。


想到的解決方法很少。

  • 將幾條 SQL 語句放入一個長字符串中,並通過一次調用sqlsrv_query來運行它。

  • 創建一個帶參數的存儲過程並在其中放入多個 SQL 語句,然后通過一次調用sqlsrv_query來調用您的過程。 (我個人更喜歡這種方法)。

  • 顯式創建(並可選擇刪除)臨時表。

這是我用來驗證最后一個解決方法是否有效的代碼。 通過 PHP 5.4.28、SQL Server Express 2014、Microsoft 驅動程序 PHP SQLSRV 3.2 驗證。 它使用額外的CREATE TABLE語句顯式創建臨時表,然后使用INSERT INTO而不是單個SELECT INTO語句。

創建測試表並填充一些數據

CREATE TABLE [dbo].[MyTable](
    [ID] [int] NOT NULL,
    [MyField] [varchar](50) NOT NULL,
CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
))

INSERT INTO [dbo].[MyTable] ([ID],[MyField]) VALUES
(1, 'Value1'),
(2, 'Value2'),
(3, 'Value3'),
(4, 'Value1')

運行php腳本

$connectionInfo = array("Database" => "tempdb");
$conn = sqlsrv_connect($serverName, $connectionInfo);

if ($conn)
{
     echo "Connection established.\n";
}
else
{
     echo "Connection could not be established.\n";
     die( print_r( sqlsrv_errors(), true));
}

echo "Running CREATE TABLE ...\n";
$sql_create = "CREATE TABLE #mytable_temp([ID] [int] NOT NULL, [MyField] [varchar](50) NOT NULL)";
$stmt_create = sqlsrv_query($conn, $sql_create);
if( $stmt_create === false )
{
    echo "CREATE TABLE failed\n";
    die( print_r( sqlsrv_errors(), true));
}
else
{
    echo "CREATE TABLE result set:\n";
    while ($row = sqlsrv_fetch_array($stmt_create))
    {
        var_dump($row);
    }
}
sqlsrv_free_stmt($stmt_create);


echo "Running INSERT INTO with param ...\n";
$select_into = "INSERT INTO #mytable_temp(ID, MyField) SELECT ID, MyField FROM MyTable WHERE MyField = ?";

$search = "Value1";
$params = array
    (
    array(&$search, SQLSRV_PARAM_IN)
    );
$stmt_into = sqlsrv_query($conn, $select_into, $params);
if( $stmt_into === false )
{
    echo "INSERT INTO failed\n";
    die( print_r( sqlsrv_errors(), true));
}
else
{
    echo "INSERT INTO result set:\n";
    while ($row = sqlsrv_fetch_array($stmt_into))
    {
        var_dump($row);
    }
}
sqlsrv_free_stmt($stmt_into);


echo "Running SELECT FROM ...\n";
$select_from = "SELECT * FROM #mytable_temp";
$stmt_from = sqlsrv_query($conn, $select_from);
if( $stmt_from === false )
{
    echo "SELECT FROM failed\n";
    die( print_r( sqlsrv_errors(), true));
}
else
{
    echo "SELECT FROM result set:\n";
    while ($row = sqlsrv_fetch_array($stmt_from))
    {
        var_dump($row);
    }
}

echo "end\n";

腳本的輸出

Connection established.
Running CREATE TABLE ...
CREATE TABLE result set:
Running INSERT INTO with param ...
INSERT INTO result set:
Running SELECT FROM ...
SELECT FROM result set:
array(4) {
  [0]=>
  int(1)
  ["ID"]=>
  int(1)
  [1]=>
  string(6) "Value1"
  ["MyField"]=>
  string(6) "Value1"
}
array(4) {
  [0]=>
  int(4)
  ["ID"]=>
  int(4)
  [1]=>
  string(6) "Value1"
  ["MyField"]=>
  string(6) "Value1"
}
end

為了補充弗拉基米爾的回答,這里是跟蹤結果(PHP 5.6.9,MSSQL 2014 Express)。

當您不添加任何參數時

$select  = sqlsrv_query($conn, 'SELECT * INTO #mytable_temp FROM mytable WHERE myfield = \'myvalue\'', []);
$select2 = sqlsrv_query($conn, 'SELECT * FROM #mytable_temp ', []);

PHP 將向 MSSQL 發送普通命令:

SELECT * INTO #mytable_temp FROM mytable WHERE myfield = 'myvalue'
SELECT * FROM #mytable_temp 

添加參數時

$select  = sqlsrv_query($conn, 'SELECT * INTO #mytable_temp FROM mytable WHERE myfield = ?', ['myvalue']);
$select2 = sqlsrv_query($conn, 'SELECT * FROM #mytable_temp ', []);

然后 PHP 將使用sp_executesql

exec sp_executesql N'SELECT * INTO #mytable_temp FROM mytable WHERE myfield = @P1',N'@P1 nvarchar(6)',N'myvalue'
SELECT * FROM #mytable_temp 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM