【问题标题】:What if the column to be indexed is nvarchar data type in SQL Server?如果要索引的列是 SQL Server 中的 nvarchar 数据类型怎么办?
【发布时间】:2016-03-01 14:09:53
【问题描述】:

如下图所示,我通过连接多个表来检索数据。另一方面,由于 Event 表的 FK 列 (EmployeeID) 中没有数据,我必须使用 CardNo (nvarchar) 字段来连接两个表。另一方面,Event 和 Employee 表中 CardNo 字段的位数不同,我还必须使用 SQL Server 的 RIGHT 函数,这使得查询的执行时间大约延长了 10 倍。那么,在这个场景中我该怎么办呢?我可以使用 CardNo 字段而不将其数据类型更改为 int 等(因为更改后可能会出现其他问题,最好在不更改其数据类型的情况下找到解决方案)。这也是下面查询的执行计划。

查询:

; WITH a AS (SELECT emp.EmployeeName, emp.Status, dep.DeptName, job.JobName, emp.CardNo 
    FROM TEmployee emp 
    LEFT JOIN TDeptA AS dep ON emp.DeptAID = dep.DeptID 
    LEFT JOIN TJob AS job ON emp.JobID = job.JobID),                           

b AS (SELECT eve.EventID, eve.EventTime, eve.CardNo, evt.EventCH, dor.DoorName 
    FROM TEvent eve LEFT JOIN TEventType AS evt ON eve.EventType = evt.EventID
    LEFT JOIN TDoor AS dor ON eve.DoorID = dor.DoorID) 
    SELECT * FROM b LEFT JOIN a ON RIGHT(a.CardNo, 8) = RIGHT(b.CardNo, 8)

ORDER BY b.EventID ASC

【问题讨论】:

  • 为什么是 MySQL 标签? (那是另一种产品……)
  • 您可以将RIGHT() 的内容存储在数据库中,而不是每次都即时计算。由于您没有提供任何数据,我真的不知道这会是什么样子
  • 这里的问题不在于要连接的数据是 varchar,而是您要连接其中的一部分,这需要您将列包装在一个函数中。这会使查询变为 nonSARGable,并且它必须为强制索引扫描的每一行计算正确的 8 个字符。
  • @JoePhilllips 感谢您的回复。您是指带有数据的 CardNo 吗?如果是这样,这里的示例数据为 00014293720、00007004720、00013145687、00012597006。如果您需要其他数据或表格结果,请告诉我。
  • 解决问题的绝对最佳方法是停止将多条信息存储在单个值中。当您只需要加入某个值的一部分时,几乎总是会出现这种情况。第二个最佳选择是@JoePhilllips 已经建议的,将这部分信息放在计算列中。

标签: sql sql-server sql-server-2008 join indexing


【解决方案1】:

您可以像这样向表中添加计算列:

ALTER TABLE TEmployee -- Don't start your table names with prefixes, you already know they're tables
ADD CardNoRight8 AS RIGHT(CardNo, 8) PERSISTED

ALTER TABLE TEvent
ADD CardNoRight8 AS RIGHT(CardNo, 8) PERSISTED

CREATE INDEX TEmployee_CardNoRight8_IDX ON TEmployee (CardNoRight8)
CREATE INDEX TEvent_CardNoRight8_IDX ON TEvent (CardNoRight8)

您不需要保留该列,因为它已经符合要编制索引的计算列的条件,但是添加 PERSISTED 关键字应该不会造成伤害,并且可能有助于其他查询的性能。这会对更新和插入造成轻微的性能影响,但在您的情况下这可能没问题,除非您一次导入大量数据(数百万行)。

不过,更好的解决方案是确保应该匹配的列实际匹配。如果卡号的右 8 个字符有意义,那么它们不应该是卡号的一部分,它们应该是另一列。如果这是一个表使用前导零而另一个不使用前导零的问题,那么您应该修复该数据以保持一致,而不是像这样将解决方法放在一起。

【讨论】:

  • 非常感谢您的回答。实际上我试过了,但是在创建索引时出错:“表 'TEmployee' 中的列 'CardNoRight8' 的类型对于用作索引中的键列无效。”。我认为这是由于 CardNo 或 CardNoRight8 字段的数据类型。那么,是否可以在上面或以后的表格更改期间定义它?如果不是,如何解决这个问题?
  • 在添加列时尝试添加PERSISTED 关键字。在我自己的测试中,这不是必需的,但是如果没有看到您的表结构,可能会遗漏一些东西。
  • 非常感谢您的回答。我还为遇到类似问题的人发布了步骤。投票+
  • 请提出一个新问题,以便遇到相同问题的其他人可以更轻松地找到它,然后在这里尝试偶然发现我们的 cmets。评论不适用于扩展讨论。谢谢
【解决方案2】:

这一行花费了您 86% 的查询时间:

LEFT JOIN a ON RIGHT(a.CardNo, 8) = RIGHT(b.CardNo, 8)

发生这种情况是因为它必须在每一行的这些字段上运行RIGHT(),然后将它们与另一个表匹配。这显然是低效的。

