7

SQL Server データベースの現状

次の列を持つテーブル エントリがあります。

  • EntryID (整数)
  • EntryName (nvarchar)
  • EntrySize (整数)
  • EntryDate (日時)

さらに、エントリの追加のメタデータを保存する可能性があるはずです。これらのメタデータの名前と値は自由に選択でき、データベースのテーブル構造を変更せずに動的に追加できる必要があります。各メタデータ キーは、次のデータ型のいずれかになります。

  • 文章
  • 数値
  • 日付時刻
  • ブール値 (True/False)

したがって、次の列を持つメタデータ名とデータ型を表すテーブル DataKey があります。

  • DataKeyID (int)
  • DataKeyName (nvarchar)
  • DataKeyType (smallint) 0: テキスト。1: 数値。2: 日時。3: ビット

Entry 値と DataKey 値の各組み合わせのテーブル DataValue には、メタデータ キーのデータ型に応じて挿入できます。データ型ごとに、NULL 値を許容する列が 1 つあります。このテーブルには次の列があります。

  • DataValueID (整数)
  • EntryID (int) 外部キー
  • DataKeyID (int) 外部キー
  • TextValue (nvarchar) Nullable
  • NumericValue (float) Nullable
  • DateValue (日時) Nullable
  • BoolValue (ビット) Nullable

データベース構造のイメージ:

ここに画像の説明を入力

目標

対象は、WHERE句のように仕様を満たすエントリのリストを取得することです。次の例のように:

予測:

  • メタ データ キー KeyName1 はテキストです
  • メタ データ キー KeyName2 は DateTime です
  • メタデータ キー KeyName3 は数値です
  • メタデータ キー KeyName4 はブール値です

クエリ:

... WHERE (KeyName1 = „Test12345“ AND KeyName2 BETWEEN ’01.09.2012 00:00:00’ AND
’01.04.2013 23:59:00’) OR (KeyName3 > 15.3 AND KeyName4 = True)

目標は、これらのクエリを非常に効率的な方法で実行することです。また、次のような大量のデータも使用します。

  • エントリ数 > 2.000.000
  • 50 から 100 の間、または 100 を超える可能性のあるデータ キーの数
  • エントリごとに少なくとも指定された値のサブセット、または各キーの値 (2.000.000 * 100)

問題

最初の問題は、クエリを作成するときに発生します。通常、クエリには、WHERE 句で使用できる列を含むセットが必要です。この場合、クエリで使用される列はテーブル DataKey のエントリであり、データベース テーブル構造を変更せずにメタデータを動的に追加できます。調査中に、実行時に PIVOT テーブル手法を使用して解決策が見つかりました。しかし、データベースに大量のデータがある場合、このソリューションは非常に遅くなることが判明しました。

質問

  • この目的のためにデータを保存するためのより効率的な方法または構造はありますか?
  • クエリ時のパフォーマンスと時間の消費に関しても、上記の要件を満たすにはどうすればよいでしょうか?

これは、記述されたデータベース構造といくつかのサンプルデータを使用したSQLフィドルです: http://www.sqlfiddle.com/#!3/d1912/3

4

7 に答える 7

6

Entity Attribute Value 設計 (ここにあるもの) の根本的な欠陥の 1 つは、効率的でパフォーマンスの高いクエリの難しさです。

データを格納するためのより効率的な構造は、EAV を放棄し、正規化されたリレーショナル形式を使用することです。ただし、データ構造が変更されると、必然的にデータベースの構造を変更する必要があります(これは自明のはずです)。

TextValue/NumericValue/DateValue/BoolValue フィールドを放棄して単一のsql_variant列に置き換えることで、クエリの複雑さをわずかに軽減できますが、基本的な問題は残ります。

補足として、すべての数値を float として格納すると、お金を扱う必要がある場合に問題が発生します。

于 2013-09-05T07:52:27.523 に答える
1

何が最善か、または設計アプローチについてコメントする資格はないと思います。実際、私はまったく答えない傾向があります。しかし、私はあなたの問題について考え、あなたが時間を割いてそれを明確に説明してくれたと考えました。これが私がそれに取り組む方法です.

各メタデータ データ型を独自のテーブルに格納します。そう

Table MetaData_Text:
    ID int identity
    EntryID int
    KeyName nvarchar(50)
    KeyValue nvarchar(max)

MetaData_DateTime、MetaData_Boolean、および MetaData_Numeric はこれと同じ構造を持ちますが、それぞれのケースで KeyValue 列の適切な異なるデータ型を持ちます。

エントリとこれらの各テーブルの関係は 0-Many です。これらの各テーブルのすべての行は、1 つのエントリに属します。

エントリに新しいメタデータ アイテムを追加するには、EntryID、キー名、可能なメタデータ データ型のオプション パラメータを持つストアド プロシージャを使用します。

 create procedure AddMetaData @entryid int, @keyname varchar(50), @textvalue varchar(max) = null, @datevalue datetime = null, @boolvalue bool = null, @numvalue float = null
 as ...

クエリの場合、(a) メタデータ データ型と (b) そのデータ型で実行する必要があるテストの各タイプを管理する一連の関数を定義します。次に例を示します。

 create function MetaData_HasDate_EQ(@entryid int, @keyname varchar(50), @val datetime)
 returns bool
 as begin
     declare @rv bool
     select @rv = case when exists(
       select 1 from MetaData_DateTime where EntryID = @entryid and KeyName = @keyname and KeyValue = @val) then 1 else 0 end;
     return @rv
 end

