在存储过程中插入 SQL WHERE 子句作为参数,并仅使用非空子句及其参数值
Insert SQL WHERE clauses as parameter in a stored procedure and use only the non-null clauses and their parameter value
我的问题有点复杂,但我会尽量简化它。我的基本想法是声明 3 个条件(条件是 where 子句)和 3 个参数(与条件一起使用)。这些是为存储过程声明的 运行 动态 SQL.
我想要的是创建一个 SELECT * FROM TableName
并仅使用不可为空的条件和参数对过滤 table。
虚拟 table:
CREATE TABLE test
(
id_number NVARCHAR(50) NOT NULL,
number_of_products INT,
username TEXT
);
INSERT INTO test (id_number, number_of_products, username)
VALUES (1000077004, 3, 'Jhon Smith'),
(1000077005, 4, 'Nick Smith'),
(1000077006, 4, 'Dale Smith'),
(1000077007, 5, 'Diana Smith'),
(1000077008, 5, 'Alice Smith'),
(1000077009, 6, 'Antony Smith'),
(1000077010, NULL, 'Bruce Smith');
SELECT * FROM test
例如,如果用户仅指定 (condition1=' >', parameter1='3') 和 (condition2=' <', parameter2='6') 那么 SQL 查询将是:
SELECT * FROM test
WHERE number_of_products condiction1 parameter1 AND condition2 paramater2
但如果用户仅指定 (condition1=' >', parameter1='5') 那么 SQL 查询将为:
SELECT * FROM test
WHERE number_of_products condiction1 parameter1
为此,我创建了以下存储过程(不完整):
CREATE OR ALTER PROCEDURE [dbo].[dynamicquery1] (
@TableName NVARCHAR(50),
@Field NVARCHAR(100) = NULL,
@Criterion1 NVARCHAR(100) = NULL,
@Parameter1 NVARCHAR(100) = NULL,
@Criterion2 NVARCHAR(100) = NULL,
@Parameter2 NVARCHAR(100) = NULL,
@Criterion3 NVARCHAR(100) = NULL,
@Parameter3 NVARCHAR(100) = NULL,
@All VARCHAR(2) = '-1'
)
AS
BEGIN
PRINT('Starting the procedure')
SET NOCOUNT ON;
DECLARE
@SQL NVARCHAR(MAX),
@SQL_WHERE NVARCHAR(MAX),
@ParameterDef NVARCHAR(500);
SET @ParameterDef = '@Parameter NVARCHAR(100)'
SET @SQL = 'SELECT * FROM ' + @TableName;
SET @SQL_WHERE = '';
/* BUILD THE WHERE CLAUSE IF @Field IS PRESENT */
IF NULLIF ( @Field, '' ) IS NOT NULL
BEGIN
-- Field value
SET @SQL_WHERE = ' WHERE ' + @Field;
-- Set @Parameter value
SET @Parameter1 =
CASE WHEN NULLIF ( @Parameter1, '' ) IS NOT NULL
THEN @Parameter1
ELSE @All
END;
SET @Parameter2 =
CASE WHEN NULLIF ( @Parameter2, '' ) IS NOT NULL
THEN @Parameter2
ELSE @All
END;
-- Field Comparison value
IF @Field LIKE '%[0-9]%'
PRINT('Column is numeric')
BEGIN
SET @SQL_WHERE += CASE @Criterion1
WHEN 'greater than' THEN ' >' + @Parameter1
WHEN 'greater than or equal' THEN ' >=' + @Parameter1
WHEN 'less than' THEN ' <' + @Parameter1
WHEN 'less than or equal' THEN ' <=' + @Parameter1
WHEN 'not equal' THEN ' <>' + @Parameter1
WHEN 'equal' THEN ' =' + @Parameter1
ELSE ''
END;
PRINT('Column is still numeric')
END;
IF @Field NOT LIKE '%[0-9]%'
PRINT('Column is text')
BEGIN
SET @SQL_WHERE += CASE @Criterion1
WHEN 'start with' THEN ' LIKE ' + ''''+ @Parameter1 + '&'''
WHEN 'end with' THEN ' LIKE ' + '''&' + @Parameter1 + ''''
WHEN 'in any position' THEN ' LIKE ' + '''%' + @Parameter1 + '%'''
WHEN 'in second position' THEN ' LIKE ' + '''_' + @Parameter1 + '%'''
WHEN 'specific character and at least 2 characters in length' THEN ' LIKE ' + @Parameter1 + '_%'''
WHEN 'specific character and at least 3 characters in length' THEN ' LIKE ' + @Parameter1 + '__%'''
ELSE ''
END;
END;
END;
-- Finish SQL statement.
SET @SQL = @SQL + ISNULL ( @SQL_WHERE, '' ) + ';';
-- Execute the dynamic statement.
PRINT(@SQL)
PRINT(@ParameterDef)
PRINT(@Parameter1)
PRINT(@Criterion1)
PRINT(@Parameter2)
PRINT(@Criterion2)
EXEC sp_executesql @SQL, @ParameterDef, @Parameter1=@Parameter1, @Parameter2=Parameter2;
END
GO
对于如何更改 Criterion1 和 Parameter1 的 SET 语句以包括 Criterion2、3 和 Parameters2、3 对,我将非常感激。但是只有当它们不为空时才将它们包含在查询中,因此例如,
EXEC [dynamicquery1] @TableName='test', @Field='number_of_products', @Criterion1='greater than', @Parameter1 = '3', @Criterion2='less than or equal', @Parameter2 = '6'
上面的EXEC会return产品数量大于3小于等于6的行
正如我在评论中所说,我真的不建议这样做,但如果您要这样做,您需要在脚本中更改一些内容。比如你判断是否使用数字操作数:
IF @Field LIKE '%[0-9]%'
PRINT('Column is numeric')
这取决于您在所有数字列名称中都输入一个数字,并且不在任何 non-numeric 中输入一个数字,您最好在系统目录视图中查找实际类型:
SELECT t.name
FROM sys.columns AS c
INNER JOIN sys.types AS t
ON t.user_type_id = c.user_type_id
AND t.system_type_id = c.system_type_id
WHERE c.Name = @Field
AND c.object_id = OBJECT_ID(@TableName, 'U');
这还有一个额外的好处,即确保参数 @TableName
和 @Field
分别是有效的 table 和列名,因此为您的查询提供了一些额外的验证。
如果我要这样做,我会创建一个 table 来存储您要使用的操作数列表,以及 table:[=33 中的显示名称=]
CREATE TABLE dbo.Operands
(
OperandID INT IDENTITY(1, 1) NOT NULL,
Name VARCHAR(255) NOT NULL,
SqlExpression VARCHAR(50) NOT NULL,
CONSTRAINT PK_Operands__OperandID PRIMARY KEY (OperandID)
);
INSERT dbo.Operands(Name, SqlExpression)
VALUES
('greater than' , ' > %s'),
('greater than or equal', ' >= %s'),
('less than', ' < %s');
然后您可以将此 table 映射到有效的数据类型,这也将有助于验证传递的参数(例如,如果有人通过“开始于”比较但针对的是日期时间列)。在我的演示中,我跳过了创建实际的 table,而只是使用 table 值构造函数来创建它,使用您在问题中使用的数字和 non-numeric 值。
SELECT *
FROM (VALUES
(1, 'greater than' , ' > {{parameter}}'),
(1, 'greater than or equal', ' >= {{parameter}}'),
(1, 'less than', ' < {{parameter}}'),
(1, 'less than or equal', ' <= {{parameter}}'),
(1, 'not equal', ' <> {{parameter}}'),
(1, 'equal', ' = {{parameter}}'),
(0, 'equal', ' = {{parameter}}'),
(0, 'start with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'end with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'in any position', ' LIKE CONCAT(''%'', {{parameter}}, ''%'')'),
(0, 'in second position', ' LIKE CONCAT(''_'', {{parameter}}, ''%'')'),
(0, 'specific character and at least 2 characters in length', ' LIKE CONCAT({{parameter}}, ''_%'')'),
(0, 'specific character and at least 3 characters in length', ' LIKE CONCAT({{parameter}}, ''__%'')')
) op (NumericField, Criterion, Operator);
正如我所说,还有改进的余地,但是增加了映射的复杂性,从简单的 numeric/non-numeric 到考虑其他类型。
我在表达式中使用 {{parameter}}
的原因是稍后我将用实际参数名称替换它,可能还有一个表达式,所以它现在只是一个占位符。一个简单的例子是:
SELECT CONCAT(p.ColumnName, REPLACE(Operand, '{{parameter}}', p.ParameterName))
FROM (VALUES
(' = {{parameter}}', 'Column1', '@Parameter1'),
(' >= {{parameter}}', 'Column2', '@Parameter2'),
(' LIKE CONCAT(''%'', {{parameter}}, ''%'')', 'Column3', '@Parameter3')
) p (Operand, ColumnName, ParameterName);
哪个returns
Column1 = @Parameter1
Column2 >= @Parameter2
Column3 LIKE CONCAT('%', @Parameter3, '%')
在我的演示中,我只将类型限制为文本或数字类型,因为这似乎是您想要做的,因此下一节将检索传入的列的实际类型,并且将设置一个字段来表示它是否为数字。这将与操作数 table 结合使用以确定 field/criterion 是否为有效组合:
DECLARE @IsNumericField BIT, @TypeName SYSNAME;
SELECT @IsNumericField = CASE WHEN t.name IN ('sysname', 'nvarchar', 'nchar', 'char', 'text', 'varchar', 'ntext') THEN 0 ELSE 1 END,
@TypeName = t.name
FROM sys.columns AS c
INNER JOIN sys.types AS t
ON t.user_type_id = c.user_type_id
AND t.system_type_id = c.system_type_id
WHERE c.Name = @Field
AND c.object_id = OBJECT_ID(@TableName, 'U')
AND t.name IN ('sysname', 'nvarchar', 'nchar', 'char', 'text', 'varchar', 'ntext', 'tinyint',
'smallint', 'int', 'real', 'money', 'float', 'decimal', 'numeric', 'smallmoney', 'bigint');
下一部分是将所有这些结合起来以实际构建您的 where 子句:
DECLARE @Criterion1 NVARCHAR(100) = 'greater than',
@Parameter1 NVARCHAR(100) = '1',
@Criterion2 NVARCHAR(100) = 'less than or equal',
@Parameter2 NVARCHAR(100) = 5,
@Criterion3 NVARCHAR(100) = NULL,
@Parameter3 NVARCHAR(100) = NULL;
SELECT CONCAT('AND ',
QUOTENAME(@Field),
REPLACE(op.Operator, '{{parameter}}', CONCAT('TRY_CONVERT(', @TypeName, ', ', p.ParameterName, ')')))
FROM (VALUES
(1, 'greater than' , ' > {{parameter}}'),
(1, 'greater than or equal', ' >= {{parameter}}'),
(1, 'less than', ' < {{parameter}}'),
(1, 'less than or equal', ' <= {{parameter}}'),
(1, 'not equal', ' <> {{parameter}}'),
(1, 'equal', ' = {{parameter}}'),
(0, 'equal', ' = {{parameter}}'),
(0, 'start with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'end with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'in any position', ' LIKE CONCAT(''%'', {{parameter}}, ''%'')'),
(0, 'in second position', ' LIKE CONCAT(''_'', {{parameter}}, ''%'')'),
(0, 'specific character and at least 2 characters in length', ' LIKE CONCAT({{parameter}}, ''_%'')'),
(0, 'specific character and at least 3 characters in length', ' LIKE CONCAT({{parameter}}, ''__%'')')
) op (NumericField, Criterion, Operator)
INNER JOIN
(VALUES
(@Criterion1, @Parameter1, '@Parameter1'),
(@Criterion2, @Parameter2, '@Parameter2'),
(@Criterion3, @Parameter3, '@Parameter3')
) p (Criterion, ParameterValue, ParameterName)
ON p.Criterion = op.Criterion
WHERE op.NumericField = @IsNumericField;
由于我只传递了 parameter1 和 parameter2 的值,因此 returns 两行:
AND [number_of_products] > TRY_CONVERT(int, @Parameter1)
AND [number_of_products] <= TRY_CONVERT(int, @Parameter2)
我已经使用 TRY_CONVERT()
以及之前检索到的类型名称来更优雅地处理无效数据,因此如果有人为数字列传递参数值“String”,您将不会获得转换错误,你将得不到任何结果。如果您想抛出错误,只需使用 CONVERT()
即可。
在完整的工作演示中,我使用 STRING_AGG()
将其连接成一个变量。
最后,当调用你的SQL时,你不需要动态声明参数,如果它们没有出现在SQL中,它们将不会被使用,所以声明并通过所有 3 个将不是问题:
EXECUTE sp_executesql
@SQL,
N'@Parameter1 NVARCHAR(100), @Parameter2 NVARCHAR(100), @Parameter3 NVARCHAR(100)',
@Parameter1,
@Parameter2,
@Parameter3;
然后您需要做的就是将所有这些整合到一个存储过程中。
CREATE OR ALTER PROCEDURE [dbo].[dynamicquery1] (
@TableName sysname,
@Field NVARCHAR(100) = NULL,
@Criterion1 NVARCHAR(100) = NULL,
@Parameter1 NVARCHAR(100) = NULL,
@Criterion2 NVARCHAR(100) = NULL,
@Parameter2 NVARCHAR(100) = NULL,
@Criterion3 NVARCHAR(100) = NULL,
@Parameter3 NVARCHAR(100) = NULL,
@All VARCHAR(2) = '-1'
)
AS
BEGIN
-- VALIDATE TABLE EXISTS
IF NOT EXISTS (SELECT 1 FROM sys.tables AS t WHERE t.object_id = OBJECT_ID(@TableName))
BEGIN
RAISERROR ('Invalid table', 16, 1);
RETURN;
END
DECLARE @SQL NVARCHAR(MAX) = CONCAT('SELECT * FROM ', @TableName);
IF @Field IS NULL
BEGIN
EXECUTE sp_executesql @SQL;
RETURN;
END
-- USE SYSTEM CATALOGUE VIEWS TO VALIDATE @FIELD AND GET THE CORRECT TYPE
DECLARE @IsNumericField BIT, @TypeName SYSNAME;
SELECT @IsNumericField = CASE WHEN t.name IN ('sysname', 'nvarchar', 'nchar', 'char', 'text', 'varchar', 'ntext') THEN 0 ELSE 1 END,
@TypeName = t.name
FROM sys.columns AS c
INNER JOIN sys.types AS t
ON t.user_type_id = c.user_type_id
AND t.system_type_id = c.system_type_id
WHERE c.Name = @Field
AND c.object_id = OBJECT_ID(@TableName, 'U')
AND t.name IN ('sysname', 'nvarchar', 'nchar', 'char', 'text', 'varchar', 'ntext', 'tinyint',
'smallint', 'int', 'real', 'money', 'float', 'decimal', 'numeric', 'smallmoney', 'bigint');
IF @IsNumericField IS NULL
BEGIN
-- If @Numeric field was not set it means the column doesn't exist,
-- or the type of the column is not numeric or text
RAISERROR ('Invalid column or column is not queryable type', 16, 1);
RETURN;
END
-- DECLARE THE WHERE CLAUSE FOR THE DYNAMIX SQL
DECLARE @SQLWhere NVARCHAR(MAX) = ' WHERE 1 = 1 ';
-- BUILD UP THE WHERE CLAUSE BASED ON THE CRITERIA AND PARAMETERS PASSED
SELECT @SQLWhere += STRING_AGG(CONCAT('AND ',
QUOTENAME(@Field),
REPLACE(op.Operator, '{{parameter}}', CONCAT('TRY_CONVERT(', @TypeName, ', ', p.ParameterName, ')'))),
' ')
FROM (VALUES
(1, 'greater than' , ' > {{parameter}}'),
(1, 'greater than or equal', ' >= {{parameter}}'),
(1, 'less than', ' < {{parameter}}'),
(1, 'less than or equal', ' <= {{parameter}}'),
(1, 'not equal', ' <> {{parameter}}'),
(1, 'equal', ' = {{parameter}}'),
(0, 'equal', ' = {{parameter}}'),
(0, 'start with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'end with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'in any position', ' LIKE CONCAT(''%'', {{parameter}}, ''%'')'),
(0, 'in second position', ' LIKE CONCAT(''_'', {{parameter}}, ''%'')'),
(0, 'specific character and at least 2 characters in length', ' LIKE CONCAT({{parameter}}, ''_%'')'),
(0, 'specific character and at least 3 characters in length', ' LIKE CONCAT({{parameter}}, ''__%'')')
) op (NumericField, Criterion, Operator)
INNER JOIN
(VALUES
(@Criterion1, @Parameter1, '@Parameter1'),
(@Criterion2, @Parameter2, '@Parameter2'),
(@Criterion3, @Parameter3, '@Parameter3')
) p (Criterion, ParameterValue, ParameterName)
ON p.Criterion = op.Criterion
WHERE op.NumericField = @IsNumericField;
SET @SQL += @SQLWhere;
PRINT @SQL;
EXECUTE sp_executesql
@SQL,
N'@Parameter1 NVARCHAR(100), @Parameter2 NVARCHAR(100), @Parameter3 NVARCHAR(100)',
@Parameter1,
@Parameter2,
@Parameter3;
END
附录
如果要传递多个字段,添加新参数(@Field1、@Field2 等),然后调整 table 值构造函数,为字段名称添加另一列:
INNER JOIN
(VALUES
(@Field1, @Criterion1, @Parameter1, '@Parameter1'),
(@Field2, @Criterion2, @Parameter2, '@Parameter2'),
(@Field3, @Criterion3, @Parameter3, '@Parameter3')
) p (ColumnName, Criterion, ParameterValue, ParameterName)
ON p.Criterion = op.Criterion
然后使用这个新列来构建您的表达式而不是 @Field
:
SELECT @SQLWhere += STRING_AGG(CONCAT('AND ',
QUOTENAME(p.ColumnName),
REPLACE(op.Operator, '
{{parameter}}',
CONCAT('TRY_CONVERT(', t.Name, ', ', p.ParameterName, ')'))),
' ')
我的问题有点复杂,但我会尽量简化它。我的基本想法是声明 3 个条件(条件是 where 子句)和 3 个参数(与条件一起使用)。这些是为存储过程声明的 运行 动态 SQL.
我想要的是创建一个 SELECT * FROM TableName
并仅使用不可为空的条件和参数对过滤 table。
虚拟 table:
CREATE TABLE test
(
id_number NVARCHAR(50) NOT NULL,
number_of_products INT,
username TEXT
);
INSERT INTO test (id_number, number_of_products, username)
VALUES (1000077004, 3, 'Jhon Smith'),
(1000077005, 4, 'Nick Smith'),
(1000077006, 4, 'Dale Smith'),
(1000077007, 5, 'Diana Smith'),
(1000077008, 5, 'Alice Smith'),
(1000077009, 6, 'Antony Smith'),
(1000077010, NULL, 'Bruce Smith');
SELECT * FROM test
例如,如果用户仅指定 (condition1=' >', parameter1='3') 和 (condition2=' <', parameter2='6') 那么 SQL 查询将是:
SELECT * FROM test
WHERE number_of_products condiction1 parameter1 AND condition2 paramater2
但如果用户仅指定 (condition1=' >', parameter1='5') 那么 SQL 查询将为:
SELECT * FROM test
WHERE number_of_products condiction1 parameter1
为此,我创建了以下存储过程(不完整):
CREATE OR ALTER PROCEDURE [dbo].[dynamicquery1] (
@TableName NVARCHAR(50),
@Field NVARCHAR(100) = NULL,
@Criterion1 NVARCHAR(100) = NULL,
@Parameter1 NVARCHAR(100) = NULL,
@Criterion2 NVARCHAR(100) = NULL,
@Parameter2 NVARCHAR(100) = NULL,
@Criterion3 NVARCHAR(100) = NULL,
@Parameter3 NVARCHAR(100) = NULL,
@All VARCHAR(2) = '-1'
)
AS
BEGIN
PRINT('Starting the procedure')
SET NOCOUNT ON;
DECLARE
@SQL NVARCHAR(MAX),
@SQL_WHERE NVARCHAR(MAX),
@ParameterDef NVARCHAR(500);
SET @ParameterDef = '@Parameter NVARCHAR(100)'
SET @SQL = 'SELECT * FROM ' + @TableName;
SET @SQL_WHERE = '';
/* BUILD THE WHERE CLAUSE IF @Field IS PRESENT */
IF NULLIF ( @Field, '' ) IS NOT NULL
BEGIN
-- Field value
SET @SQL_WHERE = ' WHERE ' + @Field;
-- Set @Parameter value
SET @Parameter1 =
CASE WHEN NULLIF ( @Parameter1, '' ) IS NOT NULL
THEN @Parameter1
ELSE @All
END;
SET @Parameter2 =
CASE WHEN NULLIF ( @Parameter2, '' ) IS NOT NULL
THEN @Parameter2
ELSE @All
END;
-- Field Comparison value
IF @Field LIKE '%[0-9]%'
PRINT('Column is numeric')
BEGIN
SET @SQL_WHERE += CASE @Criterion1
WHEN 'greater than' THEN ' >' + @Parameter1
WHEN 'greater than or equal' THEN ' >=' + @Parameter1
WHEN 'less than' THEN ' <' + @Parameter1
WHEN 'less than or equal' THEN ' <=' + @Parameter1
WHEN 'not equal' THEN ' <>' + @Parameter1
WHEN 'equal' THEN ' =' + @Parameter1
ELSE ''
END;
PRINT('Column is still numeric')
END;
IF @Field NOT LIKE '%[0-9]%'
PRINT('Column is text')
BEGIN
SET @SQL_WHERE += CASE @Criterion1
WHEN 'start with' THEN ' LIKE ' + ''''+ @Parameter1 + '&'''
WHEN 'end with' THEN ' LIKE ' + '''&' + @Parameter1 + ''''
WHEN 'in any position' THEN ' LIKE ' + '''%' + @Parameter1 + '%'''
WHEN 'in second position' THEN ' LIKE ' + '''_' + @Parameter1 + '%'''
WHEN 'specific character and at least 2 characters in length' THEN ' LIKE ' + @Parameter1 + '_%'''
WHEN 'specific character and at least 3 characters in length' THEN ' LIKE ' + @Parameter1 + '__%'''
ELSE ''
END;
END;
END;
-- Finish SQL statement.
SET @SQL = @SQL + ISNULL ( @SQL_WHERE, '' ) + ';';
-- Execute the dynamic statement.
PRINT(@SQL)
PRINT(@ParameterDef)
PRINT(@Parameter1)
PRINT(@Criterion1)
PRINT(@Parameter2)
PRINT(@Criterion2)
EXEC sp_executesql @SQL, @ParameterDef, @Parameter1=@Parameter1, @Parameter2=Parameter2;
END
GO
对于如何更改 Criterion1 和 Parameter1 的 SET 语句以包括 Criterion2、3 和 Parameters2、3 对,我将非常感激。但是只有当它们不为空时才将它们包含在查询中,因此例如,
EXEC [dynamicquery1] @TableName='test', @Field='number_of_products', @Criterion1='greater than', @Parameter1 = '3', @Criterion2='less than or equal', @Parameter2 = '6'
上面的EXEC会return产品数量大于3小于等于6的行
正如我在评论中所说,我真的不建议这样做,但如果您要这样做,您需要在脚本中更改一些内容。比如你判断是否使用数字操作数:
IF @Field LIKE '%[0-9]%'
PRINT('Column is numeric')
这取决于您在所有数字列名称中都输入一个数字,并且不在任何 non-numeric 中输入一个数字,您最好在系统目录视图中查找实际类型:
SELECT t.name
FROM sys.columns AS c
INNER JOIN sys.types AS t
ON t.user_type_id = c.user_type_id
AND t.system_type_id = c.system_type_id
WHERE c.Name = @Field
AND c.object_id = OBJECT_ID(@TableName, 'U');
这还有一个额外的好处,即确保参数 @TableName
和 @Field
分别是有效的 table 和列名,因此为您的查询提供了一些额外的验证。
如果我要这样做,我会创建一个 table 来存储您要使用的操作数列表,以及 table:[=33 中的显示名称=]
CREATE TABLE dbo.Operands
(
OperandID INT IDENTITY(1, 1) NOT NULL,
Name VARCHAR(255) NOT NULL,
SqlExpression VARCHAR(50) NOT NULL,
CONSTRAINT PK_Operands__OperandID PRIMARY KEY (OperandID)
);
INSERT dbo.Operands(Name, SqlExpression)
VALUES
('greater than' , ' > %s'),
('greater than or equal', ' >= %s'),
('less than', ' < %s');
然后您可以将此 table 映射到有效的数据类型,这也将有助于验证传递的参数(例如,如果有人通过“开始于”比较但针对的是日期时间列)。在我的演示中,我跳过了创建实际的 table,而只是使用 table 值构造函数来创建它,使用您在问题中使用的数字和 non-numeric 值。
SELECT *
FROM (VALUES
(1, 'greater than' , ' > {{parameter}}'),
(1, 'greater than or equal', ' >= {{parameter}}'),
(1, 'less than', ' < {{parameter}}'),
(1, 'less than or equal', ' <= {{parameter}}'),
(1, 'not equal', ' <> {{parameter}}'),
(1, 'equal', ' = {{parameter}}'),
(0, 'equal', ' = {{parameter}}'),
(0, 'start with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'end with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'in any position', ' LIKE CONCAT(''%'', {{parameter}}, ''%'')'),
(0, 'in second position', ' LIKE CONCAT(''_'', {{parameter}}, ''%'')'),
(0, 'specific character and at least 2 characters in length', ' LIKE CONCAT({{parameter}}, ''_%'')'),
(0, 'specific character and at least 3 characters in length', ' LIKE CONCAT({{parameter}}, ''__%'')')
) op (NumericField, Criterion, Operator);
正如我所说,还有改进的余地,但是增加了映射的复杂性,从简单的 numeric/non-numeric 到考虑其他类型。
我在表达式中使用 {{parameter}}
的原因是稍后我将用实际参数名称替换它,可能还有一个表达式,所以它现在只是一个占位符。一个简单的例子是:
SELECT CONCAT(p.ColumnName, REPLACE(Operand, '{{parameter}}', p.ParameterName))
FROM (VALUES
(' = {{parameter}}', 'Column1', '@Parameter1'),
(' >= {{parameter}}', 'Column2', '@Parameter2'),
(' LIKE CONCAT(''%'', {{parameter}}, ''%'')', 'Column3', '@Parameter3')
) p (Operand, ColumnName, ParameterName);
哪个returns
Column1 = @Parameter1
Column2 >= @Parameter2
Column3 LIKE CONCAT('%', @Parameter3, '%')
在我的演示中,我只将类型限制为文本或数字类型,因为这似乎是您想要做的,因此下一节将检索传入的列的实际类型,并且将设置一个字段来表示它是否为数字。这将与操作数 table 结合使用以确定 field/criterion 是否为有效组合:
DECLARE @IsNumericField BIT, @TypeName SYSNAME;
SELECT @IsNumericField = CASE WHEN t.name IN ('sysname', 'nvarchar', 'nchar', 'char', 'text', 'varchar', 'ntext') THEN 0 ELSE 1 END,
@TypeName = t.name
FROM sys.columns AS c
INNER JOIN sys.types AS t
ON t.user_type_id = c.user_type_id
AND t.system_type_id = c.system_type_id
WHERE c.Name = @Field
AND c.object_id = OBJECT_ID(@TableName, 'U')
AND t.name IN ('sysname', 'nvarchar', 'nchar', 'char', 'text', 'varchar', 'ntext', 'tinyint',
'smallint', 'int', 'real', 'money', 'float', 'decimal', 'numeric', 'smallmoney', 'bigint');
下一部分是将所有这些结合起来以实际构建您的 where 子句:
DECLARE @Criterion1 NVARCHAR(100) = 'greater than',
@Parameter1 NVARCHAR(100) = '1',
@Criterion2 NVARCHAR(100) = 'less than or equal',
@Parameter2 NVARCHAR(100) = 5,
@Criterion3 NVARCHAR(100) = NULL,
@Parameter3 NVARCHAR(100) = NULL;
SELECT CONCAT('AND ',
QUOTENAME(@Field),
REPLACE(op.Operator, '{{parameter}}', CONCAT('TRY_CONVERT(', @TypeName, ', ', p.ParameterName, ')')))
FROM (VALUES
(1, 'greater than' , ' > {{parameter}}'),
(1, 'greater than or equal', ' >= {{parameter}}'),
(1, 'less than', ' < {{parameter}}'),
(1, 'less than or equal', ' <= {{parameter}}'),
(1, 'not equal', ' <> {{parameter}}'),
(1, 'equal', ' = {{parameter}}'),
(0, 'equal', ' = {{parameter}}'),
(0, 'start with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'end with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'in any position', ' LIKE CONCAT(''%'', {{parameter}}, ''%'')'),
(0, 'in second position', ' LIKE CONCAT(''_'', {{parameter}}, ''%'')'),
(0, 'specific character and at least 2 characters in length', ' LIKE CONCAT({{parameter}}, ''_%'')'),
(0, 'specific character and at least 3 characters in length', ' LIKE CONCAT({{parameter}}, ''__%'')')
) op (NumericField, Criterion, Operator)
INNER JOIN
(VALUES
(@Criterion1, @Parameter1, '@Parameter1'),
(@Criterion2, @Parameter2, '@Parameter2'),
(@Criterion3, @Parameter3, '@Parameter3')
) p (Criterion, ParameterValue, ParameterName)
ON p.Criterion = op.Criterion
WHERE op.NumericField = @IsNumericField;
由于我只传递了 parameter1 和 parameter2 的值,因此 returns 两行:
AND [number_of_products] > TRY_CONVERT(int, @Parameter1)
AND [number_of_products] <= TRY_CONVERT(int, @Parameter2)
我已经使用 TRY_CONVERT()
以及之前检索到的类型名称来更优雅地处理无效数据,因此如果有人为数字列传递参数值“String”,您将不会获得转换错误,你将得不到任何结果。如果您想抛出错误,只需使用 CONVERT()
即可。
在完整的工作演示中,我使用 STRING_AGG()
将其连接成一个变量。
最后,当调用你的SQL时,你不需要动态声明参数,如果它们没有出现在SQL中,它们将不会被使用,所以声明并通过所有 3 个将不是问题:
EXECUTE sp_executesql
@SQL,
N'@Parameter1 NVARCHAR(100), @Parameter2 NVARCHAR(100), @Parameter3 NVARCHAR(100)',
@Parameter1,
@Parameter2,
@Parameter3;
然后您需要做的就是将所有这些整合到一个存储过程中。
CREATE OR ALTER PROCEDURE [dbo].[dynamicquery1] (
@TableName sysname,
@Field NVARCHAR(100) = NULL,
@Criterion1 NVARCHAR(100) = NULL,
@Parameter1 NVARCHAR(100) = NULL,
@Criterion2 NVARCHAR(100) = NULL,
@Parameter2 NVARCHAR(100) = NULL,
@Criterion3 NVARCHAR(100) = NULL,
@Parameter3 NVARCHAR(100) = NULL,
@All VARCHAR(2) = '-1'
)
AS
BEGIN
-- VALIDATE TABLE EXISTS
IF NOT EXISTS (SELECT 1 FROM sys.tables AS t WHERE t.object_id = OBJECT_ID(@TableName))
BEGIN
RAISERROR ('Invalid table', 16, 1);
RETURN;
END
DECLARE @SQL NVARCHAR(MAX) = CONCAT('SELECT * FROM ', @TableName);
IF @Field IS NULL
BEGIN
EXECUTE sp_executesql @SQL;
RETURN;
END
-- USE SYSTEM CATALOGUE VIEWS TO VALIDATE @FIELD AND GET THE CORRECT TYPE
DECLARE @IsNumericField BIT, @TypeName SYSNAME;
SELECT @IsNumericField = CASE WHEN t.name IN ('sysname', 'nvarchar', 'nchar', 'char', 'text', 'varchar', 'ntext') THEN 0 ELSE 1 END,
@TypeName = t.name
FROM sys.columns AS c
INNER JOIN sys.types AS t
ON t.user_type_id = c.user_type_id
AND t.system_type_id = c.system_type_id
WHERE c.Name = @Field
AND c.object_id = OBJECT_ID(@TableName, 'U')
AND t.name IN ('sysname', 'nvarchar', 'nchar', 'char', 'text', 'varchar', 'ntext', 'tinyint',
'smallint', 'int', 'real', 'money', 'float', 'decimal', 'numeric', 'smallmoney', 'bigint');
IF @IsNumericField IS NULL
BEGIN
-- If @Numeric field was not set it means the column doesn't exist,
-- or the type of the column is not numeric or text
RAISERROR ('Invalid column or column is not queryable type', 16, 1);
RETURN;
END
-- DECLARE THE WHERE CLAUSE FOR THE DYNAMIX SQL
DECLARE @SQLWhere NVARCHAR(MAX) = ' WHERE 1 = 1 ';
-- BUILD UP THE WHERE CLAUSE BASED ON THE CRITERIA AND PARAMETERS PASSED
SELECT @SQLWhere += STRING_AGG(CONCAT('AND ',
QUOTENAME(@Field),
REPLACE(op.Operator, '{{parameter}}', CONCAT('TRY_CONVERT(', @TypeName, ', ', p.ParameterName, ')'))),
' ')
FROM (VALUES
(1, 'greater than' , ' > {{parameter}}'),
(1, 'greater than or equal', ' >= {{parameter}}'),
(1, 'less than', ' < {{parameter}}'),
(1, 'less than or equal', ' <= {{parameter}}'),
(1, 'not equal', ' <> {{parameter}}'),
(1, 'equal', ' = {{parameter}}'),
(0, 'equal', ' = {{parameter}}'),
(0, 'start with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'end with', ' LIKE CONCAT({{parameter}}, ''%'')'),
(0, 'in any position', ' LIKE CONCAT(''%'', {{parameter}}, ''%'')'),
(0, 'in second position', ' LIKE CONCAT(''_'', {{parameter}}, ''%'')'),
(0, 'specific character and at least 2 characters in length', ' LIKE CONCAT({{parameter}}, ''_%'')'),
(0, 'specific character and at least 3 characters in length', ' LIKE CONCAT({{parameter}}, ''__%'')')
) op (NumericField, Criterion, Operator)
INNER JOIN
(VALUES
(@Criterion1, @Parameter1, '@Parameter1'),
(@Criterion2, @Parameter2, '@Parameter2'),
(@Criterion3, @Parameter3, '@Parameter3')
) p (Criterion, ParameterValue, ParameterName)
ON p.Criterion = op.Criterion
WHERE op.NumericField = @IsNumericField;
SET @SQL += @SQLWhere;
PRINT @SQL;
EXECUTE sp_executesql
@SQL,
N'@Parameter1 NVARCHAR(100), @Parameter2 NVARCHAR(100), @Parameter3 NVARCHAR(100)',
@Parameter1,
@Parameter2,
@Parameter3;
END
附录
如果要传递多个字段,添加新参数(@Field1、@Field2 等),然后调整 table 值构造函数,为字段名称添加另一列:
INNER JOIN
(VALUES
(@Field1, @Criterion1, @Parameter1, '@Parameter1'),
(@Field2, @Criterion2, @Parameter2, '@Parameter2'),
(@Field3, @Criterion3, @Parameter3, '@Parameter3')
) p (ColumnName, Criterion, ParameterValue, ParameterName)
ON p.Criterion = op.Criterion
然后使用这个新列来构建您的表达式而不是 @Field
:
SELECT @SQLWhere += STRING_AGG(CONCAT('AND ',
QUOTENAME(p.ColumnName),
REPLACE(op.Operator, '
{{parameter}}',
CONCAT('TRY_CONVERT(', t.Name, ', ', p.ParameterName, ')'))),
' ')