web-dev-qa-db-ja.com

SQL Serverのキー/値ペアテーブルの列ストアインデックス

評価を保存するためのデータベースを構築する必要があります。

各評価には、無制限の数の質問と無制限の数の回答を含めることができます。

各評価の応答は、最大50万まで成長する可能性があります。

各アセスメントの質問は10〜200です。

AssessmentsおよびAssessmentResponsesテーブルは「通常の」リレーショナルテーブルを使用して設計されますが、質問と回答はキーと値のペアとして保存する必要があります。

Assessments
|AssessmentID|Name|JsonSchema|

Questions
|QuestionID|AssessmentID|QuestionValue|QuestionType|

AssessmentResponses
|ResponseID|AssessmentID|RespondentName|Date|JsonResponse|

Answers
|ResponseID|QuestionID|AnswerValueText|AnswerValueDecimal|

ご覧のとおり、Web UIですばやく視覚化すると便利であることがわかったので、評価スキーマと応答もJSON形式で保存します。特にJsonResponseには、次のように、関連するresponseで指定されたすべてのanswersが含まれます。

{
  "interviewDate":"2001/12/28",
  "city":"Mombasa",
  "phone":"123456789",
  "name":"Marco",
  "age":16
}

一般的なクエリは次のとおりです。

  1. 特定のAssesmentIDのすべての回答(ページング)を抽出します。
  2. ageキーの特定のAssessmentIDのすべての回答の平均を計算します。
  3. キー「年齢」の値が25より大きく、キー「市」が「ニューヨーク」に等しいすべての応答を抽出します

クエリNo.2の場合、年齢の値は列AnswerValueDecimalに格納され、すべての数値が格納されることに注意してください。

列ストアインデックスがこの構造でパフォーマンスを増やすかどうか疑問に思いますか?

ElasticSearchインスタンスが非常に役立ちますが、予算の問題のため、この段階では実装できないことに注意してください。

データについての詳細

評価の質問に対する回答は、通常、限られた数の選択肢から選択されます。例では:

  • あなたは結婚していますか? > [はい、いいえ]
  • 建物の種類は? > [コンクリート、スケルトン、エターナイトシート、鉄シート、その他]
  • 市街地? > [北、南、東、西、中央]

クエリは、特定の評価から必要な情報に応じて、エンドユーザーが作成できます。上記の例で、彼らは都市の北と西のエリアにあるエターナイトの建物の数を知りたいと思っています。

しかし、たとえば、「あなたは何歳ですか」という質問もあり、25歳未満の人がいる北と西のエリアにあるエターナイトの建物がいくつあるかを知るために、彼らは前のクエリをフィルタリングしたいと考えています...

JSONフィールドを使用してデータを保存する

SQL Server 2016/2017の新機能を使用して、NVARCHAR(MAX)フィールド内にJSONドキュメントを格納しようとしました。それはかなりうまくいくように見えますが、おそらくテーブル全体がメモリにロードされたときにのみです。

これがdbfiddleです: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=2ce135dc37d72f9db951bbb4e2708baa

5
Giox

テストするよりも良い方法は何ですか?

TL:DRを前もって。同じ応答に対して異なる行をチェックする必要があるため、データを構造化する方法は、複数の条件がある場合に不便になります。 Columnstoreはよりよく機能しますテストクエリの自己結合でバッチモードを使用するためだけです。

ストレージオプションを検討するよりも、別の方法でデータを構造化することで、はるかに多くのことが得られると思います。

これは、500万行の設定と、ある特定の調査を想定して、応答の範囲について市内中心部の人々の平均年齢を検索する最後のクエリの例です。 Dbfiddleリンクも

DROP TABLE IF EXISTS dbo.AnswersRow

CREATE TABLE AnswersRow (
ResponseID INT NOT NULL,
QuestionID INT NOT NULL,
AnswerValueText VARCHAR(20) NULL,
AnswerValueDecimal DECIMAL(15,2) NULL,
CONSTRAINT PK_R_Q PRIMARY KEY CLUSTERED (ResponseID,QuestionID)
)

