web-dev-qa-db-ja.com

関数を使用して永続的な計算列を作成する

私はデータベースソリューションについてプログラマと協力しています。彼らは計算された列を追加して、古いクエリ、プロシージャ、システムの古いキーを模倣し、インデックスを付けたいと考えています。新しいキーはGUIDSになります。

これを行うには、値を作成して永続化する計算列の関数を作成します。列を永続化させません。アイデアについての温かいあいまいさはなく、テクニックに関するWeb上の情報も見つかりません(それはテクニックですか?)。

代わりにトリガーを追加する必要があると思います。誰かアイデアはありますか?

関数は次のように実行されます。

(SELECT [INT Identity field] FROM TABLE WHERE [GUID COLUMN] = @GUIDKEY

GUIDに基づいてINT Identityフィールドを返します。

これは、関連するテーブルへの挿入時に実行されます。 SOテーブル1が主キーを保持している場合、関連するテーブル2が(GUID渡された)を使用して)更新され、テーブル1からキーを取得して挿入しますそれを表2に入れます。

5
Paul

これがなぜテーブルの列である必要があるのか​​まだ理解できません。

クエリが実際にそれを必要とするとき(そしてそのときだけ)に相互適用するテーブル値関数を作成しないのはなぜですか。古いキーは決して変更されないため、とにかく計算したり永続化したりする必要はありません。

古いキーを複数の場所に保存したい場合(そして、この種の決定を行うべきではない人々がすでにこの種の決定を行っているように思われる場合)、トリガーでルックアップを実行し、書き込み時にそれを入力します。 。次に、それはテーブル内の静的列です。

これを容易にするためにテーブル値関数を強くお勧めします。これにより、ループを記述したり、スカラー値関数を呼び出したりすることなく、複数行の操作を処理するようにトリガーを記述できます。すべての行で繰り返します。

これらがどれほど似ているかを示すためだけに(そして、「気に入らない」リード開発者に質問します):

-- bad, slow, painful row-by-row
CREATE FUNCTION dbo.GetIDByGUID
(
  @GuidKey uniqueidentifier
)
RETURNS int
AS
BEGIN
  RETURN (SELECT $IDENTITY FROM dbo.tablename WHERE guid_column = @GuidKey);
END
GO

-- in the trigger:
UPDATE r SET oldkey = dbo.GetIDByGUID(i.guid_column)
  FROM dbo.related AS r
  INNER JOIN inserted AS i
  ON r.guid_column = i.guid_column;

これで、テーブル値関数がある場合、コードは非常に似ていますが、パフォーマンスは複数行の操作ではるかに向上し、単一行の操作とほぼ同じであることがわかります。

-- ah, much better
ALTER FUNCTION dbo.GetIDByGUID_TVF
(
  @GuidKey uniqueidentifier
)
RETURNS TABLE
AS
  RETURN (SELECT id = $IDENTITY FROM dbo.tablename WHERE guid_column = @GuidKey);
GO

-- in the trigger:
UPDATE r SET oldkey = f.id
  FROM dbo.related AS r
  INNER JOIN inserted AS i
  ON r.guid_column = i.guid_column
  CROSS APPLY dbo.GetIDByGUID_TVF(i.guid_column) AS f;
9
Aaron Bertrand

これを行うには関数または計算列が必要だと思う理由がわかりません。デフォルト値を使用してテーブルに新しい列を追加し、必要に応じてインデックスを付けることができます。

CREATE TABLE dbo.whatever ( Id INT );

ALTER TABLE dbo.whatever
ADD YourMom UNIQUEIDENTIFIER
        DEFAULT NEWSEQUENTIALID();

CREATE INDEX ix_whatever ON dbo.whatever (YourMom);

質問を更新したので、これが本当にひどい考えに対処しましょう。例を少し簡略化します。

CREATE TABLE dbo.whatever ( Id INT PRIMARY KEY);

CREATE TABLE dbo.ennui ( Id INT PRIMARY KEY, meh INT );
GO 

CREATE FUNCTION dbo.BadIdea ( @notguido INT )
RETURNS INT
WITH SCHEMABINDING, RETURNS NULL ON NULL INPUT
AS
    BEGIN
        DECLARE @out INT;
        SELECT @out = ( SELECT e.Id FROM dbo.ennui AS e WHERE e.meh = @notguido );
        RETURN @out;
    END;
GO 

ALTER TABLE dbo.whatever ADD ishygddt AS dbo.BadIdea(Id)

/*Will fail*/
ALTER TABLE dbo.whatever ALTER COLUMN ishygddt ADD PERSISTED;

/*Will fail*/
CREATE INDEX ix_whatever ON dbo.whatever (ishygddt);

