web-dev-qa-db-ja.com

SQL Server文字列の連結とサブクエリ

いくつかの動的SQLプロダクションコードの作業中に、一連の文字列とサブクエリを連結し、さらに文字列を連結しているコードに遭遇しました。コードの目標は、SQL変数に値を入力して、最終的にEXEC(@sql)を呼び出すことです。私たちが気付いたのは、文字列連結とCOALESCEチェックにもかかわらず、最後のレコードだけが変数に入力していることです。

以下のコードは、結果を再現するための簡略化されたバージョンです。

IF OBJECT_ID('tempdb..#test') IS NOT NULL
BEGIN
  DROP TABLE #test
END

CREATE TABLE #test
(
    tmp_id INT IDENTITY(1,1),
    error VARCHAR(MAX),
    error2 VARCHAR(MAX),
    object_id INT
)


DECLARE @sql VARCHAR(MAX) = ''

INSERT INTO #test(error, error2, object_id)
SELECT TOP 100 CONVERT(VARCHAR(MAX), name), CONVERT(VARCHAR(MAX), name), object_id
FROM sys.columns

    SELECT @sql += 
      ' | '+(SELECT xC.name
        FROM sys.columns xC
        WHERE xC.name  = T.error
        AND xC.object_id = T.object_id) + ' | ' 
            + CAST(T.error2 AS VARCHAR(MAX))
FROM #test T
ORDER BY T.tmp_id

SELECT @sql

奇妙なことに、ここにはさまざまな要因が絡んでいるようです。サブクエリsys.columns、order by、またはerror2列を削除すると、期待どおりに機能します。また、tmp_idの代わりにエラーで並べ替えられるように順序を変更することもできます。

既に量産コードを書き直しているので、私は解決策を探していませんが、これが起こっている「理由」に非常に興味があります。 SQL Server 2008、2016、2017、2019サーバーでテストしました。

誰かが原因について何か考えがありますか?

2
James Holcomb

_ORDER BY_と組み合わせたその形式の文字列連結は定義されていません(不安定で信頼性がありません)。同じ症状が出て、1つの結果しか得られません。すべての行が必要な場合は、_ORDER BY_をドロップします(順序は保証されませんが、_TOP 100_には_ORDER BY_がないため、それは保証されませんどちらかが確定的であること)。 _ORDER BY_が必要な場合は、バージョンに応じて、代わりに_XML PATH_またはSTRING_AGG()を使用してください。

_SELECT @sql = STUFF((
  SELECT ',' + t.error + CONVERT(varchar(12), xC.[object_id]) + ',' 
    + t.error + ',' + t.error + ',' + t.error + ',' 
    + t.error2 + ',' -- carriage returns are good for the soul!
    + t.error + ',' + t.error + ',' + t.error + ',' 
FROM #test AS t
INNER JOIN sys.columns AS xC 
  ON t.object_id = xC.object_id
ORDER BY t.tmp_id -- this is the key to ordering
FOR XML PATH, TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'');
_

2017以降では、以下を使用できます。

_SELECT @sql = STRING_AGG(t.error + CONVERT(varchar(11), xC.[object_id]) + ',' 
    + t.error + ',' + t.error + ',' + t.error + ',' 
    + t.error2 + ',' 
    + t.error + ',' + t.error + ',' + t.error, ',') 
  WITHIN GROUP (ORDER BY t.tmp_id)
FROM #test AS t
INNER JOIN sys.columns AS xC 
  ON t.object_id = xC.object_id;
_

詳細については、 (Order by 1は結果セットを1行に短縮します とその中のリンクを参照してください。

3
Aaron Bertrand