【问题标题】:SQL Updating Optional Parameters PHPSQL 更新可选参数 PHP
【发布时间】:2016-12-21 12:37:31
【问题描述】:

我们想要改变将值从 PHP 传递到存储过程 (T-SQL) 的方式。我只有少量 PHP 经验,但我将尝试通过与我们的 Web 开发人员的讨论来解释该过程。

当前进程

示例测试表

为了更新记录,例如本例中的 Field3,我们会将所有现有值传递回存储过程。

EXEC dbo.UpdateTest @ID = 1, @Field1 = 'ABC', @Field2 = 'DEF', @Field3 = 'GHI', @Field4 = 'JKL'

假设要更新 Field3,您必须单击一个按钮。这将导航到一个新页面,该页面将运行存储过程来更新数据。由于新页面不知道值,它必须运行SELECT 过程以在运行UPDATE 之前检索值。

然后,该脚本会将用户重定向回重新加载更新数据的页面,并且更改会反映在屏幕上。

新流程

我们想做的只是传递我们想要更改的字段。

EXEC dbo.UpdateTest @ID = 1, @Field2 = 'DEF', @Field3 = 'GHI'

我们的解决方案很简单。首先,我们将所有可更新字段设置为可选(因此可以传递NULL)。然后我们检查参数是否为NULL(未通过),如果是则忽略它,如果不是则更新它。

UPDATE 
    dbo.Test
SET
    Field1 = NULLIF(ISNULL(@Field1,Field1),'-999')
    ,Field2 = NULLIF(ISNULL(@Field2,Field2),'-999')
    ,Field3 = NULLIF(ISNULL(@Field3,Field3),'-999')
    ,Field4 = NULLIF(ISNULL(@Field4,Field4),'-999')
WHERE
    ID = @ID

但是,如果NULL传递,我们仍然希望过程将数据库记录更新为NULL。解决方法是分配一个等于NULL 的任意值(在本例中为-999),这样如果传递了任意值(-999),该过程将更新NULL

这个解决方案相当混乱,在我看来,这是一种低效的解决问题的方法。有没有更好的解决方案?我们做错了什么?

非常感谢任何回复

【问题讨论】:

  • 不确定我是否遵循了这个问题。但是由于您在执行存储过程时传递了命名参数。 EXEC dbo.UpdateTest ID = 1, Field2 = 'DEF', Field3 = 'GHI' 您正在向所有其他字段发送空值。您无需执行任何操作,该值将为空。我不会传递 -999 的值,因为我认为不需要它。你在数据库中写的是什么,null 还是 -999?
  • 您的“我们想通过什么”这行没有任何问题。如果你在 MsSql 服务器上,不同的 Sql 平台工作方式不同。
  • 我在答案部分发帖是因为使用了@符号,如果我把它们放在 cmets 部分,它会试图阻止我。
  • 您能否将参数包装在XML 中并将其传递给程序。 SQL Server 可以解析 XML,因此它允许传递和解析任何参数组合。也许有趣? stackoverflow.com/questions/15128999/…。另外:itworld.com/article/2960645/development/…。并且:simple-talk.com/blogs/…

标签: php sql sql-server tsql stored-procedures


【解决方案1】:
EXEC dbo.UpdateTest @ID = 1, @Field1 = 'ABC', @Field2 = 'DEF', @Field3 = 'GHI', @Field4 = 'JKL'

EXEC dbo.UpdateTest @ID = 1, @Field2 = 'DEF', @Field3 = 'GHI'

都是通过 MsSql 或 Sybase 使用相同存储过程的有效方法。当您不发送值时,它与发送空值相同。除非您在存储过程中设置了默认值。在这种情况下,使用默认值而不是 null。

