在 sql 查询中启用 where 子句时出现 .NET 框架错误

.NET framework error when enabling where clause in sql query

我遇到了一个奇怪的问题,其中 disabling/enabling 在 where 子句中的某些条件下,我的 Select 查询抛出 .net 框架错误。

这是 CREATE table 脚本。

Table test_classes:

CREATE TABLE [dbo].[test_classes]
(
    [CLASSID] [int] NOT NULL,
    [PARENTID] [int] NULL,
    [CATID] [int] NOT NULL,
    [CLASS_NAME] [nvarchar](255) NOT NULL,
    [ORIGINAL_NAME] [nvarchar](255) NULL,
    [GEOMETRY] [tinyint] NOT NULL,
    [READ_ONLY] [bit] NOT NULL,
    [DISPLAY_STYLES] [image] NULL,
    [FEATURE_COUNT] [int] NOT NULL,
    [TEMPOWNER] [int] NULL,
    [OPTIONS] [int] NOT NULL,
    [POLYGON_TYPE] [int] NULL,
    [CLASS_EXTRA] [nvarchar](1024) NULL,
    [MAPID] [int] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

Table test_polygon:

CREATE TABLE [dbo].[test_polygon]
(
    [FID] [nvarchar](36) NOT NULL,
    [EXTENT_L] [float] NOT NULL,
    [EXTENT_T] [float] NOT NULL,
    [EXTENT_R] [float] NOT NULL,
    [EXTENT_B] [float] NOT NULL,
    [COORDINATES] [image] NULL,
    [CHAINS] [smallint] NOT NULL,
    [CLASSID] [int] NOT NULL,
    [SPATIAL_KEY] [bigint] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

由于字数限制(由于图像数据类型),这里是 INSERT 输入:GDrive SQL Link

SELECT SQL 查询:

select 
    Class_Name, FID, 
    geometry::STGeomFromWKB(b1+b2,0) as polygon, 
    Class_ID, Original_Name
from 
    (Select 
         cl.Class_Name, p.FID,
         substring(CAST(p.Coordinates AS varbinary(max)),1,1) as b1,
         substring(CAST(p.Coordinates AS varbinary(max)),3,999999) as b2,
         cl.ClassID as Class_ID,
         cl.Original_Name
     From       
         test_polygon p
     Inner Join 
         test_classes cl on cl.ClassID = p.ClassID) s_polygon
--where Class_ID = 215                  --Filter#1
--where Class_Name = 'L1_County'        --Filter#2

注意,Class_ID215代表'L1_County'class_name.

问题是,如果启用 Filter#1,则输出符合预期。但是当我只启用 Filter#2 时,查询失败并显示 .NET Error

预期输出:

Class_Name  FID               polygon       Class_ID    Original_Name
----------- ----------------  ------------- ----------- ------------------------
L1_County   Northamptonshire  <long value>  215         B8USR_4DB8184E88092424 

我得到的错误:

Msg 6522, Level 16, State 1, Line 4
A .NET Framework error occurred during execution of user-defined routine or aggregate "geometry":
System.FormatException: 24119: The Polygon input is not valid because the start and end points of the exterior ring are not the same. Each ring of a polygon must have the same start and end points.

System.FormatException:
at Microsoft.SqlServer.Types.GeometryValidator.ValidatePolygonRing(Int32 iRing, Int32 cPoints, Double firstX, Double firstY, Double lastX, Double lastY)
at Microsoft.SqlServer.Types.Validator.Execute(Transition transition)
at Microsoft.SqlServer.Types.ForwardingGeoDataSink.EndFigure()
at Microsoft.SqlServer.Types.WellKnownBinaryReader.ReadLineStringPoints(ByteOrder byteOrder, UInt32 cPoints, Boolean readZ, Boolean readM)
at Microsoft.SqlServer.Types.WellKnownBinaryReader.ReadLinearRing(ByteOrder byteOrder, Boolean readZ, Boolean readM)
at Microsoft.SqlServer.Types.WellKnownBinaryReader.ParseWkbPolygonWithoutHeader(ByteOrder byteOrder, Boolean readZ, Boolean readM)
at Microsoft.SqlServer.Types.WellKnownBinaryReader.ParseWkb(OpenGisType> type) > at Microsoft.SqlServer.Types.WellKnownBinaryReader.Read(OpenGisType type, Int32 srid)
at Microsoft.SqlServer.Types.SqlGeometry.GeometryFromBinary(OpenGisType type, SqlBytes binary, Int32 srid) .

我想问的是,为什么当 WHERE 子句有 Class_Name 而不是 Class_ID.

时出现错误

我正在使用 SQL Server 2012 企业版。 SQL Server 2008 中也会出现错误。

编辑:

过滤器 #1 的估计执行计划:

过滤器 #2 的估计执行计划:

我会总结评论:

您遇到此问题是因为您的 table 包含无效数据。在按 test_polygon.Class_ID 搜索时看不到它的原因是 Class_ID 作为谓词传递给 table 扫描。当 test_classes.Class_Name 用作过滤器时,搜索谓词将应用于 test_classes table。 由于 geometry::STGeomFromWKB "Compute Scalar" 发生在 "Join" 之前,它会导致 test_polygon 的所有行都由该函数求值,包括包含无效数据的行。

更新: 尽管计划看起来相同,但实际上并非如此,因为不同过滤器(WHERE 条件)的谓词条件不同,因此 table扫描操作符不同

在 SQL 服务器查询中没有强制执行评估顺序的标准方法,因为设计是您不应该这样做的。

有两种选择:

  1. 具体化(存储在 table 中)子查询的结果。简单地说,这将查询拆分为两个单独的查询,一个用于查找记录,第二个查询用于根据找到的结果计算数据。中间结果存储在 (temp) table.
  2. 使用 "hacks" 可以强制 SQL 服务器以某种方式评估查询。

下面是一个 "hack" 的例子:

select 
    Class_Name, FID, 
    CASE WHEN Class_Name = Class_Name THEN geometry::STGeomFromWKB(b1+b2,0) ELSE NULL END as polygon,
    Class_ID, Original_Name
from 
    (Select 
         cl.Class_Name, p.FID,
         substring(CAST(p.Coordinates AS varbinary(max)),1,1) as b1,
         substring(CAST(p.Coordinates AS varbinary(max)),3,999999) as b2,
         cl.ClassID as Class_ID,
         cl.Original_Name
     From       
         test_polygon p
     Inner Join 
         test_classes cl on cl.ClassID = p.ClassID) s_polygon
--where Class_ID = 215                  --Filter#1
where Class_Name = 'L1_County'        --Filter#2

通过添加一个查看 test_classes.Class_Name 的虚拟 CASE 表达式,我们强制 SQL 服务器在 JOIN 解析后对其求值。

计划:

有用的文章: http://dataeducation.com/cursors-run-just-fine/