拆分逗号分隔值并对值执行查询

时间:2017-07-19 12:47:36

标签: sql tsql sql-server-2012

我有两个表affectedservicecall受影响的服务是这样构建的;

ID    Name
1     Area 1
2     Area 2

并且打电话就像这样

ID    AffectedService
100   1
101   1,2
102   2

如何从通话ID中选择受影响的服务名称?

我试过

select Aff_Serv_Name 
from AffectedService 
where Aff_Serv_ID in (
  select Spec_Area_ID 
  from call 
  where call_id = 100)

但是出现了这个错误

Conversion failed when converting the nvarchar value '1,2' to data type int.

The AffectedService'列的范围可以是1到4个数字

修改 我知道将CSV数据存储在列中是非常糟糕的,但不幸的是它不是我的数据库,并且数据库大约有20年的历史,所以它无法更改 SQL Server 2012数据库

在sql fiddle http://sqlfiddle.com/#!6/d548e

上构建架构

3 个答案:

答案 0 :(得分:1)

动态SQL可能是最好的方法,任何字符串都可以像执行SQL代码一样执行,这使您可以将SQL命令创建为字符串,这非常强大。我在这里用过这个

DECLARE @Call_ID INT = 100
DECLARE @IdList VARCHAR(100)
DECLARE @SQLQuery AS NVARCHAR(500)

SET @IdList = (SELECT Spec_Area_ID
               FROM Call
               WHERE ID = @Call_ID)

SET @SQLQuery = 'SELECT STUFF((SELECT '', '' + Aff_Serv_Name 
                               FROM AffectedService 
                               WHERE Aff_Serv_ID IN (' + @IdList + ')
                               FOR XML PATH('''')), 1, 1, '''') [Specific_Area]'

EXECUTE(@SQLQuery)

以此方式填充字符串变量(实际上是您的服务列表),然后将其放入SELECT...WHERE...IN()中,您已经证明自己在问题中知道。我还通过使用FOR XML PATH()函数使其返回到适合您问题的CSV列表,允许您将所有返回值放在同一行上,并用逗号分隔每个值,但是在第一个值之前放置一个逗号,我使用stuff函数将其清除。

答案 1 :(得分:0)

嗯,你不能使用IN,因为IN需要一个值列表,而你实际上提供的是一个值(包含一个列表)。

由于您在评论中写道无法更改数据库结构,因此您可以使用string splitting user defined function,也可以使用LIKE

select Aff_Serv_Name 
from AffectedService 
inner join call on '%,'+ CAST(Aff_Serv_ID as varchar(10)) +',%' LIKE ','+ Spec_Area_ID +','
where call_id = 100 

我已将您的查询更改为内部联接,因为它更具可读性 - 当然您也可以使用子查询版本:

select Aff_Serv_Name 
from AffectedService 
where '%,'+ CAST(Aff_Serv_ID as varchar(10)) +',%' LIKE ','+ 
(
  select Spec_Area_ID 
  from call 
  where call_id = 100
) +','

答案 2 :(得分:0)

您还可以拆分和透视数据,使其更加可行,并以更正常的方式显示。关系模型。不是很简洁:

IF OBJECT_ID('dbo.mytableService', 'U') IS NOT NULL 
  DROP TABLE dbo.mytableService; 

IF OBJECT_ID('dbo.mytableCall', 'U') IS NOT NULL 
  DROP TABLE dbo.mytableCall; 

CREATE TABLE mytableService(ID INT    ,AffectedService VARCHAR (100))
CREATE TABLE mytableCall(ID INT, Name VARCHAR (100))

--test data
INSERT INTO mytableService (ID, AffectedService)
VALUES (100 ,  '1'),
(101   ,'1,2'),
(102   ,'2')

INSERT INTO mytableCall (ID, Name)
VALUES
(1,'Area 1'),
(2,'Area 2'),
(3,'Area 3')

--table for split and pivot
DECLARE @table table ( ID INT,Service VARCHAR(200))
INSERT INTO @table(ID,Service) SELECT ID,AffectedService from mytableService

 ;WITH Split_Names (ID,Service, xmlService)
 AS
 (
        SELECT 
        ID,
        Service,
        CONVERT(XML,'<Names><name>'  
        + REPLACE(Service,',', '</name><name>') + '</name></Names>') AS xmlService
          FROM @table
 )

 ,Results
 AS
 ( SELECT  
 ID,  
 xmlService.value('/Names[1]/name[1]','varchar(100)') AS Service1    
  ,xmlService.value('/Names[1]/name[2]','varchar(100)') AS Service2
 ,xmlService.value('/Names[1]/name[3]','varchar(100)') AS Service3
 ,xmlService.value('/Names[1]/name[4]','varchar(100)') AS Service4
 FROM Split_Names)
 ,
 Atomic_Services
 AS
 (
  SELECT ROW_NUMBER() OVER(ORDER BY ID) AS RN, ID,Services  FROM Results
  UNPIVOT
                     (Services FOR Service IN 
                           (Service1,Service2,Service3,Service4 )
                     )AS unpvt 
)


--join
 SELECT * 
 FROM Atomic_Services cte 
 JOIN mytableCall area
 ON cte.Services = area.ID