【问题标题】:Cannot create CLUSTERED INDEX on a View due to LEFT JOIN or subquery由于 LEFT JOIN 或子查询,无法在视图上创建 CLUSTERED INDEX
【发布时间】:2012-03-28 23:52:41
【问题描述】:

我为视图中使用的查询创建了两个选项,它们返回我需要的结果。我需要重写任一选项,以便可以在 Indexed View 中使用它。都失败了 在视图上创建唯一聚集索引时。第一个因 LEFT OUTER JOIN 而失败,第二个因子查询而失败。我相信两者都会失败,因为 自加入。

找到Creating Indexed Views后,有一大串无法使用的TSQL语法元素。其中:派生表、UNION、EXCEPT、INTERSECT、子查询、 外连接或自连接、TOP、ORDER BY、DISTINCT、MAX...

对于每个唯一的Company,查询应该获得最大的CompanyIDStatuses 表中的 StatusName 也需要显示,我只是添加它以防它影响 解决方案。当前是INNER JOIN,因此不会导致创建索引出现问题。

Companies 表的示例,所有 3 列都是 INT

CompanyID Company Revision
1         1       1
2         1       2
3         2       1
4         2       2

查询应该返回:

CompanyID Company Revision
2         1       2
4         2       2

这是我创建的两个选项:

SELECT t1.CompanyID, t1.Company, t1.Revision, Statuses.StatusName
FROM dbo.Companies AS t1

LEFT OUTER JOIN dbo.Companies AS t2
ON t1.Company = t2.Company AND t1.CompanyID < t2.CompanyID

INNER JOIN dbo.Statuses
ON dbo.Statuses.StatusID = t1.StatusID

WHERE t2.Company IS NULL

还有一个:

SELECT t1.CompanyID, t1.Company, t1.Revision, Statuses.StatusName
FROM dbo.Companies AS t1

INNER JOIN dbo.Statuses
ON dbo.Statuses.StatusID = t1.StatusID

WHERE t1.Company NOT IN (SELECT t2.Company from dbo.Companies AS t2 WHERE t1.CompanyID < t2.CompanyID)

所以,我的问题是,是否可以重写任一查询以在索引视图中使用?

我使用的是 MS SQL Server 2008 R2 和 2005。

【问题讨论】:

  • 为什么您认为解决您遇到的任何问题(但您没有说明)的唯一解决方案是索引视图?您能否说明您的实际问题,而不是告诉我们为什么不能使用索引视图来解决它?
  • 对不起,让我澄清一下。我们的硬件性能有限,并且正在寻找一种方法来提高查询性能,同时尽可能少地更改数据库模式。非索引视图已经到位。基表(本例中为公司)中将有 100,000 条记录,但通常从任何视图返回的记录少于 100 条。如果可以重写相当简单的查询,那么索引视图似乎是一个很好的解决方案。
  • 如果您找到了一种构造结果集的方法并且它在“禁止”列表中,那么构造相同结果集的任何其他方法也可能会失败。索引视图的限制列表不仅仅是随机选择的——它们与索引在幕后的实现方式有关。
  • 替代方案:重新设计 - 已经看到了您的目标。如果您安排行的最新版本始终具有 NULL Revision 值,那么您可以基于 that 创建索引视图。此外,您可以强制执行每个公司只有一个非 NULL 修订的约束。当然,插入会出现问题(插入新时必须在现有行中分配修订),因此您必须切换到MERGE(并接受将在插入之后分配修订)
  • (当我说非 NULL 时,我的意思是 NULL:每个公司只能强制执行一个 NULL 修订版)

标签: sql sql-server tsql clustered-index


【解决方案1】:

很遗憾,你不能。

您的查询不仅需要 LEFT JOIN,而且还需要将同一个表与自身进行 LEFT JOIN。并引用 BooksOnline 和您的问题...

The SELECT statement in the view cannot contain the following Transact-SQL syntax elements: 
 - Outer or self joins.

另一种选择可能是创建一个真实的映射表,您可以通过触发器保持更新。记录随着Companies 的变化而创建/删除,记录随着Statuses 的变化而更新。


同样,视图被内嵌扩展到使用它们的查询中(除非您使用NOEXPAND 提示明确说明)。您是否检查过查询的执行计划,看看是否可以在基表上创建更合适的索引?

编辑

另一种查询布局,只是作为一种选择...

;WITH
  sequenced_companies
AS
(
  SELECT
    ROW_NUMBER() OVER (PARTITION BY Company ORDER BY CompanyID DESC) AS sequence_id,
    *
  FROM
    dbo.companies
)
SELECT
  *
FROM
  sequenced_companies
INNER JOIN
  dbo.statuses
    ON statuses.StatusID = sequenced_companies.StatusID
WHERE
  sequenced_companies.sequence_id = 1

使用(Company, CompanyID DESC) 上的索引,这应该很快。 (虽然仍然不适合可索引视图。)