スカラー関数( [〜#〜] schemabinding [〜#〜] を使用した確定的な列でも)に基づいて計算列を永続化しようとすると、データアクセスを実行すると失敗します。

メッセージ4934、レベル16、状態3、行23テーブル 'whatever'の計算された列 'ishygddt'は、列がユーザーまたはシステムデータにアクセスするため、保持できません。

インデックスを作成することもできません。

メッセージ2709、レベル16、状態1、行25テーブル 'dbo.whatever'の列 'ishygddt'は、ユーザーまたはシステムのデータアクセスを行うため、インデックスまたは統計で、またはパーティションキーとして使用できません。

また、関数はデータを取得するために行ごとに実行され、 テーブルに対するすべてのクエリを強制的に逐次実行する になるため、多くの問題に遭遇します。

関数が参照するテーブルのデータを変更し、計算列の関数を呼び出すテーブルからデータを選択する場合、関数がクエリにデータを返さないようにブロックされている、非常に混乱するブロックシナリオが発生する可能性があります一見無関係なテーブルに。

これは至る所で悪い考えです。アーロンは彼のコメントで私が最高のアドバイスだと思うものを与えました:

クエリが実際にそれを必要とするとき(そしてそのときだけ)に相互適用するテーブル値関数を作成しないのはなぜですか。古いキーは決して変更されないため、とにかく計算したり永続化したりする必要はありません。古いキーを複数の場所に保存したい場合は、トリガーで検索を実行してください。

8
Erik Darling

BOLの永続的な計算列 、および関連する 計算列のインデックス について読むことができます。

永続的な計算列で使用できる式には制限があります。 「PERSISTEDが指定されている場合、式は確定的でなければなりません。」

私が正しく理解していれば、次のようになります。

  1. テーブル、それをtと呼びましょう。
  2. テーブルtにはguid列があります。これをgcと呼びましょう。
  3. 追加のテーブルは、t.gcの値を異なるタイプの異なるキー値にマッピングするルックアップであり、レガシーコードで使用されます。テーブルltとレガシーキー列lkを呼び出します。
  4. lt.lktt.lkとして表示し、レガシーコードが引き続き使用できるようにする必要があります。

ビューを使用して調査します。

  1. tt_baseのような名前に変更します。
  2. t_basetに結合し、t_baselt.lkの列を返すltという名前のビューを作成します。
  3. 永続化する必要がある場合は、インデックス付きビューを調べてください。 (エンタープライズ版が必要で、多くの制限がありますが、この結合にはおそらく適切です。)
0

実際の問題

開発者が解決しようとしている問題は、あるデータ型から新しいデータ型へのかなり標準的な移行の問題です。データベースが最初に設計されたときに予期されなかった新しい問題があります。つまり、データベース間でデータを同期する必要があります。これは、特にデータ型が多くのコードで信頼されている場合、コストのかかる作業になる傾向があります。コスト削減策を探すことは完全に不合理ではありません。

彼らの解決策

数学はノーと言う

まず第一に、問題はおそらく数学的に扱いにくいことを理解することが重要です。 A GUIDまたはUUIDは、128ビット、つまり16バイトの数値です。IDENTITY列のサイズは、使用されるデータ型によって異なりますが、通常はINT(32ビット、4バイト);場合によってはBIGINT(64バイト、8バイト)が使用されます。GUIDの範囲をINTの範囲またはBIGINTに完全にマッピングする数学的な方法はありません。列がDECIMAL(38,0)であれば数学的に可能ですが、これは十分な大きさですが、これは非常にまれです。

実用性

GUIDをDECIMAL型にマップすることが可能であっても、それが実用的であるとは限りません。事実上誰もこれを行わないので、マッピングが正しく機能することを保証するために時間(=お金)を費やす必要があります。彼らのソリューションは、奇妙で診断が難しいバグを作成するという重要なリスクをもたらします。

さらに、ソリューションでデータの既存のIDを保持することはできません。これは、IDを含むエンドユーザーのブックマークを破壊する可能性があります。

最後に、それらの解決策は固定されている可能性があります。上記のすべての理由により、これは良い習慣ではありませんが、もしそれが発生した場合、整数キーをすべて新しいもので使い続けるのは「簡単」であるため、すぐにそれから離れることができない可能性があります。コード。

より標準的なアプローチ

既存のシステムに同期を導入する比較的標準的なアプローチは、add新しい一意のIDです。この新しいIDは、古い既存のキーに加えて、データに追加されます。次に、代理キーは同期されません

これにはいくつかの大きな利点があります。

  • データベース間の同期を有​​効にする問題を解決します。
  • 古いキーに依存する既存のコードを変更する必要はありません。 (間違いなく短期的には、そしておそらくこれまでにないでしょう。)
  • 数学的に不可能なマッピングはありません。
  • 既存のキー値は保持されます。

このアプローチには、2つの小さな問題があります。

  • サロゲートキーは同期されないため、サロゲートキーがコード、特にアプリケーションに表示される場合、これらのIDはデータベースごとに異なります。特定の状況(複数の本番データベース間でのある種の複製ではなく、データベースのコピーのテストと開発に同期する)の場合、これはわずかな問題です。ただし、これも解決できるものです。これによって生じる非効率性によってニーズが明らかになると、開発者は特定のターゲットコードを調整して新しい同期IDを使用できます必要に応じて、アプリケーション全体を書き直すことなく。一時的に両方のIDをサポートする可能性さえあります。 (たとえば、Webエンドポイントは整数キーを受け入れてから、GUIDキーにリダイレクトして、ブックマークが壊れていないことを確認します。)これは、整数の使用から徐々に移行する動機にもなります。コード内のキー。
  • 同期コードは、データを外部キーと同期するときに整数の代理IDをマップする必要がある場合があります。ただし、これは扱いにくい問題とはほど遠いものです。関連する同期IDを調べ、それを使用して、宛先データベースの整数代理キーを見つけます。ただし、開発チームはすでに外部キーを既存の代理キーから新しいGUID=キーに切り替える準備が整っているようです。そのため、これはまったく問題にならない可能性があります。

ただし、これらの問題はどちらも管理可能であり、すべてをGUIDに切り替えるのが非常に高額である場合は、トレードオフとして妥当です。

また、このソリューションでは、実行を要求しているクエリを正確に有効にしたことにも注意してください。

これでは今は手遅れになるかもしれませんが、今後は良い情報だと思います。

0
jpmc26