使用函数创建持久计算列

Jer*_*sey 5 sql-server-2008 sql-server computed-column

我正在与程序员一起研究数据库解决方案。他们想要添加一个计算列来模拟旧查询、过程和系统的旧键,并为其编制索引。新密钥将是 GUIDS。

为此,他们希望为计算列创建一个函数,该函数创建一个值并将其持久化。它不会让他们保留该列。我对这个想法没有任何热情的模糊,我也无法在网上找到有关该技术的任何信息(它是一种技术吗?)。

我认为他们需要添加触发器。有没有人有任何想法?

该函数将按如下方式运行:

(SELECT [INT Identity field] FROM TABLE WHERE [GUID COLUMN] = @GUIDKEY
Run Code Online (Sandbox Code Playgroud)

它根据 GUID 返回一个 INT 标识字段。

这将在插入相关表时运行。因此,如果表一持有主键,则相关表二将更新(使用传入的 GUID)以从表一中获取键并将其插入到表二中。

Aar*_*and 9

仍然不明白为什么这需要是表中的,更不用说持久化的了。

为什么不创建一个表值函数,当(且仅当)查询实际需要它时交叉应用它?由于旧密钥永远不会改变,因此无论如何都不需要计算或持久化。

如果您真的希望旧密钥存在于多个地方(听起来不应该做出这种决定的人已经做出了这种决定),只需在触发器中进行查找并在写入时填充它. 那么它只是表中的一个静态列。

我仍然强烈推荐一个表值函数来促进这一点,这样你就可以以处理多行操作的方式编写触发器......而不必编写循环,或调用标量值函数对每一行再次重复。

只是为了展示这些东西到底有多相似(并询问“不喜欢它”的首席开发人员):

-- bad, slow, painful row-by-row
CREATE FUNCTION dbo.GetIDByGUID
(
  @GuidKey uniqueidentifier
)
RETURNS int
AS
BEGIN
  RETURN (SELECT $IDENTITY FROM dbo.tablename WHERE guid_column = @GuidKey);
END
GO

-- in the trigger:
UPDATE r SET oldkey = dbo.GetIDByGUID(i.guid_column)
  FROM dbo.related AS r
  INNER JOIN inserted AS i
  ON r.guid_column = i.guid_column;
Run Code Online (Sandbox Code Playgroud)

现在,如果您有一个表值函数,代码非常相似,但您会发现多行操作的性能要好得多,而单行操作的性能几乎相同。

-- ah, much better
ALTER FUNCTION dbo.GetIDByGUID_TVF
(
  @GuidKey uniqueidentifier
)
RETURNS TABLE
AS
  RETURN (SELECT id = $IDENTITY FROM dbo.tablename WHERE guid_column = @GuidKey);
GO

-- in the trigger:
UPDATE r SET oldkey = f.id
  FROM dbo.related AS r
  INNER JOIN inserted AS i
  ON r.guid_column = i.guid_column
  CROSS APPLY dbo.GetIDByGUID_TVF(i.guid_column) AS f;
Run Code Online (Sandbox Code Playgroud)


Eri*_*ing 8

我不确定为什么你认为你需要一个函数或一个计算列来做到这一点。您可以使用默认值向表中添加一个新列,然后根据需要对其进行索引。

CREATE TABLE dbo.whatever ( Id INT );

ALTER TABLE dbo.whatever
ADD YourMom UNIQUEIDENTIFIER
        DEFAULT NEWSEQUENTIALID();

CREATE INDEX ix_whatever ON dbo.whatever (YourMom);
Run Code Online (Sandbox Code Playgroud)

既然你更新了你的问题,让我们来谈谈这是一个多么糟糕的想法。我将稍微简化一下这个例子。

CREATE TABLE dbo.whatever ( Id INT PRIMARY KEY);

CREATE TABLE dbo.ennui ( Id INT PRIMARY KEY, meh INT );
GO 

CREATE FUNCTION dbo.BadIdea ( @notguido INT )
RETURNS INT
WITH SCHEMABINDING, RETURNS NULL ON NULL INPUT
AS
    BEGIN
        DECLARE @out INT;
        SELECT @out = ( SELECT e.Id FROM dbo.ennui AS e WHERE e.meh = @notguido );
        RETURN @out;
    END;
GO 

ALTER TABLE dbo.whatever ADD ishygddt AS dbo.BadIdea(Id)

/*Will fail*/
ALTER TABLE dbo.whatever ALTER COLUMN ishygddt ADD PERSISTED;

/*Will fail*/
CREATE INDEX ix_whatever ON dbo.whatever (ishygddt);
Run Code Online (Sandbox Code Playgroud)

如果它们执行数据访问,则尝试根据标量函数(即使是具有SCHEMABINDING的确定性函数)持久化计算列将失败。

消息 4934,级别 16,状态 3,第 23 行无法保留表 'whatever' 中的计算列 'ishygddt',因为该列执行用户或系统数据访问。

你也不能索引它:

消息 2709,级别 16,状态 1,第 25 行表 'dbo.whatever' 中的列 'ishygddt' 不能用于索引或统计信息或作为分区键,因为它执行用户或系统数据访问。

您还会遇到很多问题,因为该函数将逐行运行以检索数据,并将强制针对表的所有查询串行运行

如果您正在修改函数引用的表中的数据,并从调用计算列中的函数的表中选择数据,您最终可能会遇到一些非常令人困惑的阻塞场景,即函数被阻止向查询返回数据在一张看似无关的桌子上。

这是一个坏主意。Aaron 在他的评论中给出了我认为最好的建议:

为什么不创建一个表值函数,当(且仅当)查询实际需要它时交叉应用它?由于旧密钥永远不会改变,因此无论如何都不需要计算或持久化。如果您真的希望旧密钥存在于多个地方,只需在触发器中进行查找即可。


Dav*_*oft 5

您可以阅读BOL 中的持久计算列以及计算列上的相关索引

您可以在持久计算列中使用的表达式有一些限制。表达式“在指定 PERSISTED 时必须是确定性的”。