我正在运行更新脚本来混淆数据,偶尔会遇到算术溢出错误消息,如标题中所示。正在更新的表有260k条记录,但更新脚本需要多次运行才能产生错误。虽然它非常罕见,但我不能依赖代码直到它被修复,因为它很难调试。
查看其他类似问题,通常可以通过在表格或计算中更改数据类型(例如从INT
到BIGINT
来解决)。但是,我无法确定这可能需要的地方。我已将脚本缩减到下面,因为我已设法将其指向更新一列。
更新调用了一个函数,我在下面将其包含在内。我怀疑,由于错误的随机性,使用NEW_ID函数可能会导致它,但我还没有能够在多次运行该部分函数时重新创建错误。 NEW_ID功能无法在功能中使用,因此可以从视图中调用它,也包含在下面。
更新脚本:
UPDATE dbo.Addresses
SET HouseNumber = CASE WHEN LEN(HouseNumber) > 0
THEN dbo.fn_GenerateRandomString (LEN(HouseNumber), 1, 1, 1)
ELSE HouseNumber
END
NEW_ID视图和随机字符串函数
CREATE VIEW dbo.vw_GetNewID
AS
SELECT NEWID() AS New_ID
CREATE FUNCTION dbo.fn_GenerateRandomString (
@stringLength int,
@upperCaseBit bit,
@lowerCaseBit bit,
@numberBit bit
)
RETURNS nvarchar(100)
AS
BEGIN
-- Sanitise string length values.
IF ISNULL(@stringLength, -1) < 0
SET @stringLength = 0
-- Generate a random string from the specified character sets.
DECLARE @string nvarchar(100) = ''
SELECT
@string += c2
FROM
(
SELECT TOP (@stringLength) c2 FROM (
SELECT c1 FROM
(
VALUES ('A'),('B'),('C')
) AS T1(c1)
WHERE @upperCaseBit = 1
UNION ALL
SELECT c1 FROM
(
VALUES ('a'),('b'),('c')
) AS T1(c1)
WHERE @lowerCaseBit = 1
SELECT c1 FROM
(
VALUES ('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9')
) AS T1(c1)
WHERE @numberBit = 1
)
AS T2(c2)
ORDER BY (SELECT ABS(CHECKSUM(New_ID)) from vw_GetNewID)
) AS T2
RETURN @string
END
地址表(用于测试):
CREATE TABLE dbo.Addresses(HouseNumber nchar(32) NULL)
INSERT Addresses(HouseNumber)
VALUES ('DSjkmf jkghjsh35hjk h2jkhj3h jhf'),
('SDjfksj3548 ksjk'),
(NULL),
(''),
('2a'),
('1234567890'),
('An2b')
注意:地址表中只有7k的行输入了值LEN(HouseNumber) > 0
。
答案 0 :(得分:3)
基于字符串的代码中的算术溢出令人困惑。但是有一件事可能导致算术溢出。那是你的ORDER BY
条款:
ORDER BY (SELECT ABS(CHECKSUM(New_ID)) from vw_GetNewID)
CHECKSUM()
返回一个整数,其范围为-2,147,483,648到2,147,483,647。请注意,最小数字的绝对值是2,147,483,648,这就在范围之外。您可以验证SELECT ABS(CAST('-2147483648' as int))
是否生成算术溢出错误。
您不需要checksum()
。唉,你确实需要视图,因为这个逻辑在一个函数中,而NEWID()
是副作用的。但是,您可以使用:
ORDER BY (SELECT New_ID from vw_GetNewID)
我怀疑你每隔百万左右而不是每40亿行看到这个行的原因是因为ORDER BY
值作为排序过程的一部分被多次评估。最终,它将达到下限。
编辑:
如果你关心效率,使用字符串操作而不是表来执行此操作可能会更快。我可能会推荐这个版本的函数:
CREATE VIEW vw_rand AS SELECT rand() as rand;
GO
CREATE FUNCTION dbo.fn_GenerateRandomString (
@stringLength int,
@upperCaseBit bit,
@lowerCaseBit bit,
@numberBit bit
)
RETURNS nvarchar(100)
AS
BEGIN
DECLARE @string NVARCHAR(255) = '';
-- Sanitise string length values.
IF ISNULL(@stringLength, -1) < 0
SET @stringLength = 0;
DECLARE @lets VARCHAR(255) = '';
IF (@upperCaseBit = 1) SET @lets = @lets + 'ABC';
IF (@lowerCaseBit = 1) SET @lets = @lets + 'abc';
IF (@numberBit = 1) SET @lets = @lets + '0123456789';
DECLARE @len int = len(@lets);
WHILE @stringLength > 0 BEGIN
SELECT @string += SUBSTRING(@lets, 1 + CAST(rand * @len as INT), 1)
FROM vw_rand;
SET @stringLength = @stringLength - 1;
END;
RETURN @string
END;
作为备注:rand()
被记录为不包括其范围的结尾,因此您不必担心它会正好返回1。
此外,此版本与您的版本略有不同,因为它可以多次拉出相同的字母(因此也可以处理更长的字符串)。我认为这实际上是一种好处。