如何在 TVF 中的视图上使用 FOR SYSTEM_TIME 子句?

huangapple go评论56阅读模式
英文:

How to use FOR SYSTEM_TIME clause on a view in a TVF?

问题

我正在尝试在一个包含系统版本表的视图上使用FOR SYSTEM_TIME子句,该视图位于一个表值函数中。

根据微软的文档,当查询视图时,使用FOR SYSTEM_TIME子句应该是可能的:https://learn.microsoft.com/en-us/sql/relational-databases/tables/querying-data-in-a-system-versioned-temporal-table?view=sql-server-ver16

然而,我遇到了以下错误:

临时FOR SYSTEM_TIME子句只能与系统版本表一起使用。'dbo.AccountView'不是系统版本表。

函数、视图和表的定义:

-- 账户表
CREATE TABLE [dbo].[Account](
[Account No] [int] IDENTITY(1,1) NOT NULL,
[Global ID] AS ('A'+CONVERT(nvarchar,[Account No])) PERSISTED,
[User] varchar NULL,
[Version No] [tinyint] NULL,
[Version Comment] varchar NULL,
[Valid From] datetime2 GENERATED ALWAYS AS ROW START NOT NULL,
[Valid To] datetime2 GENERATED ALWAYS AS ROW END NOT NULL,
CONSTRAINT [PK_Account] PRIMARY KEY CLUSTERED
(
[Account No] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY],
PERIOD FOR SYSTEM_TIME ([Valid From], [Valid To])
) ON [PRIMARY]
WITH
(
SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[hst_Account])
)
GO

-- 其他表和视图...

-- 表值函数
CREATE FUNCTION [dbo].[GetAsOfDate_AccountView] (@tblID int, @asOfDate datetime2(2))
RETURNS TABLE
AS RETURN
SELECT av.*
FROM [dbo].[AccountView] FOR SYSTEM_TIME AS OF @asOfDate AS av
WHERE av.[Account No] = @tblID
GO

*已编辑以添加视图和表的脚本。

英文:

I am trying to use the FOR SYSTEM_TIME clause on a view, which contains system-versioned tables, in a table-valued function.

Per Microsoft's documentation, using the FOR SYSTEM_TIME clause when querying a view should be possible: https://learn.microsoft.com/en-us/sql/relational-databases/tables/querying-data-in-a-system-versioned-temporal-table?view=sql-server-ver16

However, I am getting the following error:

> Temporal FOR SYSTEM_TIME clause can only be used with system-versioned tables. 'dbo.AccountView' is not a system-versioned table.

Function, view, and table definitions:

--Account Table
CREATE TABLE [dbo].[Account](
	[Account No] [int] IDENTITY(1,1) NOT NULL,
	[Global ID]  AS ('A'+CONVERT([nvarchar](10),[Account No])) PERSISTED,
	[User] [varchar](50) NULL,
	[Version No] [tinyint] NULL,
	[Version Comment] [varchar](280) NULL,
	[Valid From] [datetime2](2) GENERATED ALWAYS AS ROW START NOT NULL,
	[Valid To] [datetime2](2) GENERATED ALWAYS AS ROW END NOT NULL,
 CONSTRAINT [PK_Account] PRIMARY KEY CLUSTERED 
(
	[Account No] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY],
	PERIOD FOR SYSTEM_TIME ([Valid From], [Valid To])
) ON [PRIMARY]
WITH
(
SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[hst_Account])
)
GO

ALTER TABLE [dbo].[Account] ADD  CONSTRAINT [DF_Account_ValidFrom]  DEFAULT (sysutcdatetime()) FOR [Valid From]
GO

ALTER TABLE [dbo].[Account] ADD  CONSTRAINT [DF_Account_ValidTo]  DEFAULT (CONVERT([datetime2](2),'9999-12-31 23:59:59.99')) FOR [Valid To]
GO

--Account_General Table
CREATE TABLE [dbo].[Account_General](
	[Account No] [int] NOT NULL,
	[Name] [varchar](100) NULL,
	[Main Phone] [varchar](12) NULL,
	[General Email] [varchar](100) NULL,
	[Website] [varchar](100) NULL,
	[Company] [varchar](50) NULL,
	[Valid From] [datetime2](2) GENERATED ALWAYS AS ROW START NOT NULL,
	[Valid To] [datetime2](2) GENERATED ALWAYS AS ROW END NOT NULL,
 CONSTRAINT [PK_Account_General] PRIMARY KEY CLUSTERED 
(
	[Account No] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY],
	PERIOD FOR SYSTEM_TIME ([Valid From], [Valid To])
) ON [PRIMARY]
WITH
(
SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[hst_Account_General])
)
GO

