web-dev-qa-db-ja.com

ストアドプロシージャがSSMSコードとC#コードで異なる結果を返す

SSMSで実行すると、同じSPがコードで実行される場合とは異なる値を返すストアドプロシージャがあります。非常に単純なSP呼び出しとダンプLinqpad。これは、SQL Server 2016 Standardを実行する新しいサーバーに移行した後に発生し始めたと考えています。

ストアドプロシージャは3つのテーブル変数を使用し、そのうちの1つはカーソルを使用して更新されます(ベストプラクティスではありません)。

これまでに実行したデバッグ手順:

  1. この問題は、夜間のバックアップから復元された開発サーバーでは発生しません

  2. 同じデータベースに同じストアドプロシージャを作成しました。問題が発生せず、新しいSPはSSMSとLINQPADで同じ結果を返しました。

  3. ストアドプロシージャでsp_recompileを実行しました。これで問題が解決したようで、SSMSとLINQPADでも同じ結果が得られました。ただし、これは一時的なものでした。金曜日に再コンパイルしましたが、問題は今日(火曜日)に戻りました。

  4. 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です。

SSMS結果: SSMS RESULTS

LINQPAD結果: enter image description here

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(); 

}
4
Steve

カーソルに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を含め、問題が解決するかどうかをお知らせください。

1
Scott Hodgin

カーソルの意図はMCBoxXROW_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 ;
_
3
ypercubeᵀᴹ