给出下面的test3表
/****** Object: Table [dbo].[test3] Script Date: 11/12/2023 9:30:17 AM ******/
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[test3]') AND type in (N'U'))
DROP TABLE [dbo].[test3]
GO
/****** Object: Table [dbo].[test3] Script Date: 11/12/2023 9:30:17 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[test3](
[id] [int] IDENTITY(1,1) NOT NULL,
[column1] [varchar](10) NOT NULL
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[test3] ON
GO
INSERT [dbo].[test3] ([id], [column1]) VALUES (1, N'aaa')
GO
INSERT [dbo].[test3] ([id], [column1]) VALUES (2, N'bbb')
GO
SET IDENTITY_INSERT [dbo].[test3] OFF
GO
问题:
sqlstatement1 返回表的所有两行。 sqlstatement2 返回表的零行。
import pyodbc
connectionString = 'DRIVER={ODBC Driver 17 for SQL Server};SERVER=7D3QJR3;DATABASE=mint2;Trusted_Connection=yes'
currentConnection = pyodbc.connect(connectionString)
sqlStatement1 = '''
SELECT
id,
column1
FROM test3
WHERE
ISNULL(?, id) = id
ORDER BY
ID
'''
sqlStatement2 = '''
SELECT
id,
column1
FROM test3
WHERE
ISNULL(?, column1) = column1
ORDER BY
ID
'''
#Process sqlStatement1
sqlArgs = []
sqlArgs.append(None)
cursor = currentConnection.cursor()
cursor.execute(sqlStatement1,sqlArgs)
rows = cursor.fetchall()
print('ROWS WITH ID=NULL:' + str(len(rows)))
cursor.close()
#Process sqlStatement2
sqlArgs = []
sqlArgs.append(None)
cursor = currentConnection.cursor()
cursor.execute(sqlStatement2,sqlArgs)
rows = cursor.fetchall()
print('ROWS WITH COLUMN1=NULL:' + str(len(rows)))
cursor.close()
那么为什么它适用于 int 数据类型而不适用于 string 数据类型?
我的直觉是,当语句比较时,sp_prepexec 语句由于某种原因将位置参数 P1 创建为 varchar(1) ?到 varchar 列,并在语句 comapres ? 时将 P1 设置为 int 。到 int 列:
declare @p1 int
set @p1=1
exec sp_prepexec @p1 output,N'@P1 int',N'
SELECT
id,
column1
FROM test3
WHERE
ISNULL(@P1, id) = id
ORDER BY
ID
',NULL
select @p1
对
declare @p1 int
set @p1=2
exec sp_prepexec @p1 output,N'@P1 varchar(1)',N'
SELECT
id,
column1
FROM test3
WHERE
ISNULL(@P1, column1) = column1
ORDER BY
ID
',NULL
select @p1
我的直觉是,当语句比较时,sp_prepexec 语句由于某种原因将位置参数 P1 创建为 varchar(1) ?到 varchar 列,并在语句 comapres ? 时将 P1 设置为 int 。到 int 列。
是的,这正是它的作用。它不知道参数的大小,因为你没有告诉它。这已在 GitHub 上注明。
因为您在
ISNULL
的左侧使用它,所以右侧会投射到左侧,因此查询不会给出正确的结果。
您有多种解决方案:
可以使用
setinputsizes
设置字符串变量的类型和大小
cursor.setinputsizes([(pyodbc.SQL_VARCHAR, 10, 0)])
但这会为所有字符串变量设置它。
进行显式转换。这是一个更好的选择,因为它永远不会失败,并且您可以单独设置每个值。
WHERE
ISNULL(CAST(? AS varchar(10)), column1) = column1
您也可以通过先将其设置为正确大小的变量来完成此操作。
重写您的查询以不使用
ISNULL
,无论如何您都应该这样做,因为它会阻止使用索引。
OR
sqlStatement2 = '''
SELECT
id,
column1
FROM test3
WHERE
(? = column1 OR ? IS NULL)
ORDER BY
ID;
'''
请注意,您需要传递参数两次,或者将其分配给 SQL 中的变量。sqlStatement2 = '''
SELECT
id,
column1
FROM test3
'''
if someValue is not None:
sqlStatement2 = sqlStatement2 + '''WHERE
? = column1
'''
sqlStatement2 = sqlStatement2 + '''ORDER BY
ID;
'''
请注意,在最后两个选项中,设置可变大小可能仍然是理想的选择。