ALTER TABLE [dbo].[Account_General] ADD  CONSTRAINT [DF_Account_General_ValidFrom]  DEFAULT (sysutcdatetime()) FOR [Valid From]
GO

ALTER TABLE [dbo].[Account_General] ADD  CONSTRAINT [DF_Account_General_ValidTo]  DEFAULT (CONVERT([datetime2](2),'9999-12-31 23:59:59.99')) FOR [Valid To]
GO

ALTER TABLE [dbo].[Account_General]  WITH CHECK ADD  CONSTRAINT [FK_Account_General_AccountNo] FOREIGN KEY([Account No])
REFERENCES [dbo].[Account] ([Account No])
ON UPDATE CASCADE
ON DELETE CASCADE
GO

ALTER TABLE [dbo].[Account_General] CHECK CONSTRAINT [FK_Account_General_AccountNo]
GO

--Account_Temp Table
CREATE TABLE [dbo].[Account_Temp](
	[Account No] [int] NOT NULL,
	[Status] [varchar](30) NULL,
	[Locked By] [varchar](50) NULL,
	[Time Locked] [datetime2](2) NULL,
 CONSTRAINT [PK_Account_Temp] PRIMARY KEY CLUSTERED 
(
	[Account No] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Account_Temp]  WITH CHECK ADD  CONSTRAINT [FK_Account_Temp_AccountNo] FOREIGN KEY([Account No])
REFERENCES [dbo].[Account] ([Account No])
ON UPDATE CASCADE
ON DELETE CASCADE
GO

ALTER TABLE [dbo].[Account_Temp] CHECK CONSTRAINT [FK_Account_Temp_AccountNo]
GO

--AccountView
CREATE VIEW [dbo].[AccountView] AS
SELECT a.[Account No]
	  ,a.[Global ID]
	  ,atemp.[Status]
	  ,agen.[Name]
	  ,agen.[Main Phone]
	  ,agen.[General Email]
	  ,agen.[Website]
	  ,agen.[Company]
	  ,a.[Version No]
      ,atemp.[Locked by]
	  ,atemp.[Time Locked]
	  ,a.[User] 'Modified By'
	  ,CONVERT(datetime2(2), a.[Valid From] AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time') 'Time Modified'
	  ,ISNULL(ahist.[User], a.[User]) 'Created By'
	  ,CONVERT(datetime2(2), ISNULL(ahist.[Valid From], a.[Valid From]) AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time') 'Time Created'
FROM [dbo].[Account] a
LEFT JOIN [dbo].[Account_Temp] atemp
ON a.[Account No] = atemp.[Account No]
LEFT JOIN [dbo].[Account_General] agen
ON a.[Account No] = agen.[Account No]
LEFT JOIN (
	SELECT ahist.*
		  ,rn = ROW_NUMBER() OVER (PARTITION BY ahist.[Account No] ORDER BY ahist.[Valid From])
	FROM [dbo].hst_Account ahist) ahist
ON ahist.[Account No] = a.[Account No] AND ahist.rn = 1
GO

--Table-Valued Function
CREATE FUNCTION [dbo].[GetAsOfDate_AccountView] (@tblID int, @asOfDate datetime2(2))
RETURNS TABLE
AS RETURN
SELECT av.*
FROM [dbo].[AccountView] FOR SYSTEM_TIME AS OF @asOfDate AS av
WHERE av.[Account No] = @tblID

GO

*Edited to add script for view and tables.

答案1

得分: 1

正如评论中提到的,经过其他人的多次尝试和错误来创建一个最小可重现示例,您不能在FOR SYSTEM_TIME AS OF子句中使用一个不是时间对象本身的对象中的变量/参数。因此,即使您引用的“VIEW”包含时间对象,您仍会收到一个有点误导性的错误:
> 仅可使用时间 FOR SYSTEM_TIME 子句与系统版本表一起使用。“Sandbox.dbo.PartiallyTemporalView”不是系统版本表。

为什么会这样工作,诚实地说,只有微软的某人可能能够回答。您可能最好通过他们开一个工单(可以通过您的支持计划或通过Azure 365反馈SQL社区)提出它作为一个“bug”;他们可能会提供一些见解,甚至可能会在SQL Server的将来版本中“修复”这个问题。

至于解决方案,那就是在您的“FUNCTION”中重新定义“VIEW”的定义,并针对相关的表使用“FOR SYSTEM_TIME AS OF”。在这种情况下,似乎只是“dbo.Account_General”:

CREATE FUNCTION [dbo].[GetAsOfDate_AccountView] (@tblID int,
                                                 @asOfDate datetime2(2))
RETURNS table
AS
    RETURN SELECT a.[Account No],
                  a.[Global ID],
                  atemp.[Status],
                  agen.[Name],
                  agen.[Main Phone],
                  agen.[General Email],
                  agen.[Website],
                  agen.[Company],
                  a.[Version No],
                  atemp.[Locked by],
                  atemp.[Time Locked],
                  a.[User] AS [Modified By],
                  CONVERT(datetime2(2), a.[Valid From] AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time') AS [Time Modified],
                  ISNULL(ahist.[User], a.[User]) AS [Created By],
                  CONVERT(datetime2(2), ISNULL(ahist.[Valid From], a.[Valid From])AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time') AS [Time Created]
           FROM [dbo].[Account] a
               LEFT JOIN [dbo].[Account_Temp] atemp ON a.[Account No] = atemp.[Account No]
               LEFT JOIN [dbo].[Account_General] FOR SYSTEM_TIME AS OF @asOfDate agen ON a.[Account No] = agen.[Account No]
               LEFT JOIN (SELECT ahist.*,
                                 ROW_NUMBER() OVER (PARTITION BY ahist.[Account No]
                                                    ORDER BY ahist.[Valid From]) AS rn
                          FROM [dbo].hst_Account ahist) ahist ON ahist.[Account No] = a.[Account No]
                                                             AND ahist.rn = 1;

GO
英文:

As have been mentioned in the comments, and after much trial and error by others to create a minimal reproducible example, you can't use a variable/parameter in a (i)TVF in the FOR SYSTEM_TIME AS OF clause against a object that isn't itself isn't a temporal object. As such, even if the VIEW you are referencing does contain temporal objects you'll get a (some what misleading) error:
> Temporal FOR SYSTEM_TIME clause can only be used with system-versioned tables. 'Sandbox.dbo.PartiallyTemporalView' is not a system-versioned table.

Why it works this way, honestly only someone from Microsoft could likely answer. You would probably be best opening a ticket (either via your support plan or via the Azure 365 Feedback SQL Community) with them to raise it as a "bug"; they may give some insight or they might even "fix" the problem (in a future version of SQL Server).

As for a solution, that would be to repeat the definition of the VIEW in your FUNCTION instead, and use FOR SYSTEM_TIME AS OF against the relevant table(s). That appears to just be dbo.Account_General in this case:

CREATE FUNCTION [dbo].[GetAsOfDate_AccountView] (@tblID int,
                                                 @asOfDate datetime2(2))
RETURNS table
AS
    RETURN SELECT a.[Account No],
                  a.[Global ID],
                  atemp.[Status],
                  agen.[Name],
                  agen.[Main Phone],
                  agen.[General Email],
                  agen.[Website],
                  agen.[Company],
                  a.[Version No],
                  atemp.[Locked by],
                  atemp.[Time Locked],
                  a.[User] AS [Modified By],
                  CONVERT(datetime2(2), a.[Valid From] AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time') AS [Time Modified],
                  ISNULL(ahist.[User], a.[User]) AS [Created By],
                  CONVERT(datetime2(2), ISNULL(ahist.[Valid From], a.[Valid From])AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time') AS [Time Created]
           FROM [dbo].[Account] a
               LEFT JOIN [dbo].[Account_Temp] atemp ON a.[Account No] = atemp.[Account No]
               LEFT JOIN [dbo].[Account_General] FOR SYSTEM_TIME AS OF @asOfDate agen ON a.[Account No] = agen.[Account No]
               LEFT JOIN (SELECT ahist.*,
                                 ROW_NUMBER() OVER (PARTITION BY ahist.[Account No]
                                                    ORDER BY ahist.[Valid From]) AS rn
                          FROM [dbo].hst_Account ahist) ahist ON ahist.[Account No] = a.[Account No]
                                                             AND ahist.rn = 1;

GO

huangapple
  • 本文由 发表于 2023年6月12日 22:19:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/76457571.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定