【问题标题】:Stored Procedure was working fine but suddenly stopped for certain values存储过程工作正常,但突然停止某些值
【发布时间】:2019-01-09 16:10:47
【问题描述】:

因此,我一直在为我正在构建的 SSRS 报告处理多个存储过程,但遇到了一个奇怪的错误,需要一双新的眼睛来看看我可能遗漏了什么。

我的程序非常简单 - SELECT 来自一些 JOINed 表的各种列,INSERT 将它们转换为 #temp 表和 SELECT 表的所有内容,以在我的表中显示为详细行报告。

我的完整过程如下所示:

USE [DB]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[rpt_select_ACHW_Ob]
      @PR_ID INT
AS BEGIN

SET NOCOUNT ON;

CREATE TABLE #temp
(BuildingNum INT,
MTHD VARCHAR(50),
ObDescript NVARCHAR(50),
SizeRemarks VARCHAR(50),
ObLength NUMERIC,
ObWidth NUMERIC,
ObArea NUMERIC,
Stories NUMERIC,
Grade VARCHAR(50),
YearBuilt SMALLINT,
Condition VARCHAR(50),
Phys NUMERIC,
FC VARCHAR(50),
PercentDone NUMERIC,
TaxValue NUMERIC)

DECLARE @RowCounter INT, @h INT
SET @RowCounter = 11

INSERT INTO #temp(BuildingNum, MTHD, ObDescript, SizeRemarks, ObLength, ObWidth, ObArea, Stories, Grade, YearBuilt, Condition, Phys, FC, PercentDone, TaxValue)
SELECT  ob.Ob_LineNumber AS BuildingNum,
        CASE WHEN ob.SP IS NOT NULL THEN 'S' ELSE 'P' END AS MTHD,
        som.Id AS ObDescript,
        CASE WHEN ob.SF IS NULL THEN ob.CN ELSE CAST((ob.SF + '/' + ob.CN) AS VARCHAR) END AS SizeRemarks,
        CASE WHEN ob.ob_Length IS NULL THEN 0 ELSE ob.Ob_Length END AS ObLength,
        CASE WHEN ob.ob_Width IS NULL THEN 0 ELSE ob.Ob_Width END AS ObWidth,
        CASE WHEN ob.ob_Length IS NULL WHEN ob.ob_Width IS NULL THEN 0 THEN 0 ELSE (ob.Ob_Length * ob.Ob_Width) END AS ObArea,
        ob.Ob_NStories AS OBStories,
        sovg.Grade AS ObGrade,
        ob.Ob_YearBuilt As ObYearBuilt,
        ob.Ob_ConditionCode AS ObConditionCode,
        ob.DR AS phys,
        ob.FC AS FC,
        ob.Ob_PercentComplete AS ObPercentComplete,
        ob.Ob_ValueTax AS TaxValue

    FROM        t_Ob ob WITH (NOLOCK)
    LEFT JOIN   t_ObToPR otpr WITH (NOLOCK) ON ob.Ob_ID=otpr.Ob_ID
    LEFT JOIN   t_PR pr WITH (NOLOCK) ON otpr.PR_ID=pr.PR_ID
    LEFT JOIN   t_S_Grade sovg WITH (NOLOCK) ON ob.S_Grade_ID=sovg.S_Grade_ID
    LEFT JOIN   t_SObD sod WITH (NOLOCK) ON ob.SObD_ID=sod.SObD_ID
    LEFT JOIN   t_SObM som WITH (NOLOCK) ON sod.SObM_ID=som.SObM_ID

WHERE       pr.PR_Id = @PR_ID