【讨论】:

  • 不确定我是否完全理解这个问题。但是我认为如果那里已经有一个值并且没有任何内容发送到存储过程,那么您的 update 语句将用 null 覆盖一个值。如果我理解正确,我认为 PHP 方面是正确的,问题出在存储过程方面。
  • 虽然这是正确的,但 OP 想知道处理传递空值时情况的最佳方法,并了解何时将值更新为空值或根本不更新。
  • 我的意思是说我认为 sql 语句不会那样做。它将接受一个空值并按照它的设置方式覆盖一个非空值。我不认为这个问题与 php.ini 有任何关系。这是他们执行 sql 的方式。如果需要,他们可能需要执行动态 sql 存储过程。试图破译这个问题有点麻烦。试图提供帮助,但不确定我是否理解他们在细节中想要做什么。
【解决方案2】:

您使用幻数-999 作为NULL 值的方法存在问题,就像任何使用幻数的方法一样。为什么-999?为什么不-999999?您确定 -999 不能是该字段的正常值吗?即使现在不允许用户在此字段中输入-999,您确定在您的应用程序和数据库发展的几年后,该规则仍将保留吗?这不是关于效率与否,而是关于正确与否。

如果您在表中的字段是NOT NULL,那么您可以传递一个NULL 值来指示该字段不应被更新。在这种情况下,可以使用魔术值NULL,因为表模式保证该字段不能是NULL。将来表架构有可能发生变化,因此NULL 可以成为字段的有效值。

无论如何,您当前的架构允许NULLs,所以我们应该选择另一种方法。每个字段都有一个明确的标志,告诉程序是否应该更新该字段。

当您想要更改此字段的值时,请将@ParamUpdateFieldN 设置为 1。过程将使用在相应的@ParamFieldN 中传递的值。

当您不想更改此字段的值时,请将 @ParamUpdateFieldN 设置为 0。将@ParamFieldN设置为任意值(例如NULL),表格中对应的字段不会改变。

CREATE PROCEDURE dbo.UpdateTest
    -- Add the parameters for the stored procedure here
    @ParamID numeric(10,0),                 -- not NULL

    -- 1 means that the field should be updated
    -- 0 means that the fleld should not change
    @ParamUpdateField1 bit,                 -- not NULL
    @ParamUpdateField2 bit,                 -- not NULL
    @ParamUpdateField3 bit,                 -- not NULL
    @ParamUpdateField4 bit,                 -- not NULL

    @ParamField1 nvarchar(250),             -- can be NULL
    @ParamField2 nvarchar(250),             -- can be NULL
    @ParamField3 nvarchar(250),             -- can be NULL
    @ParamField4 nvarchar(250)              -- can be NULL
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from 
    -- interfering with SELECT statements.
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    BEGIN TRANSACTION;
    BEGIN TRY

        UPDATE dbo.Test
        SET
             Field1 = CASE WHEN @ParamUpdateField1 = 1 THEN @ParamField1 ELSE Field1 END
            ,Field2 = CASE WHEN @ParamUpdateField2 = 1 THEN @ParamField2 ELSE Field2 END
            ,Field3 = CASE WHEN @ParamUpdateField3 = 1 THEN @ParamField3 ELSE Field3 END
            ,Field4 = CASE WHEN @ParamUpdateField4 = 1 THEN @ParamField4 ELSE Field4 END
        WHERE
            ID = @ParamID
        ;

        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        -- TODO: process the error
        ROLLBACK TRANSACTION;
    END CATCH;

END

因此,过程的参数不是可选的,但您使用@ParamUpdateFieldN 标志来指示哪些参数包含有用的值以及应该忽略哪些参数。