INSERT dbo.AnswersRow
SELECT TOP 5000000
FLOOR((-1+ROW_NUMBER() OVER(ORDER BY(SELECT 'Joe')))/5+1) AS ResponseID,
(-1+ROW_NUMBER() OVER(ORDER BY(SELECT 'Joe')))%5+1 AS QuestionID,
NULL AS AnswerValueText,
NULL AS AnswerValueDecimal
FROM master.dbo.spt_values a
CROSS JOIN master.dbo.spt_values b

--5 questions
--1) Married 0 or 1
--2) BuildingType
--3) CityArea
--4) Age
--5) Income

--randomize answers
UPDATE ar
SET AnswerValueText = CASE
WHEN QuestionID = 2 THEN (CASE x2.seed
                            WHEN 0 THEN 'Concrete'
                            WHEN 1 THEN 'Skeleton'
                            WHEN 2 THEN 'Eternite Sheets'
                            WHEN 3 THEN 'Iron Sheets'
                            WHEN 4 THEN 'Other'
                            END)
WHEN QuestionID = 3 THEN (CASE x3.seed
                            WHEN 0 THEN 'North'
                            WHEN 1 THEN 'South'
                            WHEN 2 THEN 'East'
                            WHEN 3 THEN 'West'
                            WHEN 4 THEN 'Center'
                            END)
ELSE AnswerValueText END,
ar.AnswerValueDecimal = CASE
WHEN QuestionID = 1 THEN (FLOOR(Rand(CONVERT(BINARY(8),NEWID()))*2))
WHEN QuestionID = 4 THEN (FLOOR(Rand(CONVERT(BINARY(8),NEWID()))*60+17))
WHEN QuestionID = 5 THEN (FLOOR(Rand(CONVERT(BINARY(8),NEWID()))*200000))
ELSE AnswerValueDecimal END
FROM dbo.AnswersRow ar
CROSS APPLY (SELECT FLOOR(Rand(CONVERT(BINARY(8),NEWID()))*5) AS seed) x2
CROSS APPLY (SELECT FLOOR(Rand(CONVERT(BINARY(8),NEWID()))*5) AS seed) x3

SELECT *
INTO dbo.AnswersCol
FROM dbo.AnswersRow

CREATE CLUSTERED COLUMNSTORE INDEX CX_Answers ON dbo.AnswersCol
CREATE NONCLUSTERED INDEX IX_Answer_text ON dbo.AnswersRow(AnswerValueText)
CREATE NONCLUSTERED INDEX IX_Answer_decimal ON dbo.AnswersRow(AnswerValueDecimal)
CREATE NONCLUSTERED INDEX IX_Answer_question ON dbo.AnswersRow(QuestionID) INCLUDE(AnswerValueText,AnswerValueDecimal)


--average age of particular survey, city center
SELECT AVG(AnswerValueDecimal), COUNT(*)
FROM dbo.AnswersRow ar1
WHERE 1=1
AND ar1.ResponseID >= 100000 AND ar1.ResponseID < 200000
AND ar1.QuestionID = 4
AND EXISTS (SELECT 1
            FROM dbo.AnswersRow ar2
            WHERE ar1.ResponseID = ar2.ResponseID
            AND ar2.QuestionID = 3
            AND ar2.AnswerValueText = 'Center'
            )


SELECT AVG(AnswerValueDecimal), COUNT(*)
FROM dbo.AnswersCol ar1
WHERE 1=1
AND ar1.ResponseID >= 100000 AND ar1.ResponseID < 200000
AND ar1.QuestionID = 4
AND EXISTS (SELECT 1
            FROM dbo.AnswersCol ar2
            WHERE ar1.ResponseID = ar2.ResponseID
            AND ar2.QuestionID = 3
            AND ar2.AnswerValueText = 'Center'
            )
3
Forrest