如果要索引的列在SQL Server中是nvarchar数据类型,该怎么办?

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

我通过连接多个表来检索数据,如下图所示。另一方面,由于事件表的FK列(EmployeeID)中没有数据,我必须使用CardNo(nvarchar)字段来连接这两个表。另一方面,事件表和员工表中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


这一行花费了您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

这一行花费了您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

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

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

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

select * from #temp

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

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

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

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

select * from #temp

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

您可以向表中添加计算列,如下所示:

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)
您不需要持久化列,因为它已经与要索引的计算列的条件相匹配,但是添加
persistend
关键字应该不会有什么坏处,并且可能有助于其他查询的性能。它会对更新和插入造成轻微的性能影响,但在您的情况下,这可能没问题,除非您一次导入大量数据(数百万行)


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

您可以向表中添加一个计算列,如下所示:

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)
您不需要持久化列,因为它已经与要索引的计算列的条件相匹配,但是添加
persistend
关键字应该不会有什么坏处,并且可能有助于其他查询的性能。它会对更新和插入造成轻微的性能影响,但在您的情况下,这可能没问题,除非您一次导入大量数据(数百万行)


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

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

以下是针对那些患有类似问题的人发布的步骤:

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


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

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

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

以下是针对那些患有类似问题的人发布的步骤:

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


第二步: