SQL:避免硬编码或魔术数字

时间:2010-01-04 18:50:47

标签: sql tsql stored-procedures

问题:在SQL脚本或存储过程中避免使用幻数或硬编码值的其他策略是什么?

考虑一个存储过程,其作用是根据其StatusID或其他一些FK查找表或值范围来检查/更新记录的值。

考虑一个Status表,其中ID最重要,因为它是另一个表的FK:

alt text

要避免使用的SQL脚本类似

DECLARE  @ACKNOWLEDGED tinyint

SELECT  @ACKNOWLEDGED = 3   --hardcoded BAD

UPDATE  SomeTable
SET     CurrentStatusID = @ACKNOWLEDGED
WHERE   ID = @SomeID

这里的问题是这不可移植,并且明确依赖于硬编码值。将此部署到其他具有标识插入的环境时,存在细微的缺陷。

还要根据状态的文字说明/名称来避免SELECT

UPDATE  SomeTable
SET     CurrentStatusID = (SELECT ID FROM [Status] WHERE [Name] = 'Acknowledged')
WHERE   ID = @SomeID

问题:在SQL脚本或存储过程中避免使用幻数或硬编码值的其他策略是什么?

关于如何实现这一目标的其他一些想法:

  • 添加一个新的bit列(名称为'IsAcknowledged')和一组规则,其中只有一行的值为1。这有助于找到唯一的行:SELECT ID FROM [Status] WHERE [IsAcknowledged] = 1)

11 个答案:

答案 0 :(得分:12)

对于像状态表这样的情况,我创建了所谓的“静态”数据集。这些表包含

的数据
  • 在创建时设置和定义,
  • 永远不会改变,
  • 从数据库实例到数据库实例,总是一样的 no 例外

也就是说,在创建表的同时,也使用脚本填充表,以确保值始终相同。此后,无论数据库在何处或何时,您都知道值是什么,并且可以相应地进行相应的硬编码。 (在这些情况下,我永远不会使用代理键或标识列属性。)

您不必使用数字,您可以使用字符串 - 或二进制文件或日期,或任何最简单,最简单和最合适的。 (当我可以的时候,我使用char字符串 - 而不是varchars - 例如“RCVD”,“DLVR”,“ACKN”等等,比0,2和3更容易编码硬编码值。)

此系统适用于不可扩展的值集。如果可以修改这些值(这样0不再意味着“已确认”),那么您就会遇到安全访问问题。如果您的系统中有用户可以添加新代码,那么您有一个不同且棘手的设计问题需要解决

答案 1 :(得分:10)

我最近发现魔术数字可以是implemented by views

CREATE VIEW V_Execution_State AS
SELECT 10 AS Pending, 20 AS Running, 30 AS Done

DECLARE @state INT
SELECT @state = Pending FROM V_Execution_State

答案 2 :(得分:7)

在某种程度上,会有一些“硬编码”的价值观。消除它们的想法来自两个方面:

  1. 让代码更具可读性(即,说'Acknowledged'而不是3 可能 会让读者的意图更加明显。
  2. 使代码更具动态性,其中一个函数可以使用一个参数而不是一些不具有的函数(这显然是一种简化,但其意义应该是相当不言而喻的)
  3. 为各种状态制作bit列可能是一个好主意或坏主意;它真的只取决于数据。如果数据经历了各种“阶段”(接收,确认,审核,拒绝,接受,响应等),那么这种方法很快就会超出可行性(更不用说必须确保的烦躁过程)在任何给定时间,只有一个列设置为1)。另一方面,如果状态真的像你描述的那样简单,那么这样做可以使代码更具可读性,索引性能更好。

    硬编码值中最大的禁忌值是引用其他实体的硬编码值(换句话说,硬编码相应对象的主键)。字符串'Acknowledged'仍然是一个硬编码的值,它的含义更加透明,而不是对其他内容的引用。对我而言,归结为:如果你能(合理地)查找它,那么。如果您不能(或者从性能或可维护性的角度来看某些事情使其成为不合理的任务),那就硬编码吧。使用此功能,您可以使用Acknowledged查找值3;你不能从其他任何东西中查找Acknowledged

答案 3 :(得分:3)

我就是这样做的。 (因为它比你的例子快得多)

UPDATE SomeTable
SET CurrentStatusID = [Status].[ID]
FROM SomeTable
 RIGHT JOIN [Status] ON [Name] = 'Acknowledged'
WHERE SomeTable.[ID] = @SomeID

(未经测试可能有拼写错误)

答案 4 :(得分:3)

不要依赖IDENTITY来获取所有ID。例如,如果查找表的行数少于50行,则将这些查找定义为具有特定ID或使用字符串值代码是完全合理的。无论哪种情况,“硬编码”都不再是问题。

答案 5 :(得分:3)

一个想法:

CREATE FUNC dbo.CONST_ACKNOWLEDGED()
RETURNS tinyint
AS
BEGIN
   RETURN 3
END

然而,只有你没有自动编号,恕我直言才有意义

答案 6 :(得分:1)

如果您的情况如上所述,IsAcknowledged位可以工作,我会走那条路。我从来没有遇到任何问题。如果你有更复杂的场景,你最终会得到十几个字段,只要你完全控制它,我就没有看到使用“幻数”的任何问题。如果您担心在移植数据库时身份列未正确映射,您可以创建另一个(非身份)唯一列,其中包含您的ID值,整数或guid或其他任何有用的列。

答案 7 :(得分:1)

如果构成域模型一部分的“状态”实体具有预定义的值,其中一些需要由存储过程以特定方式处理,那么将引用硬编码到那些特定值是完全可以的。你的代码。这里的问题是,您可能会混淆可能是您的域模型中具有含义的值的抽象键(ID标识列)。虽然可以保留您的ID标识列,但在代码中引用它时,您应该使用域实体的有意义的属性,这可以是名称,也可以是数字别名。但是,此数字别名应在您的域模型中定义,例如3表示'已确认',它不应与抽象ID字段混淆,正如您所说,它可能是某些数据库实例中的标识列。

答案 8 :(得分:0)

首先,应该避免在存储层中使用业务逻辑

因为在使用诸如Sql Server之类的数据库时,这似乎是不可避免的,其中很多 BL 可以存在于 DB 中,我想你可能宁愿恢复使用字符串ID 而不是自动ID

这将比auto ID更加管理,并且可以以更好的方式处理,即使在使用实际的应用程序层时也是如此。

例如,使用.Net方法,许多唯一字符串ID可以存储在配置文件的任何位置,以及使用所选db,XML文件的其他查找。

答案 9 :(得分:0)

想象

table dbo.Status
(
     Id int PK
    ,Description varchar
)
values
1, Received
2, Acknowledged
3, Under Review
etc

所以,只是

declare @StatusReceived int = 1
declare @StatusAcknowledged int = 2
declare @StatusUnderReview = 3
etc

正如其他人所说,这假设未设置IDENTITY。

我也常常在查找表上加入,但这会使SELECT更短,更容易阅读。

这种方法适用于自动化,所以我在一个单独的查询中生成一个完整的表,然后复制我需要的元素(不是全部)。

答案 10 :(得分:0)

我们采用了一种简单的方法:添加一个名为“代码”的列,其中包含一个字符串,一旦创建了记录,该字符串就永远不会更改。

如果将ID值硬编码,则会遇到问题-如果将某些功能(例如,对ID硬编码并假设您将使用该ID值创建对应记录的存储过程)部署到另一个数据库实例,该怎么办?已经有另一个记录使用的ID值了吗?或将您的代码部署为用于创建表和数据的SQL脚本-您不能保证每次部署都会以相同的方式生成ID值。

如果您对“名称”之类的字段进行硬编码-很有可能在某处显示“名称”。如果该字段是用户可维护的,该怎么办?如果有人决定更改Name值,则会破坏您的过程和功能。即使它不是用户可维护的,将来也会做一些更改,说“我们必须更改名称”,这将由SQL完成,破坏您的从属代码并要求搜索所有依赖项,对其进行修复并再次进行测试

但是,如果您创建一个永不更改的“代码”列,那么谁在乎“名称”值是否更改?向用户显示名称-他们可以看到他们想要的。该代码仅对开发人员可见,并且在绝对必要的情况下,他们可以稍后在代码中注明“代码值X表示Y-名称更改为请求编号Z的一部分”。

非常简单的方法。