【讨论】:

  • 不认为这是可能的,但值得要求学习新的东西。有趣的方法。看起来我必须有额外的表格或额外的视图,所以到目前为止我会尝试两个建议的答案。还没有检查执行计划,但我相信索引没问题。对于CompaniesCompanyID 是身份、PK、索引。将对此进行调查,看看是否有任何突出的地方。
  • 您能解释一下为什么同一家公司多次使用不同的 CompanyID 值吗?在我看来,公司而不是代理身份值应该是关键。
  • @njb - 另一个查询,以及companies 的建议索引,添加以防万一。
  • @AaronBertrand - 我们对基表有一些严格的限制。请参阅我对您建议的答案的评论。
  • 是的,我了解了这些限制,但我不明白 WHY。如果行的旧版本不相关(或不相关),请将它们移动到不同的表中。不知道为什么你会想要这样的要求,比如“让我们永远保留公司的每个版本,在这张表中我们需要实时 OLTP 活动。”
【解决方案2】:

与其创建排他性视图,不如尝试另一种方式:

CREATE VIEW dbo.HighestCompany
AS
  SELECT t1.CompanyID, t1.Company, t1.Revision, s.StatusName
    FROM dbo.Companies AS t1
    INNER JOIN (
      SELECT Company, HighestCompany = MAX(CompanyID) 
      FROM dbo.Companies GROUP BY Company
    ) AS t2
    ON t1.Company = t2.Company
    AND t1.CompanyID = t2.HighestCompany -- not sure if CompanyID is unique
    INNER JOIN dbo.Statuses AS s
    ON s.StatusID = t1.StatusID;

您仍然无法为此创建索引视图,但它可能比您当前拥有的版本更好一些(当然,取决于几个因素,包括公司索引和选择性)。

除此之外,我认为要提高性能,您需要查看基表上的索引策略。为什么您的 Companies 表允许多个具有相同名称和不同 ID 的公司?也许这是问题的一部分,您应该将当前相关的公司存储在单独的表中。

您可以按以下方式执行此操作(请记住,我在这里猜测数据类型和最佳索引):

CREATE SCHEMA hold AUTHORIZATION dbo;
GO
CREATE SCHEMA cache AUTHORIZATION dbo;
GO
CREATE TABLE dbo.HighestCompany
(
  CompanyID INT, 
  Company NVARCHAR(255) PRIMARY KEY,
  Revision INT,
  StatusName NVARCHAR(64)
);
GO
CREATE TABLE cache.HighestCompany
(
  CompanyID INT, 
  Company NVARCHAR(255) PRIMARY KEY,
  Revision INT,
  StatusName NVARCHAR(64)
);
GO

现在,无论您认为这些数据需要刷新多少次,您都可以运行执行以下操作的作业:

TRUNCATE TABLE cache.HighestCompany;

INSERT cache.HighestCompany(CompanyID, Company, Revision, StatusName)
SELECT t1.CompanyID, t1.Company, t1.Revision, s.StatusName
        FROM dbo.Companies AS t1
        INNER JOIN (
          SELECT Company, HighestCompany = MAX(CompanyID) 
          FROM dbo.Companies GROUP BY Company
        ) AS t2
        ON t1.Company = t2.Company
        AND t1.CompanyID = t2.HighestCompany
        INNER JOIN dbo.Statuses AS s
        ON s.StatusID = t1.StatusID;

-- this is a fast, metadata operation that should result
-- in minimal blocking and disruption to end users:
BEGIN TRANSACTION;
  ALTER SCHEMA hold TRANSFER dbo.HighestCompany;
  ALTER SCHEMA dbo TRANSFER cache.HighestCompany;
  ALTER SCHEME cache TRANSFER hold.HighestCompany;
COMMIT TRANSACTION;

如果您发现公司变化如此频繁,或者数据确实需要保持最新状态,这是不切实际的,您可以按照@Dems 的建议使用触发器执行类似的操作。

【讨论】:

  • @Dems 我对查询的阅读显示“在所有与公司匹配的公司(我假设是名称)中,排除所有没有最高 companyID 的公司”......现在我不确定公司与 companyID 中的数据到底是什么,因为它们的名称非常模糊,但我相信我的视图将返回与 OP 视图相同的行。优化器可能不会对它有任何不同的处理,但值得一试。 (这也是您可以定期运行并将结果缓存在某处的东西。)
  • 'CompanyID' 只是身份列。 “公司”是每个公司唯一的数字,即使公司的其他细节发生变化(在最初未说明的列中),它也永远不会改变。我们要求基表上没有“删除”或“更新”权限。这就是“修订”列发挥作用的地方。对于每次更改,“修订”都会增加。公司中有一些额外的列(公司名称、地址等)。其他基表有更多列并且更新更频繁,这只是示例中最简单的表/视图。
  • CompanyIDCompanyRevision 都是 INTCompanyName等是NVARCHAR(255)
  • @njb - 不幸的是,没有办法让 SQL Server 知道你在这个没有 DELETE/UPDATE 的“限制”下操作(当然不能强制执行,因为sa 可以执行这样的操作)。如果系统只需要在基表上支持 INSERT,那么很多索引视图限制将不存在。
  • @njb - 如果更新必须立即进行,我可能会选择触发器,或者如果不是 Aaron 的解决方案涉及缓存。可以肯定的是,您将无法强制/欺骗 DB 引擎为此创建真正的索引视图。
猜你喜欢
  • 1970-01-01
  • 2019-08-06
  • 1970-01-01
  • 1970-01-01
  • 2021-01-28
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 2011-03-28
相关资源
最近更新 更多