【问题标题】:MERGE, UPDATE, INSERT T-SQL Cannot INSERT value NULL合并、更新、插入 T-SQL 不能插入值 NULL
【发布时间】:2016-03-29 16:28:06
【问题描述】:

目标:保持一年中每个月学生班级排名的运行表

拥有:我的代码为我提供了列

StudentID; '+@DateTXT+'

DateTXT 是动态变量,返回我运行代码的月份。

需求:我正在尝试使用 MERGE、UPDATE、INSERT 函数来运行一次代码并建立一个表:

| StudentID | Jan |
|  56789    |  2  |
|  12345    |  7  |

然后每个月我都会在永久表中添加一个新的月份列:

EXEC('ALTER TABLE StudentRanking
     ADD ' + @DateTXT + ' smallint NOT NULL DEFAULT(999)')

| StudentID | Jan | Feb |
|  56789    |  2  | 999 |
|  12345    |  7  | 999 |

我将再次运行二月份的排名代码并将其保存到一个临时表中,我将使用该表与 StudentRanking 表进行合并、更新和插入:

| StudentID | Feb |
|  56789    |  3  |

(note.. student 12345 doesn't come up)

所以我想最终得到一个运行列表:

EXEC('
  MERGE StudentRanking AS TARGET
  USING ##TEMPDB2 AS SOURCE ON (TARGET.StudentID = SOURCE.StudentID)

  WHEN MATCHED AND TARGET.' + @DateTXT + ' <> SOURCE.' + @DateTXT + '
  THEN UPDATE SET TARGET.' + @DateTXT + ' = SOURCE.' + @DateTXT + '

  WHEN NOT MATCHED BY TARGET THEN
  INSERT (StudentID, ' + @Rank_TXT + ')
  VALUES (SOURCE.StudentID, SOURCE.' + @Rank_TXT + ') ')

| StudentID | Jan | Feb |
|  56789    |  2  |  3  |
|  12345    |  7  |null |

问题:一些学生离开学校,从而在接下来的几个月中创建了一个空排名(例如 12345 在 2 月份没有排名),所以当我尝试从临时表中插入结果时,我得到了这个错误:

SQL Server Database Error: Cannot insert the value NULL into column 'Feb', table 'tempdb.dbo.##TEMPDB'; column does not allow nulls. UPDATE fails.

我可以做一个 ISNULL(ranking,0) 但我宁愿有空值而不是 0

【问题讨论】:

  • 我注意到您正在使用全局临时表。这些对于并发性来说可能是非常有问题的。您收到该错误的原因是您的列的表定义不允许 NULL。
  • 这个解决方案太可怕了。为什么不使用正确规范化的表,其中StudentIDMonth 构成您的主键?每个月动态地向表中添加列,然后使用动态 SQL 来更新它是灾难的根源。您愿意采用不同的方法吗?
  • 始终开放学习不同的方法@GarethD 我确实得到了它的工作,但如果你有更好的解决方案的来源,如果你愿意,请发送。

标签: sql sql-server merge null


【解决方案1】:

始终对不同的、更好的方法持开放态度@GarethD!

我实际上是通过这样做来实现它的:

EXEC('ALTER TABLE StudentRanking
ADD ' + @Date_TXT + ' smallint DEFAULT(null)')

WHEN MATCHED AND TARGET.' + @Date_TXT + ' IS NULL
THEN UPDATE SET TARGET.' + @Date_TXT + ' = SOURCE.' + @Date_TXT + '

【讨论】:

    【解决方案2】:

    快速解决方法是使列不可为空。值得指出的是,这个解决方案不能很好地扩展。一种更具可扩展性的方法是使用正确规范化的表,其中 StudentIDMonth 构成您的主键。

    然后你有类似的东西:

    CREATE TABLE dbo.StudentRanking 
    (
            Date DATE NOT NULL,
            StudentID INT NOT NULL,
            Score INT NOT NULL,
        CONSTRAINT PK_StudentRanking__StudentID_Date PRIMARY KEY(Date, StudentID),
    );
    

    然后,您可以在此之上创建一个视图,以获取所需格式的表格:

    CREATE VIEW dbo.StudentRankingByYear
    WITH SCHEMABINDING
    AS
        SELECT  StudentID,
                Year = DATEPART(YEAR, Date),
                Jan = SUM(CASE WHEN DATEPART(MONTH, Date) = 1 THEN Score END),
                Feb = SUM(CASE WHEN DATEPART(MONTH, Date) = 2 THEN Score END),
                Mar = SUM(CASE WHEN DATEPART(MONTH, Date) = 3 THEN Score END),
                Apr = SUM(CASE WHEN DATEPART(MONTH, Date) = 4 THEN Score END),
                May = SUM(CASE WHEN DATEPART(MONTH, Date) = 5 THEN Score END),
                Jun = SUM(CASE WHEN DATEPART(MONTH, Date) = 6 THEN Score END),
                Jul = SUM(CASE WHEN DATEPART(MONTH, Date) = 7 THEN Score END),
                Aug = SUM(CASE WHEN DATEPART(MONTH, Date) = 8 THEN Score END),
                Sep = SUM(CASE WHEN DATEPART(MONTH, Date) = 9 THEN Score END),
                Oct = SUM(CASE WHEN DATEPART(MONTH, Date) = 10 THEN Score END),
                Nov = SUM(CASE WHEN DATEPART(MONTH, Date) = 11 THEN Score END),
                Dec = SUM(CASE WHEN DATEPART(MONTH, Date) = 12 THEN Score END)
        FROM    dbo.StudentRanking
        GROUP BY StudentID, DATEPART(YEAR, Date);
    GO
    

    您甚至可以在此之上创建一个索引视图,以获取您想要的格式的表(如果您确实需要,但查询应该执行得足够好而不需要视图),唯一的区别是您不能有空列,因此缺少的月份必须显示为 0:

    CREATE VIEW dbo.StudentRankingByYear
    WITH SCHEMABINDING
    AS
        SELECT  StudentID,
                Year = DATEPART(YEAR, Date),
                Jan = SUM(CASE WHEN DATEPART(MONTH, Date) = 1 THEN Score ELSE 0 END),
                Feb = SUM(CASE WHEN DATEPART(MONTH, Date) = 2 THEN Score ELSE 0 END),
                Mar = SUM(CASE WHEN DATEPART(MONTH, Date) = 3 THEN Score ELSE 0 END),
                Apr = SUM(CASE WHEN DATEPART(MONTH, Date) = 4 THEN Score ELSE 0 END),
                May = SUM(CASE WHEN DATEPART(MONTH, Date) = 5 THEN Score ELSE 0 END),
                Jun = SUM(CASE WHEN DATEPART(MONTH, Date) = 6 THEN Score ELSE 0 END),
                Jul = SUM(CASE WHEN DATEPART(MONTH, Date) = 7 THEN Score ELSE 0 END),
                Aug = SUM(CASE WHEN DATEPART(MONTH, Date) = 8 THEN Score ELSE 0 END),
                Sep = SUM(CASE WHEN DATEPART(MONTH, Date) = 9 THEN Score ELSE 0 END),
                Oct = SUM(CASE WHEN DATEPART(MONTH, Date) = 10 THEN Score ELSE 0 END),
                Nov = SUM(CASE WHEN DATEPART(MONTH, Date) = 11 THEN Score ELSE 0 END),
                Dec = SUM(CASE WHEN DATEPART(MONTH, Date) = 12 THEN Score ELSE 0 END),
                Records = COUNT_BIG(*)
        FROM    dbo.StudentRanking
        GROUP BY StudentID, DATEPART(YEAR, Date);
    GO
    CREATE UNIQUE CLUSTERED INDEX UQ_StudentRankingByYear__StudentID_Year
        ON dbo.StudentRankingByYear (StudentID, Year);
    

    【讨论】:

    • 感谢 GarethD,这两种解决方案都运行良好。感谢您提供允许空值和零值的选项。非常感谢!
    【解决方案3】:

    将您的 ALTER TABLE 更改为:

    EXEC('ALTER TABLE StudentRanking
         ADD ' + @DateTXT + ' smallint NULL')
    

    假设否决票是因为我没有提供标准化的替代方案,我建议使用 PIVOT 来解决此类问题。

    设置:

    CREATE TABLE dbo.StudentRanking 
    (
            MonthID CHAR(3) NOT NULL,
            StudentID INT NOT NULL,
            Score INT NOT NULL,
        CONSTRAINT PK_StudentRanking__StudentID_Date PRIMARY KEY(MonthID, StudentID),
    );
    
    INSERT INTO dbo.StudentRanking VALUES ('JAN', 56321, 2)
    INSERT INTO dbo.StudentRanking VALUES ('FEB', 56321, 2)
    INSERT INTO dbo.StudentRanking VALUES ('MAR', 56321, 2)
    INSERT INTO dbo.StudentRanking VALUES ('APR', 56321, 2)
    INSERT INTO dbo.StudentRanking VALUES ('MAY', 56321, 2)
    INSERT INTO dbo.StudentRanking VALUES ('JUN', 56321, 2)
    INSERT INTO dbo.StudentRanking VALUES ('JUL', 56321, 2)
    INSERT INTO dbo.StudentRanking VALUES ('AUG', 56321, 3)
    INSERT INTO dbo.StudentRanking VALUES ('SEP', 56321, 2)
    INSERT INTO dbo.StudentRanking VALUES ('OCT', 56321, 3)
    INSERT INTO dbo.StudentRanking VALUES ('NOV', 56321, 2)
    INSERT INTO dbo.StudentRanking VALUES ('DEC', 56321, 2)
    
    INSERT INTO dbo.StudentRanking VALUES ('JAN', 56821, 1)
    INSERT INTO dbo.StudentRanking VALUES ('FEB', 56821, 1)
    INSERT INTO dbo.StudentRanking VALUES ('MAR', 56821, 1)
    INSERT INTO dbo.StudentRanking VALUES ('APR', 56821, 1)
    INSERT INTO dbo.StudentRanking VALUES ('MAY', 56821, 1)
    INSERT INTO dbo.StudentRanking VALUES ('JUN', 56821, 1)
    INSERT INTO dbo.StudentRanking VALUES ('JUL', 56821, 1)
    INSERT INTO dbo.StudentRanking VALUES ('AUG', 56821, 2)
    INSERT INTO dbo.StudentRanking VALUES ('SEP', 56821, 1)
    INSERT INTO dbo.StudentRanking VALUES ('OCT', 56821, 2)
    INSERT INTO dbo.StudentRanking VALUES ('NOV', 56821, 1)
    INSERT INTO dbo.StudentRanking VALUES ('DEC', 56821, 1)
    
    INSERT INTO dbo.StudentRanking VALUES ('JAN', 56021, 3)
    INSERT INTO dbo.StudentRanking VALUES ('FEB', 56021, 3)
    INSERT INTO dbo.StudentRanking VALUES ('MAR', 56021, 3)
    INSERT INTO dbo.StudentRanking VALUES ('APR', 56021, 3)
    INSERT INTO dbo.StudentRanking VALUES ('MAY', 56021, 3)
    INSERT INTO dbo.StudentRanking VALUES ('JUN', 56021, 4)
    INSERT INTO dbo.StudentRanking VALUES ('JUL', 56021, 5)
    

    查询

    SELECT * FROM StudentRanking
    PIVOT (SUM(Score) FOR MonthID IN (JAN, FEB, MAR, APR, 
    MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC)) AS PVT
    

    结果

    在这种情况下,SUM(SCORE) 是无害的,因为每个学生每月的记录不会超过一个。它只是为了让 PIVOT 知道要解决的问题。

    【讨论】:

    • 您的解决方案效果很好,我认为枢轴选项比使用 CASE 语句更优雅。这两种解决方案都比我自己的简单得多。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2011-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-13
    • 1970-01-01
    相关资源
    最近更新 更多