【问题标题】:I'm using SQL UDF's to encapsulate simple reporting/business logic. Should I avoid this?我正在使用 SQL UDF 来封装简单的报告/业务逻辑。我应该避免这种情况吗?
【发布时间】:2011-01-10 15:07:29
【问题描述】:

我正在 SQL Server 2008 中为某些报告建立一个新数据库,并且有许多与这些数据相关的常见业务规则,它们会进入不同类型的报告。目前,这些规则主要结合在更大的程序程序中,使用一种遗留语言,我正试图将其转移到 SQL。我希望灵活地根据这些数据实现报告,例如 SAS 中的一些报告、C# 中的一些报告等。

我目前的方法是分解这些通用规则(通常是非常简单的逻辑)并将它们封装在单独的 SQL UDF 中。性能不是问题,我只是想使用这些规则在一种报告“快照”中填充静态字段,然后可以使用它以您想要的任何方式进行报告。

就了解每个规则的作用(以及维护规则本身)而言,我喜欢这种模块化方法,但我也开始有点担心维护也可能成为一场噩梦。有些规则取决于其他规则,但我真的无法摆脱这一点 - 这些东西相互依赖......这就是我想要的......我想? ;)

对于数据库中的这种模块化方法,是否有更好的方法?我是否走在正确的轨道上,还是我在考虑这一点时过于注重应用程序开发?

【问题讨论】:

  • 谢谢大家!看起来这种方法应该没问题 :) 同样,性能不是这些问题,因为它们仅在每个 ETL/快照实例中使用一次。因此,填充这些函数输出到的字段可能需要一两三分钟,但在那之后,总是有人只查询表,从不使用查询中的函数。好吧,至少他们不应该使用它们!

标签: sql sql-server user-defined-functions modularity


【解决方案1】:

在某些时候,大量使用 UDF 将开始导致性能问题,因为它们是针对结果集中的每一行执行的,并且优化器的逻辑模糊不清,从而难以使用索引(即,我不太了解性能如何不能成为问题,但您最了解自己的要求)。对于某些功能,它们很棒;但要谨慎使用。

【讨论】:

  • 我完全同意这一点,但我只是在拍摄特定“快照”来填充静态字段时使用这些,这是有人出于任何报告目的而查询的内容。否则,我肯定会将此逻辑移入报告层(并争取标准的报告实现),或者努力将事物移入表值函数。不过,感谢您的反馈!
  • 并非所有 UDF 每行执行一次,内联的 UDF 会被优化器展平。标量 UDF 确实非常慢
  • 表UDF不会为每一行执行?
【解决方案2】:

将逻辑保留在数据库端几乎总是正确的做法。

正如您在问题中提到的,大多数业务规则都涉及非常简单的逻辑,但它通常处理大量数据。

数据库引擎是实现该逻辑的正确选择,因为首先,它将数据I/O 保持在最低限度,其次,数据库执行大多数数据转换的效率更高。

前段时间我就这个话题写了一篇非常主观的博文:

附注:UDF 与存储过程不同。

UDF 是一个由可在查询内部调用的函数,因此它只能执行非常有限的可能操作子集。

你可以做的更多是一个存储过程。

更新:

在您给出的示例中,例如更改计算“派生字段”的逻辑,计算字段的UDF 是可以的。

但是(以防万一)当性能成为问题时(相信我,这会比人们想象的要快得多),使用基于集合的操作转换数据可能比使用UDFs 更有效。

在这种情况下,您可能希望创建一个视图、一个存储过程或一个表值函数返回一个结果集,该结果集将包含一个更有效的查询,而不是限制自己更新UDFs(它们是基于记录的)。

一个例子:您的查询有类似“用户分数”之类的内容,您认为可能会发生变化并将其包装成UDF

SELECT  user_id, fn_getUserScore(user_id)
FROM    users

最初,这只是表中的一个普通字段:

CREATE FUNCTION fn_getUserScore(@user_id INT) RETURNS INT
AS
BEGIN
        DECLARE @ret INT
        SELECT  user_score
        INTO    @ret
        FROM    users
        WHERE   user_id = @user_id
        RETURN @ret
END

,然后您决定使用其他表中的数据进行计算:

CREATE FUNCTION fn_getUserScore(@user_id INT) RETURNS INT
AS
BEGIN
        DECLARE @ret INT
        SELECT  SUM(vote)
        INTO    @ret
        FROM    user_votes
        WHERE   user_id = @user_id
        RETURN @ret
END

这将使引擎在任何一种情况下都使用效率最低的NESTED LOOPS算法。

但是,如果您创建了一个视图并像这样重写了基础查询:

SELECT  user_id, user_score
FROM    users

SELECT  user_id, SUM(vote) AS user_score
FROM    users u
LEFT JOIN
        user_votes uv
ON uv.user_id = u.user_id

,这将为引擎提供更广阔的优化空间,同时仍保持结果集结构并将逻辑与表示分离。

【讨论】:

  • +1 用于在数据库端保持逻辑(尽管许多人强烈反对!)
  • 不知道我是否在寻求效率,而不仅仅是将这些规则与实施报告的选择分开——我只是希望人们能够查看数据并像“啊哈哈,有派生字段 X,我想在其中过滤“N”或类似的东西 :) 如果派生字段 X 的逻辑根据一些反馈发生变化,只需更新一个或两个 UDF 并更新该字段.无论如何,这是我的愿景;)
  • @chucknelson: 我希望我的客户能够在数据中看到“派生字段”:)
  • 为什么不用表函数而不是视图,以防万一您需要传递参数?
  • @Jeff O:表值函数也很好。
【解决方案3】:

SQL 是基于集合的,因此在应用模块化方法时性能本来就很差。
函数、存储过程和/或视图——它们都抽象了底层逻辑。当您使用使用相同表的两个(或更多)函数/等时,性能问题就会发挥作用。这意味着当一个本可以使用时,两个查询会生成同一张表。

多个函数的使用对我来说表明数据模型非常“灵活”。对我来说,这意味着有问题的数据输入和整体列/表定义。需要函数/等,因为数据库将允许存储任何内容,这意味着坏数据的可能性非常高。我宁愿努力始终拥有良好/有效的数据,而不是事后处理现有的不良数据。

数据库是包含此逻辑的地方。它比应用程序代码更快,而且最重要的是 - 集中化以最大限度地减少维护。

【讨论】:

  • 我同意 - 我得到的基础数据非常广泛。我基本上将我的“快照”过程视为 ETL 步骤,并使用这些 UDF 填充字段,这对于想要使用数据进行报告(或其他任何东西,真的)的人来说非常有用。
  • @chucknelson:我明白 - 我目前的工作让我履行类似的职责。
【解决方案4】:

我会说你走在正确的轨道上 - 随着 SQL 过程变得越来越复杂,sql 过程可能会迅速失控,将共享的、重复的逻辑片段封装到 UDF 中是解决这个问题的完全合适的解决方案。

我经常将仅在该过程中使用的 sql 过程中的逻辑封装到命名良好的 UDF 中以提高可读性。

看看 UDF 上的 this MSDN article - 也许它会给你一些关于它们用途的更多想法?

如果您打算大量使用 UDF,则需要注意各种性能注意事项,例如标量与表 UDF 的性能以及 CLR UDF 的可能优势。

【讨论】:

    【解决方案5】:

    如果您对构建用于报告的数据仓库感兴趣,您会尝试将尽可能多的数据仓库放入 ETL 的转换部分,以便您的报告 SQL 由工具和用户等能够生成的简单语句组成。

    SSIS 是一个非常强大的 ETL 工具,附带 SQL Server 来处理这类事情。

    【讨论】:

    • 目前它的范围很小,但我同意,我愿意为此使用一些适当的报告工具/流程。现在,我正在尝试为下一代构建可维护且易于阅读/理解的东西;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-17
    • 2022-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-18
    相关资源
    最近更新 更多