SQL 动态列和更新多列

Posted

技术标签:

【中文标题】SQL 动态列和更新多列【英文标题】:SQL dynamic columns and Update multiple columns 【发布时间】:2016-11-16 17:07:53 【问题描述】:

我有一个表UserPermission,其中有许多TINYINT 类型的列。例如读取、写入、更新、删除、访问等。

我在存储过程中得到三个参数:@UserId, @ColNames, @ColValues 其中@ColNames@ColValues 是逗号分隔值。

如何使用传递的列名和相应的值插入或更新表行(如果已存在)。

我尝试编写对INSERT 运行良好的动态查询,但我无法动态编写UPDATE 查询与每一列及其要连接的值。

任何回复将不胜感激

提前致谢。

【问题讨论】:

显示您的代码。 样本数据和预期结果会更有帮助。还添加您到目前为止尝试过的查询 可能想使用MERGE之类的东西 更新语句的问题是您需要拆分字符串并保持序号位置,然后通过序号位置关联 2 个表并重新连接。或者我想一次循环并更新 1 列,但后者不是很好。一般来说,不推荐这种更新/插入的方法。 是的,合并。此外,如果 INSERT 有效,则 UPDATE 也应该有效。尝试在执行之前打印您的语句并将其发布在此处。 【参考方案1】:

这是一种有点肮脏的方式来满足您的要求。但是,如果您创建以下存储过程:

CREATE FUNCTION [dbo].[stringSplit]
(
    @String NVARCHAR(4000),
    @Delimiter NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
    WITH Split(stpos,endpos)
    AS(
        SELECT 0 AS stpos, CHARINDEX(@Delimiter,@String) AS endpos
        UNION ALL
        SELECT endpos+1, CHARINDEX(@Delimiter,@String,endpos+1)
            FROM Split
            WHERE endpos > 0
    )
    SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
        'Data' = SUBSTRING(@String,stpos,COALESCE(NULLIF(endpos,0),LEN(@String)+1)-stpos)
    FROM Split
)

然后您可以使用该过程将数据连接在一起:

DECLARE @TotalCols INT
DECLARE @TotalVals INT

SET @TotalCols = (
        SELECT COUNT(ID) AS Total
        FROM dbo.stringSplit('department, teamlead', ',')
        );
 SET @TotalVals = (
         SELECT COUNT(ID) AS Total
         FROM dbo.stringSplit('IT, Bob', ',')
         );

 IF @TotalCols = @TotalVals
 BEGIN
     IF OBJECT_ID('tempdb..#temptable') IS NOT NULL
         DROP TABLE #temptable

     CREATE TABLE #temptable (
        ColName VARCHAR(MAX) NULL
        ,ColValue VARCHAR(MAX) NULL
        )

     INSERT INTO #temptable
     SELECT a.DATA
         ,b.DATA
    FROM dbo.stringSplit('department, teamlead', ',') AS a
    INNER JOIN dbo.stringSplit('IT, Bob', ',') AS b ON a.Id = b.Id

    SELECT *
    FROM #temptable;
END

效率不高,但会带给你想要的结果。

然后您可以根据需要使用临时表进行更新、插入和删除。

【讨论】:

嗨cyclington,感谢您的回复。但是它返回多行而不是单行,每个值作为列名。如何将行转换为列【参考方案2】:

我不会使用逗号分隔列表,而是为每个 Column 创建一个单独的参数,并将其默认值设为 NULL,并且在代码中如果其为 null 或插入 0,则不更新任何内容。像这样......

CREATE PROCEDURE usp_UserPermissions
 @UserID INT 
,@Update INT = NULL  --<-- Make default values NULL
,@Delete INT = NULL
,@Read   INT = NULL
,@Write  INT = NULL
,@Access INT = NULL
AS
BEGIN
  SET NOCOUNT ON;

Declare @t TABLE (UserID INT, [Update] INT,[Read] INT
                 ,[Write] INT,[Delete] INT,[Access] INT)

INSERT INTO @t (Userid, [Update],[Read],[Write],[Delete],[Access])
VALUES (@UserID , @Update , @Read, @Write , @Delete, @Access)   


IF EXISTS (SELECT 1 FROM UserPermission WHERE UserID = @UserID)
  BEGIN 
       UPDATE up        -- Only update if a value was provided else update to itself
        SET up.[Read]   = ISNULL(t.[Read]   , up.[Read])
           ,up.[Write]  = ISNULL(t.[Write]  , up.[Write])
           ,up.[Update] = ISNULL(t.[Update] , up.[Update])
           ,up.[Delete] = ISNULL(t.[Delete] , up.[Delete])
           ,up.[Access] = ISNULL(t.[Access] , up.[Access])
       FROM UserPermission up 
       INNER JOIN @t t ON up.UserID = t.UserID
  END
ELSE 
  BEGIN
         -- if already no row exists for that User add a row 
         -- If no value was passed for a column add 0 as default
    INSERT INTO UserPermission (Userid, [Update],[Read],[Write],[Delete],[Access])
    SELECT Userid
         , ISNULL([Update], 0)
         , ISNULL([Read], 0)
         , ISNULL([Write], 0)
         , ISNULL([Delete], 0)
         , ISNULL([Access], 0)
    FROM @t
  END

END

【讨论】:

此方法的问题是,如果列允许 NULL 值,则无法区分 NULL 是否是预期值,或者仅仅意味着缺少一个不打算更改的值。我正在尝试找到一个我和其他一些人写的答案,当我找到时会发布。 @Matt 因为这是一个权限表,我想一个用户要么有权限,要么没有,我们不想在权限表中放入未知权限,毕竟它是应用程序的安全性,无论如何您所要求的可以通过在最后一个插入中删除 ISNULL 函数来简单地在代码中处理,其余部分仍将保持不变。谢谢 如果列中不允许空值,则该方法运行良好,如果不删除 ISNULL 和参数的 DEFAULT 值将是必要的,因为它们不再是可选的。我发现了深入探讨这种结构的问题:***.com/questions/38952832/… 我不能将各个列用作其庞大的列表。此外,传递的列并不总是所有列,它只是实际列的子集。

以上是关于SQL 动态列和更新多列的主要内容,如果未能解决你的问题,请参考以下文章

sql动态创建多列

SQL 查询 - 要列和更新的行

SQL 行转列和列转行

JavaFX TableView 动态列和数据值

ActionScript 3 动态列和行

动态选择列和汇总数据[重复]