SQL Server 上键/值对表的列存储索引

Gio*_*iox 5 performance sql-server columnstore json

我需要建立一个数据库来存储评估。

每个评估可以有无限数量的问题和无限数量的回答。

每个评估的响应可以增长到 500k。

每个评估的问题可以是 10 到 200。

虽然评估和评估响应表将使用“正常”关系表设计,但问题和答案应存储为键/值对。

Assessments
|AssessmentID|Name|JsonSchema|

Questions
|QuestionID|AssessmentID|QuestionValue|QuestionType|

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

Answers
|ResponseID|QuestionID|AnswerValueText|AnswerValueDecimal|
Run Code Online (Sandbox Code Playgroud)

如您所见,我还以 JSON 格式存储了评估模式和响应,因为我发现在 Web UI 上快速可视化非常有用。特别是 JsonResponse 包含相关响应中给出的所有答案,如下所示:

{
  "interviewDate":"2001/12/28",
  "city":"Mombasa",
  "phone":"123456789",
  "name":"Marco",
  "age":16
}
Run Code Online (Sandbox Code Playgroud)

典型的查询可以是:

  1. 提取特定AssesmentID 的所有答案(分页)。
  2. 计算年龄键的特定 AssessmentID的所有答案的平均值。
  3. 提取键“age”的值大于 25 且键“city”等于“New York”的所有响应

请注意,对于查询 2,年龄值将存储​​在AnswerValueDecimal列中,我将在其中存储所有数值。

我想知道列存储索引是否会提高这种结构的性能

请注意,我知道 ElasticSearch 实例对我有很大帮助,但由于预算问题,我们现阶段无法实施。

关于数据的更多细节

评估中问题的答案通常是从有限数量的选项中选出的。例如:

  • 你结婚了吗?> [是,否]
  • 建筑类型?> [混凝土,骨架,永恒板,铁板,其他]
  • 市区?> [北、南、东、西、中]

最终用户可以根据他们从特定评估中需要的信息来构建查询。举上面的例子,他们想知道城市的北部和西部地区有多少永恒的建筑,到底有多少。

但是比如还有一个问题“你多大了?”,他们想过滤之前的查询,知道北区和西区有多少Eternite建筑,那里有一个年龄低于25岁的人……

使用 JSON 字段存储数据

我尝试使用 SQL Server 2016/2017 中的新功能将 JSON 文档存储在 NVARCHAR(MAX) 字段中。它似乎工作得很好,但可能只有整个表加载到内存中。

这是dbfiddle:https ://dbfiddle.uk/ ? rdbms = sqlserver_2017 & fiddle = 2ce135dc37d72f9db951bbb4e2708baa

For*_*est 3

还有什么比测试更好的方法呢?

TL:DR 在前面。当存在多个条件时,构建数据的方式会变得很尴尬,因为您必须检查不同的行以获得相同的响应。列存储在我的测试查询的自连接中使用批处理模式效果更好。

我希望您能够通过以不同的方式构建数据而不是查看存储选项获得更多的收益。

下面是一个包含 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'
            )
Run Code Online (Sandbox Code Playgroud)