SET @h = (SELECT COUNT(*) FROM #temp) 
WHILE  @h < @RowCounter OR @h % @RowCounter > 0
BEGIN
    INSERT INTO #temp (BuildingNum) VALUES (NULL)
    SET @h = @h + 1
END

SELECT * FROM #temp 
ORDER BY CASE WHEN BuildingNum IS NULL THEN 1 ELSE 0 END, BuildingNum   
END

正如我所说,我一直对这段代码有一个奇怪的问题。在过去的两周里,所有测试用例都运行良好。我正在使用EXEC 根据参数@PR_ID 选择记录,它工作正常。昨天,在没有接触任何代码之后,我开始为某些 PR_ID 值生成错误代码:

消息 8114,级别 16,状态 5,过程 rpt_select_ACHW_Ob,第 28 行 [批处理开始第 2 行]
将数据类型 varchar 转换为数值时出错。

第 28 行将您带到FC VARCHAR(50),我已经检查了 10 次。 #temp 表中声明的所有数据类型都与选择的值完全匹配。有没有人知道为什么这停止工作?

Here's a dbFiddle link with some sample data

目前正在使用 SQL Server 2012。

【问题讨论】:

  • 你为什么要在这样的循环中插入?我看不出您提供的“示例数据”与您的查询有什么关系。您提供了一个表,您的查询中有几个。另外,小心将 NOLOCK 溅到各处。它不是一个性能工具。除了许多其他“功能”之外,它还可以并且将返回丢失和/或重复的行。 blogs.sentryone.com/aaronbertrand/bad-habits-nolock-everywhere
  • 我认为您的选择部分(插入查询)中的 case-when 语句会导致这些情况。请注释掉 case-when 语句并重试(改用 null)。如果它有效,请找出是哪一个导致它,然后查看该特定列的所有值。
  • @SeanLange 报告需要特定的格式,其他 procs 的常见做法是简单地插入 NULL 值以填充报告上所需的空间。 NOLOCK 也是如此。可能有更简单的方法,但我是一家成熟公司的入门级开发人员,所以我有什么资格质疑他们的方法?样本数据只是我期望从表中返回的样本。 t_obt_pr 都包含 30 列与财产记录相关的各种数据。
  • 对于你自己,你应该考虑使用一个计数表而不是一个循环来插入一堆这样的行。如此简单和快捷。这是一篇很棒的文章,解释了它如何替换循环。 sqlservercentral.com/articles/T-SQL/62867 至于 NOLOCK……嗯……这是一种糟糕的做法,但有时我们会被卡住。请记住,它不是免费的性能工具。在这种情况下,成本是准确性。有些时候还可以,而其他时候则不然。对于手头的问题,请参阅 Gordon 的回答。

标签: sql sql-server


【解决方案1】:

发生了两件事之一。您的原始查询编写正确并且有人更改了基础表(将整数列更改为字符串列)然后用非数字数据填充字符串列的可能性较小。

更有可能的情况是您的原始查询具有隐式转换。这只是一个等待发生的问题——现在你知道为什么了。您收到来自 SQL Server 的错误消息,它没有指定出现问题的表、行或列。啊!

我的建议是通过查询并检查每个表达式和比较,以确保类型兼容(数字/数字、字符串/字符串、日期时间/日期时间就足够了)。如果不是,请添加显式转换。您可以使用try_convert()try_cast() 添加转换,这至少可以避免错误(以产生NULLs 为代价)。

我希望 SQL Server 有一个“非隐式转换”模式,它会警告您查询正在使用此类转换。唉,没有。因此,请养成编写查询的习惯,以便所有转换都是明确的。

编辑:

例如(基于 cmets),这个表达式:

CAST((ob.SF + '/' + ob.CN) AS VARCHAR

应该是:

CAST( (ob.SF as VARCHAR(255)) + '/' + ob.CN) AS VARCHAR(255))

请注意,您应该在 SQL Server 中的所有 CHAR()/VARCHAR() 引用中包含长度。

【讨论】:

  • 我会很好奇 [ CAST((ob.SF + '/' + ob.CN) AS VARCHAR) ]。例如,如果 SF 和 CN 值是 Ints - 也许 SF 条件直到最近才为 NULL,并且没有进入该语句。
  • 让我们在这里扮演魔鬼的拥护者...如果有人确实更改了基础表,您会建议做什么?至少有 3 名开发人员能够更改架构,数据库是我们正在开发的主服务器的副本,供其他客户使用。 @level3looper SF 是数字类型,CN 是 varchar,因此在添加斜杠后将整个表达式转换为 varchar。我应该采取不同的做法吗?
  • @Steve-o169 。 . .您可能希望在架构中的所有表上使用 DDL 触发器,以便在开发过程中跟踪更改。
  • 感谢您和@level3looper,问题与SF 值有关。我只为该值添加了CAST,并且失败的值正在工作。只需CAST(ob.SF AS VARCHAR(50))
【解决方案2】:

根据您对@Gordon 回答的评论:如果准确,请将 Gordon 的回答标记为您的解决方案。

这将失败:

Declare @SF Int = 12
Declare @CN VarChar(10) = '2'

Select CAST((@SF + '/' + @CN) AS VARCHAR)

除非您添加演员:

Select CAST((Cast(@SF As VarChar(10)) + '/' + @CN) AS VARCHAR)

结果

12/2

【讨论】:

    猜你喜欢
    • 2022-01-20
    • 2016-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-10
    • 1970-01-01
    • 2018-09-18
    相关资源
    最近更新 更多