尝试获取特定字段值在日志表中最后一次更改的日期和时间时,提高性能

问题描述 投票:1回答:1

我正在使用的数据

请考虑以下两个数据库表:

CREATE TABLE [dbo].[Contact](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Contact_UID] [uniqueidentifier] NOT NULL CONSTRAINT [DF_Contact_Contact_UID]  DEFAULT (newsequentialid()),
    [Name] [nvarchar](255) NOT NULL,
    [ContactStatus] [nvarchar](255) NOT NULL)

CREATE TABLE [dbo].[Contact_Log](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [LogDate] [datetimeoffset](7) NOT NULL CONSTRAINT [DF_Contact_Log_LogDate]  DEFAULT (sysdatetimeoffset()),
    [Contact_UID] [uniqueidentifier] NOT NULL CONSTRAINT [DF_Contact_Log_Contact_UID]  DEFAULT (newsequentialid()),
    [Name] [nvarchar](255) NOT NULL,
    [ContactStatus] [nvarchar](255) NOT NULL)

联系人表是联系人记录的主表。它存储联系人的姓名和状态(例如“活着”,“死亡”或其他)。

Contact_Log表存储对Contact表所做的所有更改。

所以这是一些示例数据:

联系人:

+----+--------------------------------------+------+---------------+
| ID | Contact_UID                          | Name | ContactStatus |
+----+--------------------------------------+------+---------------+
| 1  | 62918AC1-1C6C-4DEB-B7F8-5D5EF913F667 | John | Dead          |
+----+--------------------------------------+------+---------------+
| 2  | F7844037-2FF5-47B9-874D-C0920E7DC092 | Jane | Alive         |
+----+--------------------------------------+------+---------------+

Contact_Log:

+----+--------------------------------------+------+---------------+------------+
| ID | Contact_UID                          | Name | ContactStatus | LogDate    |
+----+--------------------------------------+------+---------------+------------+
| 1  | 62918AC1-1C6C-4DEB-B7F8-5D5EF913F667 | John | Alive         | 2019-01-01 |
+----+--------------------------------------+------+---------------+------------+
| 2  | 62918AC1-1C6C-4DEB-B7F8-5D5EF913F667 | John | Dead          | 2019-01-02 |
+----+--------------------------------------+------+---------------+------------+
| 3  | 62918AC1-1C6C-4DEB-B7F8-5D5EF913F667 | John | Dead          | 2019-01-03 |
+----+--------------------------------------+------+---------------+------------+
| 4  | F7844037-2FF5-47B9-874D-C0920E7DC092 | Jane | Alive         | 2019-01-04 |
+----+--------------------------------------+------+---------------+------------+

注意:至此,我还没有在该表上添加任何索引或类似的内容。

测试场景

以上只是一些示例数据。我正在测试的数据具有以下行计数:

联系人:〜10,000行

Contact_Log:〜3,000,000行

我目前正在使用SQL Server 2008 R2进行测试。因此,首选在该版本及更高版本中受支持的解决方案。

我想要达到的目标

[基本上,我正在尝试制定一个查询,该查询可以告诉我LogDate字段上一次更改的时间,对于特定的ContactStatus,取自Contact_UID表。

例如,如果我感兴趣的记录是“ John”,那么结果应该是“ 2019-01-02”。因为这是John的ContactStatus上次更改的日期(即,它从“ Alive”更改为“ Dead”)。

最终,我想将此查询放入函数中。可以通过传入Contact_UID和我要检查的字段名称来调用的函数。然后可以将此函数作为更通用查询的一部分来调用。例如:

Contact_Log

到目前为止我尝试过的事情

嗯,我已经尝试了一些方法,尽管我可以获得想要的结果。我的尝试确实遇到了性能问题。

注意:尽管我真的只想要一个datetimeoffset结果。有些尝试只是为了验证数据的准确性而包括更多的数据/字段。

尝试1:

SELECT Name, MyFunction('62918AC1-1C6C-4DEB-B7F8-5D5EF913F667', 'ContactStatus') AS StatusLastChanged FROM Contact

问题1:太慢。等待了近一个小时而没有结果,我不得不停止查询。

尝试2:

SELECT TOP(1) a.LogDate
FROM Contact_Log AS a
WHERE a.Contact_UID = '62918AC1-1C6C-4DEB-B7F8-5D5EF913F667' 
AND a.ContactStatus <>
(
SELECT TOP(1) b.ContactStatus
FROM Contact_Log AS b
WHERE b.Contact_UID = '62918AC1-1C6C-4DEB-B7F8-5D5EF913F667'
AND a.LogDate > b.LogDate
ORDER BY b.LogDate DESC
)
ORDER BY LogDate DESC

问题2:这有效,并且给了我正确的数据集。但是,这需要6秒钟,这太慢了。请记住,它将需要在更通用的查询(约10,000行)中作为函数使用。

尝试3:现在,这基本上与尝试2相同,希望我尝试应用SELECT A.LogDate FROM (SELECT ROW_NUMBER() OVER (ORDER BY LogDate DESC, ID DESC) AS rnum, ID, LogDate, Contact_UID, ContactStatus FROM Contact_Log) A LEFT JOIN (SELECT ROW_NUMBER() OVER (ORDER BY LogDate DESC, ID DESC) AS rnum, ID, LogDate, Contact_UID, ContactStatus FROM Contact_Log) B ON A.rnum = B.rnum-1 WHERE (B.rnum IS NULL OR (A.Contact_UID = '62918AC1-1C6C-4DEB-B7F8-5D5EF913F667' AND B.Contact_UID = '62918AC1-1C6C-4DEB-B7F8-5D5EF913F667' AND A.ContactStatus != B.ContactStatus)) ORDER BY A.rnum ,以便可以得到我想要的结果。

TOP(1)

问题3:令我惊讶的是,这比尝试2花费的时间长得多,尽管我所做的只是在开始时添加SELECT TOP(1) A.LogDate FROM (SELECT ROW_NUMBER() OVER (ORDER BY LogDate DESC, ID DESC) AS rnum, ID, LogDate, Contact_UID, ContactStatus FROM Contact_Log) A LEFT JOIN (SELECT ROW_NUMBER() OVER (ORDER BY LogDate DESC, ID DESC) AS rnum, ID, LogDate, Contact_UID, ContactStatus FROM Contact_Log) B ON A.rnum = B.rnum-1 WHERE (B.rnum IS NULL OR (A.Contact_UID = '62918AC1-1C6C-4DEB-B7F8-5D5EF913F667' AND B.Contact_UID = '62918AC1-1C6C-4DEB-B7F8-5D5EF913F667' AND A.ContactStatus != B.ContactStatus)) ORDER BY A.rnum 。这花了5分钟多的时间,所以我停止了查询并放弃了。

问题

我如何在“我要实现的目标”中做我想做的,但要有合理的表现? (在此阶段,我很乐意将其控制在1秒以内)。

记住,我只想要一个datetimeoffset作为结果,以便可以在函数中使用。

到目前为止,我还没有创建特定的索引。如果无法改善查询条件,我很乐意为您提供建议。或对架构进行适当的更改。

底线

我正在寻找一个查询,该查询将产生1个结果,带有1个datetimeoffset字段。它只需不到1秒的时间即可运行。

sql sql-server performance
1个回答
0
投票

您想在最高日期之后选择不等于当前ContactStatus的最小日期。就像这样:

TOP(1)
© www.soinside.com 2019 - 2024. All rights reserved.