【讨论】:

    【解决方案3】:

    没有足够的声誉来发表评论。 在我看来,只要任意值不能是任何字段的正常值,您的解决方案就足够了。

    但是,当字段不应该具有“实际”值并且故意从客户端更新时,我会考虑传递和存储除 NULL 之外的其他内容(例如“N/A”)。

    【讨论】:

      【解决方案4】:

      Valdimir 的方法非常棒,因为它可以传递一个标志变量来识别值何时通过或不通过,并且他关于任意选择值的注释是正确的,但我猜有些任意值你可能永远不会有担心。例如 -999 表示不允许使用负数时的整数,或 '|||||||'对于空字符串。当然,当您确实想使用负数时,这会破坏一些,但是您可能会使用对于诸如 BIGINT 之类的数据类型而言太大的数字作为参数默认 -9223372036854775808 用于 int ......问题真的来了值是否可以被允许的业务案例。

      但是,如果您走这样的路线,我建议您做两件事。 1)不要将值从PHP传递到SQL,而是将其设为SQL中的默认值并测试参数是否为默认值。 2)在表中添加一个CHECK CONSTRAINT,以确保值没有被使用并且不能在表中表示

      比如:

      ALTER TABLE dbo.UpdateTest
      CHECK CONSTRAINT chk_IsNotNullStandInValue (Field1 <> '|||||||||||||||||||' AND Field2 <> -999)
      
      CREATE PROCEDURE dbo.UpdateTest
          @ParamId numeric(10,0)
          ,@ParamField1 NVARCHAR(250) = '|||||||||||||||||||'
          ,@ParamField2 INT = -99999  --non negative INT
          ,@ParamField3 BIGINT = -9223372036854775808 --for an int that can be negative
      AS
      BEGIN
      
      DECLARE @ParamField3Value INT
      
      BEGIN TRY
      
          IF ISNULL(@ParamField3,0) <> -9223372036854775808
          BEGIN
             SET @ParamField3Value = CAST(@ParamField3 AS INT)
          END
      END TRY
      BEGIN CATCH
          ;THROW 51000, '@ParamField3 is not in range', 1
      END CATCH
      
          UPDATE dbo.Test
             SET Field1 = IIF(@ParamField1 = '|||||||||||||||||||',Field1,@ParamField1)
                ,Field2 = IIF(@ParamField2 = -99999,Field2,@ParamField2)
                ,Field3 = IIF(@ParamField3 = -9223372036854775808, Field3, @ParamField3Value)
          WHERE
             ID = @ParamId
      
      END
      

      这种方法的真正问题是允许负数的数字数据字段,因为您确实没有适当的方法来确定值何时应该为空,除非您可以选择一个永远不在的数字范围。 而且我绝对意识到 BIGINT for INT 示例的想法有多糟糕,因为现在您的程序将接受它不应该接受的数字范围!

      Vladimir 建议的另一种方法/轻微变化是标记何时使字段为空而不是何时更新。这将需要一点时间让您的 PHP 团队记住使用,但因为这些标志也可以是可选的,所以它们不必总是包含以下内容而变得繁琐:

      CREATE PROCEDURE dbo.UpdateTest
          @ParamId numeric(10,0)
          ,@ParamField1 NVARCHAR(250) = NULL
          ,@MakeField1Null BIT = 0
          ,@ParamField2 INT = NULL
          ,@MakeField2Null BIT = 0
          ,@ParamField3 INT = NULL
          ,@MakeField3Null BIT = 0
      AS
      BEGIN
      
          UPDATE dbo.Test
             SET Field1 = IIF(ISNULL(@MakeField1Null,0) = 1,NULL,ISNULL(@ParamField1,Field1))
                ,Field2 = IIF(ISNULL(@MakeField2Null,0) = 1,NULL,ISNULL(@ParamField2,Field2))
                ,Field3 = IIF(ISNULL(@MakeField3Null,0) = 1,NULL,ISNULL(@ParamField3,Field3))
          WHERE
             ID = @ParamId
      
      END
      

      基本上,如果您使用存储过程来更新表并且它具有可为空的字段,我认为我不建议将参数设置为可选的,因为它会导致业务案例/情况变得混乱未来,尤其是数字数据类型!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-10-18
        相关资源
        最近更新 更多