に従って、関数参照を必要なクエリ ロジックに組み込みます。

 SELECT ...
 FROM entry e ...
 WHERE (dbo.MetaData_HasText_EQ(e.EntryID, 'KeyName1', 'Test12345') <> 0
     AND dbo.MetaData_HasDate_Btwn(e.EntryID, 'KeyName2', '01.09.2012 00:00:00', '01.04.2013 23:59:00') <> 0)
   OR (dbo.MetaData_HasNum_GT(e.EntryID, 'KeyName3', 15.3) <> 0 
     AND dbo.MetaData_HasBool_EQ(e.EntryID, 'KeyName4', 1) <> 0)
于 2013-09-10T23:04:49.613 に答える
1

そのようなデータ構造のパフォーマンスの問題は、構造を作り直す必要があるかもしれないと私は信じています。

ただし、この非常に単純な動的 SQL を使用すると、必要に応じてクエリを実行でき、Entry テーブルに 100,000 行以上、DataValue テーブルに 500,000 行以上の簡単なテストを行ったところ、かなり高速に実行されるようです。

-- !! CHANGE WHERE CONDITION AS APPROPRIATE
--declare @where nvarchar(max)='where Key0=0'
declare @where nvarchar(max)='where Key1<550'

declare @sql nvarchar(max)='select * from Entry e';

select @sql=@sql
    +' outer apply (select '+DataKeyName+'='
    +case DataKeyType when 0 then 'TextValue' when 1 then 'NumericValue' when 2 then 'DateValue' when 3 then 'BoolValue' end
    +' from DataValue v where v.EntryID=e.EntryID and v.DataKeyID='+cast(DataKeyID as varchar)
    +') '+DataKeyName+' '
from DataKey;

set @sql+=@where;

exec(@sql);
于 2013-09-12T13:48:11.733 に答える
0

テーブルが更新される頻度、新しい属性が追加される頻度などに関する背景情報を指定していません...

入力を見ると、正規化されたデータを平坦化するスナップショットを使用できると思います。列を手動で追加する必要があるため理想的ではありませんが、非常に高速になる可能性があります。スナップショットは、ユーザーのニーズに応じて間隔を空けて定期的に更新できます。

于 2013-09-11T22:36:28.300 に答える
0

最初に、クエリに関して非常に非効率的であるにもかかわらず、人々が EAV や KVP を使用する理由を教えてください。ブログや教科書には、もっともらしい理由がたくさんあります。しかし実際には、非協力的な DBA との取引を避けるためです。

データ量が少ない小規模な組織の場合、非効率性が目立たないため、多目的データベース (OLTP + DW) を使用しても問題ありません。データベースが大きくなったら、オンライン データをデータ ウェアハウスに複製します。さらに、データが分析用である場合は、リレーショナル データ ウェアハウスから次元モデルまたは消費用のフラット アンド ワイドにさらに複製する必要があります。

これは、大規模な組織に期待するデータ モデルです。

  1. OLTP
  2. リレーショナル データ ウェアハウス
  3. レポート用の次元モデル
  4. 分析用のデータマート。

したがって、質問に答えるために、KVP テーブルに対してクエリを実行するべきではなく、その上にビューを作成しても改善されません。物理テーブルにフラット化 (ピボット) する必要があります。あなたが持っているのは 1 と 2 のハイブリッドです。#3 のユーザーがいない場合は、#4 を構築してください。

于 2013-09-14T19:38:30.160 に答える
0

データ型ごとに 1 つずつ、4 つのテーブルを使用します。

MDat1
DataValueID (int)
EntryID (int) Foreign-Key
DataKeyID (int) Foreign-Key
TextValue (nvarchar) Nullable
MDat2
DataValueID (int)
EntryID (int) Foreign-Key
DataKeyID (int) Foreign-Key
NumericValue (float) Nullable
MDat3
DataValueID (int)
EntryID (int) Foreign-Key
DataKeyID (int) Foreign-Key
DateValue (datetime) Nullable
MDat4
DataValueID (int)
EntryID (int) Foreign-Key
DataKeyID (int) Foreign-Key
BoolValue (bit) Nullable

パーティショニングを利用できる場合は、4 つのテーブルすべての DataKeyID でそれを使用する必要があります。次に、4つのビューを使用する必要があります:

SELECT ... FROM Entry JOIN MDat1 ON ... EnMDat1
SELECT ... FROM Entry JOIN MDat2 ON ... EnMDat2
SELECT ... FROM Entry JOIN MDat3 ON ... EnMDat3
SELECT ... FROM Entry JOIN MDat4 ON ... EnMDat4

したがって、この例:

WHERE (KeyName1 = „Test12345“ AND KeyName2 BETWEEN ’01.09.2012 00:00:00’ AND
’01.04.2013 23:59:00’) OR (KeyName3 > 15.3 AND KeyName4 = True)

次のようになります:

...EnMDat1 JOIN EnMDat3 ON ... AND EnMDat1.TextValue ='Test12345' AND EnMDat3.DateValue BETWEEN ’01.09.2012 00:00:00’ AND
’01.04.2013 23:59:00’)
...
UNION ALL 
...
EnMDat2 JOIN EnMDat4 ON ... AND EnMDat2.NumericValue > 15.3 AND EnMDat4.BoolValue = True

これは、1 つのメタデータ テーブルよりも高速に機能します。ただし、where 句にさまざまなシナリオがある場合は、クエリを作成するためのエンジンが必要になります。ビューを省略して、ステートメントを毎回ゼロから作成することもできます。

于 2017-11-30T16:14:05.817 に答える