我们希望改变将值从PHP传递到存储过程(T-SQL)的方式。我只有很少的PHP经验,但我会尝试通过与我们的Web开发人员的讨论来解释这个过程。
当前流程
示例测试表
为了更新记录,例如本例中的Field3,我们会将所有现有值传递回存储过程。
sql-mode=""
让我们说更新Field3,你必须单击一个按钮。这将导航到一个新页面,该页面将运行存储过程以更新数据。由于新页面不知道值,因此必须运行EXEC dbo.UpdateTest @ID = 1, @Field1 = 'ABC', @Field2 = 'DEF', @Field3 = 'GHI', @Field4 = 'JKL'
过程以在运行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
(在本例中为-999),以便在传递任意值(-999)时,过程将更新NULL
。
这个解决方案相当混乱,在我看来,这是一种解决问题的低效方法。还有更好的解决方案吗?我们做错了什么?
非常感谢任何回复
答案 0 :(得分:2)
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示例的想法有多糟糕,因为现在你的程序将接受它不应该的数字范围!
弗拉基米尔建议的另一种方法/略微变化是标记何时使字段为空而不是何时更新。这将需要一些习惯让你的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
基本上,如果您使用存储过程来更新表并且它具有可空字段,我不认为我会建议将参数设置为可选的,因为它会导致业务案例/情况可能是将来凌乱,尤其是数字数据类型!
答案 1 :(得分:1)
您对-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
标志来指示哪些参数包含有用的值以及哪些参数应该被忽略。
答案 2 :(得分:0)
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。
答案 3 :(得分:0)
没有足够的声誉来评论。 在我看来,只要任意值不能是任何字段的正常值,您的解决方案就足够了。
但是,当字段不应具有“实际”值且从客户端故意更新时,我会考虑传递和存储除NULL之外的其他内容(例如“N / A”)。