Bry*_*yan 3 performance sql-server pivot view
我的问题的简化版本如下:
我有三个表记录、输入和输出。
记录
Id
DTS
1
2015-01-01 00:00
...
输入
Id
RecordId
InputId
Value
1
1
1
100
...
...
N
1
1
105
输出
Id
RecordId
OutputId
Value
1
1
1
200
...
...
M
1
1
10
这样做是为了避免由于表中列数的限制而限制输入或输出的总数。我正在寻找针对类似于以下内容的查询的最佳(最快)方法:
DTS
Input1
...
InputN
Output1
...
OutputM
2015-01-01 00:00
100
...
105
200
...
10
...
我最初的计划是基于旋转必要的输入和输出并适当地加入它们来创建一个视图。数据只需要只读并且本质上是静态的,所以我想防止它为每个查询扩展。我读到我可以向视图添加索引以“具体化”它,但是如果视图使用 PIVOT,则无法做到这一点。
有没有人对我如何做到这一点有任何建议?或者,如果我以错误的方式处理这个问题,我很乐意接受建议。
鉴于数据很少更改,因此维护索引视图的开销很小,使用索引视图的方法可以帮助提高性能。正如您在尝试创建使用 的索引视图时可能注意到的那样,在索引视图中可以使用PIVOT哪些语法构造有很多限制。
要解决此限制,您可以结合使用聚合(via SUM)和CASE语句。但是,您随后会遇到另一个限制,即UNION无法实现使用的视图。但是,在这种情况下很容易为聚合的输入和输出创建单独的视图,并且该方法值得在您的特定工作负载上进行测试,以查看它是否会产生性能优势。下面是说明这两种方法的示例脚本:
-- Set up test data with 3 records, and each record has 2 inputs and 3 outputs
CREATE TABLE dbo.records (Id INT IDENTITY(1,1) NOT NULL, DTS DATETIME NOT NULL)
GO
INSERT INTO dbo.records (DTS)
VALUES (GETDATE()-2),
(GETDATE()-1),
(GETDATE())
GO
CREATE TABLE dbo.inputs (Id INT IDENTITY NOT NULL, recordId INT NOT NULL, inputId INT NOT NULL, value FLOAT NOT NULL)
GO
INSERT INTO dbo.inputs (recordId, inputId, value)
SELECT r.Id AS recordId, i.inputId, r.Id * i.inputId AS value
FROM dbo.records r
CROSS JOIN (VALUES (1), (2)) AS i(inputId)
GO
CREATE TABLE dbo.outputs (Id INT IDENTITY NOT NULL, recordId INT NOT NULL, outputId INT NOT NULL, value FLOAT NOT NULL)
GO
INSERT INTO dbo.outputs (recordId, outputId, value)
SELECT r.Id AS recordId, i.outputId, -1 * r.Id * i.outputId AS value
FROM dbo.records r
CROSS JOIN (VALUES (1), (2), (3)) AS i(outputId)
GO
-- Create the indexed view for inputs
CREATE VIEW dbo.recordInputs WITH SCHEMABINDING AS
SELECT r.Id AS recordId,
r.DTS,
-- A COUNT_BIG() is mandatory to create an indexed view
COUNT_BIG(*) AS numInputs,
-- Generate the columns input1, ..., inputM
SUM(CASE WHEN i.inputId = 1 THEN i.value ELSE 0 END) AS Input1,
SUM(CASE WHEN i.inputId = 2 THEN i.value ELSE 0 END)Input2
FROM dbo.records r
JOIN dbo.inputs i
ON i.recordId = r.Id
GROUP BY r.Id, r.DTS
GO
CREATE UNIQUE CLUSTERED INDEX UQ_recordInputs ON dbo.recordInputs (recordId)
GO
-- Create the indexed view for outputs
CREATE VIEW dbo.recordOutputs WITH SCHEMABINDING AS
SELECT r.Id AS recordId,
r.DTS,
-- A COUNT_BIG() is mandatory to create an indexed view
COUNT_BIG(*) AS numRows,
-- Generate the columns output1, ..., outputM
SUM(CASE WHEN o.outputId = 1 THEN o.value ELSE 0 END) AS Output1,
SUM(CASE WHEN o.outputId = 2 THEN o.value ELSE 0 END) AS Output2,
SUM(CASE WHEN o.outputId = 3 THEN o.value ELSE 0 END) AS Output3
FROM dbo.records r
JOIN dbo.outputs o
ON o.recordId = r.Id
GROUP BY r.Id, r.DTS
GO
CREATE UNIQUE CLUSTERED INDEX UQ_recordOutputs ON dbo.recordOutputs (recordId)
GO
-- Create the overall view
CREATE VIEW dbo.recordInputsAndOutputs WITH SCHEMABINDING AS
SELECT i.recordId, i.DTS, i.input1, i.input2, o.output1, o.output2, o.output3
FROM dbo.recordInputs i WITH (NOEXPAND) /* In non-enterprise versions of SQL, you will likely need this hint to avoid expanding the indexed view */
JOIN dbo.recordOutputs o WITH (NOEXPAND)
ON o.recordId = i.recordId
GO
-- Test the view (turn on "Include Actual Execution Plan" first in SSMS to confirm usage of the indexed views)
SELECT *
FROM dbo.recordInputsAndOutputs
GO
-- Cleanup
DROP VIEW dbo.recordInputsAndOutputs
DROP VIEW dbo.recordInputs
DROP VIEW dbo.recordOutputs
DROP TABLE dbo.inputs
DROP TABLE dbo.outputs
DROP TABLE dbo.records
GO
Run Code Online (Sandbox Code Playgroud)
需要指出的一个警告是,这些索引视图并没有真正进行任何聚合。我们仍然拥有与以前相同数量的数据元素,但它们只是简单地转换为较少数量的行(和较多数量的列)。即便如此,我还是经常观察到,对包含较少行来表示相同数据的旋转行集进行操作时,性能会显着提高。关于为什么会这样,我会听从更深入的专家的意见,但考虑到基于行的处理模型,SQL Server 需要通过每个查询计划运算符提取每一行,这可能是相当直观的,这样做可能会为每一行带来一些开销(至少对于行模式执行,它涵盖了除并行列存储计划之外的所有内容)。
最后一个注意事项是,如果您发布在您的模式中生成一些基本测试数据的 SQL 代码片段(类似于上面脚本的“设置测试数据”部分),除了只是显示架构。这使别人更容易帮助您并降低误解问题的可能性。