SQL Server:使用多个值

时间:2017-04-12 21:57:01

标签: sql-server sql-server-2008-r2 coalesce

我正在使用SQL Server 2008 R2版本。我有一个简单的表,有多列。 EmpId中列为nvarchar(50)

类型的列之一

我正在编写一个存储过程,在其中我收到一个输入,该输入可以具有以下值之一。

  1. Single EmpId:'12345'
  2. 多个EmpId的逗号分隔:'12345, 56789, 98987'
  3. null
  4. 我想要的是什么:

    1. 如果empid是单个empId,则返回

      select * 
      from table_name 
      where EmpId  = @empId
      
    2. 如果empid是多个以逗号分隔的值,则只返回

      select * 
      from table_name 
      where EmpId in (select * from dbo.splitstring(@empId))
      
    3. 如果empId为null,则返回

      Select * 
      from table_name
      

      不需要where子句。

    4. 为了涵盖这三个条件,我正在尝试:

      DECLARE @empId nvarchar(2000)
      
      SET @empId  = '97050001,97050003, 97050004'
      
      SELECT TOP 10 empId 
      FROM Employee 
      WHERE empId in (COALESCE((select * from dbo.splitstring(@empId)),[empId])) 
      

      我收到以下错误:

        

      子查询返回的值超过1。当子查询遵循=,!=,<,< =,>,> =或子查询用作表达式时,不允许这样做。

      我理解错误。 COALESCE()期待单个值,但当我得到逗号分隔值时,splitstring函数会返回多个值。

      我不想构建动态查询,所以除了使用if else块复制代码,我检查empId是否为null运行select * from table_name否则运行select * from table name where empId in ()。我有什么选择?

      要将逗号分隔的字符串拆分为表格,我正在使用此函数:

      CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
      RETURNS
           @returnList TABLE ([Name] [nvarchar] (500))
      AS
      BEGIN
          DECLARE @name NVARCHAR(255)
          DECLARE @pos INT
      
          WHILE CHARINDEX(',', @stringToSplit) > 0
          BEGIN
              SELECT @pos  = CHARINDEX(',', @stringToSplit)  
              SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)
      
              INSERT INTO @returnList 
                  SELECT @name
      
              SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
          END
      
          INSERT INTO @returnList
              SELECT @stringToSplit
          RETURN
      END
      

3 个答案:

答案 0 :(得分:2)

尝试使用更复杂的分裂字符串功能。

sp_executesql

此版本的效果会更好,因为

  1. 更高效的字符串拆分。
  2. 由于mailx因参数化执行计划缓存而导致更好的执行计划。

答案 1 :(得分:1)

取决于您的需求: -

  

我不想构建动态查询,因此除了复制代码之外   使用if else阻止我检查empId是否为null运行select * from   table_name else运行select * from table name,其中empId in()

为避免重复,请使用下一种方法: -

DECLARE @empId nvarchar(2000)
set @empId  = '97050001,97050003,97050004'
if CHARINDEX(',',@empId) > 0 -- multiple Values
begin
    set @empId = '''' +  replace (@empId,',',''',''') +  ''''
end
else if @empId is null
begin
    set @empId = 'select empId from Employee'
end

exec ('select top 10 empId from Employee where empId in (' + @empId + ')' )

这种方法处理三种情况: -

  1. 传递单个值。
  2. 传递多个价值。
  3. 传递Null

答案 2 :(得分:0)

案例1和2都可以针对您已为案例2编写的代码进行处理。您只需为案例3添加OR条件。

select * 
from table_name 
where @empId is NULL
   or EmpId in (select * from dbo.splitstring(@empId))

话虽如此,IN包含select语句的子句通常是不好的做法,原因如下

  • 当select语句返回多行
  • 时,它们比连接慢
  • 加入是更惯用的

在你的情况下,如果splitstring只返回了几行,那么它可能会带来很大的不同,但以下是这种查询的更通用的方法。

select * 
from table_name t
left join dbo.splitstring(@empId) s
    on t.EmpId = s.Name
where @empId is NULL
   or s.Name is not NULL

您可以随意检查查询和配置文件的执行计划,看看哪个更快,尽管您的初始实现应该没问题。引用Donald Knuth,"过早的优化是所有邪恶的根源。"

<强>更新

在仔细检查@empId为空且非空的情况下使用的执行计划之后,看起来上面的查询将始终使用相同的执行计划,即加入{{1的内容无论in是否为空,都可以使用子句。正如@ m-ali所指出的那样,这可能并不理想。

为确保每种情况下的正确执行计划,您可以将其分为两个查询:

@empId

我已经在SSMS中验证了正确的执行计划。

免责声明:我还没有对其进行分析,但@ m-ali建议的字符串拆分也可能更快。