语法错误:存储过程中的动态SQL

时间:2018-10-10 03:57:17

标签: sql-server stored-procedures

我对SQL Server还是很陌生,这可能是我遇到此语法错误的原因。

我的代码:

CREATE PROCEDURE spBaseVoterIndex  
    @order_col NVARCHAR(100) ,  
    @order_dir NVARCHAR(20) ,  
    @offset ITN,  
    @limit INT 
AS
    SET NOCOUNT ON;  
BEGIN
    DECLARE @sql NVARCHAR(MAX)
    SET @sql = 'SELECT id, name_voter, home_street_address_1, home_address_city FROM dbo.base_voter'  
               +' WITH(NOLOCK)'  
               +' ORDER BY @OC @OD'  
               +' OFFSET @OF ROWS'  
               +' FETCH NEXT @LIM ROWS ONLY'  

    EXECUTE sp_executesql @sql,  
               N'@OC nvarchar(191),@OD nvarchar(10),@OF int,@LIM int',  
               @OC @order_col, @OD @order_dir, @OF @offset, @LIM @limit  
END

错误:

  

消息102,级别15,状态1,过程spBaseVoterIndex,第18行[批处理开始第0行]
  '@order_col'附近的语法不正确。

我在这里做错了什么。另外,这是执行动态SQL的最佳方法还是其他优化方法?

打印@SQL会导致

SELECT id, name_voter, home_street_address_1, home_address_city 
FROM dbo.base_voter WITH(NOLOCK) 
ORDER BY @OC @OD 
OFFSET @OF ROWS 
FETCH NEXT @LIM ROWS ONLY

更新

我可以使用此代码

SELECT id,
name_voter,
home_street_address_1,
home_address_city
FROM dbo.base_voter
WITH(NOLOCK)
WHERE deleted_at IS NULL
order by name_voter asc
OFFSET 0 ROWS 
FETCH NEXT 50 ROWS ONLY 

所有我想使其动态化,其中name_voter asc和偏移值0和极限值50这是我要使其动态化的4个参数。

有1亿行数据,因此性能也很重要。

4 个答案:

答案 0 :(得分:0)

您似乎在该行中缺少@OC和@OD之间的逗号     +'ORDER BY @OC @OD'

您是否还注意到参数的大小在过程定义和动态SQL定义之间发生了变化?

您可以通过将动态SQL参数替换为实际参数,然后简单地打印最终字符串然后运行它来进行测试。

除了确保您知道使用NOLOCK可能引起的问题(脏读取,幻像读取,重复数据)外,您的用法看起来还有效。

答案 1 :(得分:0)

尝试一下。

CREATE PROCEDURE spBaseVoterIndex  
    @order_col NVARCHAR(100) ,  
    @order_dir NVARCHAR(20) ,  
    @offset INT,  
    @limit INT 
AS
    SET NOCOUNT ON;  
BEGIN
    DECLARE @sql NVARCHAR(MAX)
    SET @sql = 'SELECT id, name_voter, home_street_address_1, home_address_city FROM dbo.base_voter'  
               +' WITH(NOLOCK)'  
               +' ORDER BY @OC @OD'  
               +' OFFSET @OF ROWS'  
               +' FETCH NEXT @LIM ROWS ONLY'  

    EXECUTE sp_executesql @sql,  
               N'@OC nvarchar(100),@OD nvarchar(20),@OF int,@LIM int',  
               @OC=@order_col, @OD=@order_dir, @OF=@offset, @LIM=@limit  
END

答案 2 :(得分:0)

您需要将参数分配给sp_executesql的变量。 (您已经声明了它,但未指定它(忘记使用等号)。

CREATE PROCEDURE spBaseVoterIndex  
    @order_col NVARCHAR(191) ,  
    @order_dir NVARCHAR(10) ,  
    @offset INT,  
    @limit INT 
AS
    SET NOCOUNT ON;  
BEGIN
    DECLARE @sql NVARCHAR(MAX)

    SET @sql = 'SELECT id, name_voter, home_street_address_1, home_address_city FROM dbo.base_voter'  
            +' WITH(NOLOCK)'  
            +' ORDER BY @order_col @order_dir'  
            +' OFFSET @offset ROWS'  
            +' FETCH NEXT @limit ROWS ONLY' 

    SET @sql = REPLACE(@sql,'@order_col',@order_col)
    SET @sql = REPLACE(@sql,'@order_dir',@order_dir)
    SET @sql = REPLACE(@sql,'@offset',@offset)
    SET @sql = REPLACE(@sql,'@limit',@limit)

    EXECUTE sp_executesql @sql

END

BTW在非动态SQL中,OFFSETFETCH NEXT接受INT变量。因此,您可以例如直接使用它:

SELECT 
    id
,   name_voter
,   home_street_address_1
,   home_address_city 
FROM 
    dbo.base_voter
ORDER BY id
OFFSET @offset ROWS
FETCH NEXT @limit ROWS ONLY

但是,这将迫使您使用固定顺序(顺便说一句,这不是真正的问题)。如果需要使用order by,可以使用ROW_NUMBER方法,如下所示:

SELECT * FROM (
SELECT 
    id
,   name_voter
,   home_street_address_1
,   home_address_city 
,   ROW_NUMBER() OVER(ORDER BY @order_col + @order_dir) RN
FROM 
    base_voter
) D 
WHERE
    RN > @Offset
AND RN <= @Limit + @Offset

这将为您提供OFFSETFETCH NEXT的相同结果

希望有一天会有所帮助。

答案 3 :(得分:0)

这种方式行不通-您无法像这样对ASC进行参数设置。您需要从字面上串联字符串。

添加一些字符串卫生措施以停止SQL注入是一个好主意

CREATE PROCEDURE spBaseVoterIndex  
    @order_col NVARCHAR(100) ,  
    @descending INT,  
    @offset INT,  
    @limit INT 
AS
    SET NOCOUNT ON;  
BEGIN

   DECLARE @sql NVARCHAR(MAX)
   DECLARE @OD varchar(4) = 'ASC'

   -- Default is ascending. Pass in 1 to order descending
   IF @descending = 1 THEN SET @OD='DESC'

   -- If the order column is not valid, exit
   -- You need to put a list of valid columns in here
   IF @order_col NOT IN ('column1','column2','column3') RETURN


   -- Finally build and run the SQL
    SET @sql = 'SELECT id, name_voter, home_street_address_1, home_address_city'
               +' FROM dbo.base_voter'  
               +' WITH(NOLOCK)'  
               +' ORDER BY ' + @order_col + ' ' + @OD + ' '
               +' OFFSET ' + CAST(@offset AS VARCHAR(20)) + ' ROWS'  
               +' FETCH NEXT ' + CAST(@limit AS VARCHAR(20)) + ' ROWS ONLY'  

EXEC(@sql)

END

关于性能,这包括两个部分:

  1. 确保索引正确
  2. 这种动态的“厨房接收器” SQL总是会遇到性能问题,因为您无法预先编译查询计划。但是,它永远不会遭受参数嗅探的困扰。