最直接的解决方案可能是完全删除RIGHT(),或者将其重新实现为表中的内置列,这样就不必在查询运行时即时计算.

在插入记录时,您还必须插入卡号的右八位数字并将其存储在此字段中。我最初的想法是使用计算列,但我认为这些列不能被索引,所以你必须使用常规列。

; WITH a AS (
    SELECT emp.EmployeeName, emp.Status, dep.DeptName, job.JobName, emp.CardNoRightEight 
    FROM TEmployee emp 
    LEFT JOIN TDeptA AS dep ON emp.DeptAID = dep.DeptID 
    LEFT JOIN TJob AS job ON emp.JobID = job.JobID
),                           
b AS (
    SELECT eve.EventID, eve.EventTime, eve.CardNoRightEight, evt.EventCH, dor.DoorName 
    FROM TEvent eve LEFT JOIN TEventType AS evt ON eve.EventType = evt.EventID
    LEFT JOIN TDoor AS dor ON eve.DoorID = dor.DoorID
) 
SELECT *
FROM b
LEFT JOIN a ON a.CardNoRightEight = b.CardNoRightEight
ORDER BY b.EventID ASC

【讨论】:

  • 其实我第一次尝试删除 RIGHT() ,但之后它无法正确比较卡号,我不得不使用它。另一方面,我不知道如何实现 CardNoRightEight?您能否通过解释来更新您的答案?谢谢..
  • 在计算列上创建索引有一些额外的要求,但在这种情况下它会满足所有要求,所以应该没问题。
  • UPDATE TEmployee SET CardNoRightEight = RIGHT(CardNo, 8); UPDATE TEvent SET CardNoRightEight = RIGHT(CardNo, 8);
  • 已创建计算列,但在创建索引期间出错:“表 'TEmployee' 中的列 'CardNoRight8' 的类型对于用作索引中的键列无效。” .我认为这是由于 CardNo 或 CardNoRight8 字段的数据类型。那么,是否可以在上面或以后的表格更改期间定义它?如果不是,如何解决这个问题?
  • @hexadecimal 你使用了什么数据类型?使用你需要的最小的 varchar
【解决方案3】:

这将帮助您了解如何将计算列添加到数据库中。

create table #temp (test varchar(30))
insert into #temp
values('000456')

alter table #temp
add test2 as right(test, 3) persisted

select * from #temp

另一种选择是修复数据和数据条目,使两列具有相同的数据类型并包含相同的前导零(或删除它们)

【讨论】:

  • 已创建计算列,但在创建索引期间出错:“表 'TEmployee' 中的列 'CardNoRight8' 的类型对于用作索引中的键列无效。” .我认为这是由于 CardNo 或 CardNoRight8 字段的数据类型。那么,是否可以在上面或以后的表格更改期间定义它?如果不是,如何解决这个问题?
  • 非常感谢您的回答。我还为遇到类似问题的人发布了步骤。投票+
【解决方案4】:

非常感谢您的帮助。在您的回答的帮助下,我设法在使用计算列后的第一步将查询执行时间从 2 分钟减少到 1 分钟。之后,在为这些列创建索引时,我设法将执行时间减少到 3 秒。哇,真的很完美:)

以下是为遇到类似问题的人发布的步骤:

第一步:向表中添加计算列(由于 CardNo 字段是 nvarchar 数据类型,我将计算列的数据类型指定为 int):

ALTER TABLE TEvent ADD CardNoRightEight AS RIGHT(CAST(CardNo AS int), 8)  
ALTER TABLE TEmployee ADD CardNoRightEight AS RIGHT(CAST(CardNo AS int), 8) 


第二步:为计算列创建索引,以便更快地执行查询:

CREATE INDEX TEmployee_CardNoRightEight_IDX ON TEmployee (CardNoRightEight)
CREATE INDEX TEvent_CardNoRightEight_IDX ON TEvent (CardNoRightEight)


第 3 步:使用其中的计算列更新查询:

; WITH a AS (
    SELECT emp.EmployeeName, emp.Status, dep.DeptName, job.JobName, emp.CardNoRightEight --emp.CardNo 
    FROM TEmployee emp 
    LEFT JOIN TDeptA AS dep ON emp.DeptAID = dep.DeptID 
    LEFT JOIN TJob AS job ON emp.JobID = job.JobID
    ),                         
b AS (
    SELECT eve.EventID, eve.EventTime, evt.EventCH, dor.DoorName, eve.CardNoRightEight --eve.CardNo
    FROM TEvent eve 
    LEFT JOIN TEventType AS evt ON eve.EventType = evt.EventID 
    LEFT JOIN TDoor AS dor ON eve.DoorID = dor.DoorID) 

SELECT * FROM b LEFT JOIN a ON a.CardNoRightEight = b.CardNoRightEight --ON RIGHT(a.CardNo, 8) = RIGHT(b.CardNo, 8)
ORDER BY b.EventID ASC

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-05-22
    • 2018-07-20
    • 1970-01-01
    • 2019-11-10
    • 1970-01-01
    • 1970-01-01
    • 2014-03-04
    • 1970-01-01
    相关资源
    最近更新 更多