SSMSで実行すると、同じSPがコードで実行される場合とは異なる値を返すストアドプロシージャがあります。非常に単純なSP呼び出しとダンプLinqpad。これは、SQL Server 2016 Standardを実行する新しいサーバーに移行した後に発生し始めたと考えています。
ストアドプロシージャは3つのテーブル変数を使用し、そのうちの1つはカーソルを使用して更新されます(ベストプラクティスではありません)。
これまでに実行したデバッグ手順:
この問題は、夜間のバックアップから復元された開発サーバーでは発生しません
同じデータベースに同じストアドプロシージャを作成しました。問題が発生せず、新しいSPはSSMSとLINQPADで同じ結果を返しました。
ストアドプロシージャでsp_recompileを実行しました。これで問題が解決したようで、SSMSとLINQPADでも同じ結果が得られました。ただし、これは一時的なものでした。金曜日に再コンパイルしましたが、問題は今日(火曜日)に戻りました。
Sys.dm_exec_procedure_statsを確認したところ、SPプランが変更されたことがわかりません。ステートメントプランも確認しましたが、変更されていないようです。
次に確認できるアイデアはありますか?
これがコードです。最初に、これはSPは私たちの基準に達していないと言います。これはSPでき、カーソルを削除するために書き直されます。
ただし、これは私のキャリアの中で初めて、SSMSでストアドプロシージャの結果が他のコードから呼び出された場合とは異なることを見たことがあります。約2週間前にSQL 2016にアップグレードしました。この問題は、アップグレード直後に表示されます。
出力の「BoxX」というラベルの付いた列は、違いが見られる場所です。これは、カーソルで更新される列です。
これの目的の1つSPは、YのボックスXを表示することです(2の2のボックス1など)。
SSMSでは、BoxXの値は1,2,1,2,3などになります。LINQPADでは、値は1,1,1,1,1です。
CREATE Procedure [dbo].[SP1] as
Begin
declare @tmpMCCustOrderNo varchar(10), @tmpCompareOrderno varchar(10), @tmpMCPackageID varchar(21), @tmpCurrentMC int
declare @tmp1 TABlE(OrderNo varchar(10), MCTotCnt int)
declare @tmp2 TABLE(Orderno varchar(10),PackageID varchar(21), MCBoxX int)
--Build list of orders with packages in TABLE2
Insert into @tmp1(OrderNo, MCTotCnt)
Select s.CustOrderNo, count(Distinct s.PackageID)
From DATABASE1..TABLE1 s
Where s.CustOrderNo in (Select Distinct CustOrderNo from DATABASE1..TABLE1 where PackageID in (select PackageID from TABLE2))
and IsNull(s.BoxType, '') <> ''
Group By s.CustOrderNo
Insert into @tmp2(OrderNo,PackageID,MCBoxX)
Select distinct s.CustOrderNo, s.PackageID, 0
From DATABASE1..TABLE1 s, @tmp1 l
Where s.CustOrderNo = l.OrderNo and IsNull(s.BoxType, '') <> ''
Order By s.CustOrderNo,s.PackageID
--Cursor to increment counter (@tempCurrentMC)
DECLARE c_EX CURSOR FOR
SELECT Orderno,PackageID FROM @tmp2
OPEN c_EX
FETCH NEXT FROM c_EX INTO @tmpMCCustOrderNo,@tmpMCPackageID
SELECT @tmpCompareOrderNo = @tmpMCCustOrderNo
SELECT @tmpCurrentMC = 0
WHILE @@FETCH_STATUS = 0
BEGIN
IF (@tmpCompareOrderno <> @tmpMCCustOrderNo)
BEGIN
SELECT @tmpCurrentMC = 1
SELECT @tmpCompareOrderno = @tmpMCCustOrderNo
END
ELSE
BEGIN
SELECT @tmpCurrentMC = @tmpCurrentMC + 1
END
UPDATE @tmp2 SET MCBoxX = @tmpCurrentMC WHERE OrderNo = @tmpMCCustOrderNo and PackageID = @tmpMCPackageID
FETCH NEXT FROM c_EX INTO @tmpMCCustOrderNO, @tmpMCPackageID
END
CLOSE c_EX
DEALLOCATE c_EX
declare @tmp3 table(SalesID varchar(20), Shipper Varchar(50), PackageID Varchar(21), ShipToName Varchar(100), UCC128 Varchar(50), Priority int, status varchar(20),
LastUpdate datetime, shipdate datetime, pallets int, packages int, mcboxY int)
--Gather other order information into another table variable
Insert into @tmp3 (SalesID, Shipper, PackageID, ShipToName, UCC128, Priority, status, LastUpdate, shipdate, pallets, packages, mcboxY)
Select Distinct i.ax_salesid as SalesID, i.ShipMethod, m.PackageID, i.ShipToName, m.UCC128, m.Priority Priority, m.MCHQStatus Status, m.LastUpdate, i.ShipDate ShipDate,
COUNT(Distinct s.ucc128) Pallets, COUNT(Distinct S.packageid) Packages, T2.MCTotCnt MCBoxY
From DATABASE2..TABLE2 m
Inner Join DATABASE1..TABLE3 i on LEFT (m.packageid, 6) = i.CustOrderNo
Inner Join DATABASE1..TABLE1 s on S.CustOrderNo = I.CustOrderNo
Left Join @tmp1 T2 on T2.OrderNo = S.CustOrderNo
Group by i.ax_salesid , i.ShipMethod, m.PackageID, i.ShipToName, m.UCC128, m.Priority, m.MCHQStatus, m.LastUpdate, i.ShipDate , T2.MCTotCnt
Order by m.Priority, i.AX_SalesID, m.PackageID
--Return Data
Select MCR.SalesID, MCR.Shipper, MCR.PackageID, MCR.ShipToName,
MCR.UCC128, MCR.Priority, MCR.Status, MCR.LastUpdate, MCR.ShipDate, MCR.Pallets, MCR.Packages, MCM.MCBoxX as BoxX, MCR.mcboxY as OfY
From @tmp3 MCR
Inner Join @tmp2 MCM on
MCM.PackageID = MCR.PackageID
Order by MCR.Priority, MCR.SalesID, MCM.MCBoxX, MCR.mcboxY
END
そしてこれがLINQPADでテストした方法です
void Main()
{
var testTable = new DataTable();
SqlConnection con = new SqlConnection("Data Source=server1;Initial Catalog=Database1;Integrated Security=True");
SqlCommand cmd = new SqlCommand("exec Database1..SP1", con);
SqlDataAdapter sda = new SqlDataAdapter(cmd);
sda.Fill(testTable);
testTable.Dump();
}
カーソルにORDER BY
がないという@ypercubeの発言に同意します。
箇条書き1、2、3はすべて、新しい実行計画の可能性を示しています。
このコードは疑わしい部分です:
Insert into @tmp2(OrderNo,PackageID,MCBoxX)
Select distinct s.CustOrderNo, s.PackageID, 0
From DATABASE1..TABLE1 s, @tmp1 l
Where s.CustOrderNo = l.OrderNo and IsNull(s.BoxType, '') <> ''
Order By s.CustOrderNo,s.PackageID
明らかに、このプロシージャの作成者は、Order By
を使用してテーブル変数をロードでき、カーソルがそのテーブル変数からその順序で魔法のようにデータをプルすると想定していました。チェックアウト シートベルトなし– ORDER BYなしの注文を期待 。
カーソルを変更してOrderno、PackageIDのORDER BYを含め、問題が解決するかどうかをお知らせください。
カーソルの意図はMCBoxX
をROW_NUMBER() OVER (PARTITION BY OrderNo ORDER BY PackageID)
で埋めることです。ただし、カーソルに_ORDER BY
_がないため、_@tmp2
_の行は、各行が以前のOrderNo
とは異なる方法で処理され、すべての行番号が1、IF (@tmpCompareOrderno <> @tmpMCCustOrderNo)
チェックのため。
Scott Hogdinの回答 が示唆するように、明示的な_ORDER BY
_をカーソルに追加できます。
カーソルを完全にスキップして、挿入時に行番号を指定することもできます(コードが少なくてすっきりしています)。
_ Insert into @tmp2
(OrderNo, PackageID, MCBoxX)
Select
s.CustOrderNo, s.PackageID,
RowNumber() Over (Partition By s.CustOrderNo
Order By s.PackageID)
From DATABASE1..TABLE1 s, @tmp1 l
Where s.CustOrderNo = l.OrderNo and IsNull(s.BoxType, '') <> ''
Group By s.CustOrderNo, s.PackageID
Order By s.CustOrderNo, s.PackageID ;
_