2016-12-14 24 views
3

我有这样如何根据一列特殊的文字串的位置来获得价值

Col1     Col2 
Adam     1 
Barbara, Catherina  1 
Barbara, Catherina  2 
Adam, Catherina   1 
Adam, Catherina   2 
Barbara, Adam, Daniela 1 
Barbara, Adam, Daniela 2 
Barbara, Adam, Daniela 3 

表需要一个select语句填充COL3像图所示

Col1     Col2 Col 3 
Adam     1 Adam 
Barbara, Catherina  1 Barbara 
Barbara, Catherina  2 Catherina 
Adam, Catherina   1 Adam 
Adam, Catherina   2 Catherina 
Barbara, Adam, Daniela 1 Barbara 
Barbara, Adam, Daniela 2 Adam 
Barbara, Adam, Daniela 3 Daniela 

我尝试使用SUITTRING & CHARINDEX,但无法解决这个问题。需要一个SQL,我们可以传递Col2值并填充Col3。

请帮忙。 谢谢,Vikram

+0

您使用的是哪个版本的SQL Server? – squillman

+3

存储逗号分隔的字符串,而不是使用字段和关系是一个非常严重的设计错误。你需要修复这个bug,而不是寻找掩盖它的方法。您可以使用'STRING_SPLIT'方法作为快速修复方法,直到您修复根目录错误。 –

+1

什么是“A,B,C”值?这张桌子的目的是什么?有多种方法可以修复此设计。选择正确的取决于你想要对他们做什么。您可以使用稀疏列,即每个字母有一列,而不会浪费额外空间。您可以使用XML或JSON类型字段来存储多个值(查询速度会很慢)。如果“字母”本身就是实体,那么您应该为它们使用一个表格,另一个将它们与“数字”联系起来 –

回答

3

具有分析/拆分功能和交叉应用。

我应该补充一点,如果你不能使用或不想使用UDF,那么解析可以很容易地移植到CROSS APPLY中。

Declare @YourTable table (Col1 varchar(25),Col2 int) 
Insert Into @YourTable Values 
('A',  1), 
('B,C', 1), 
('B,C', 2), 
('A,C', 1), 
('A,C', 2), 
('B,A,D', 1), 
('B,A,D', 2), 
('B,A,D', 3) 

Select A.* 
     ,Col3 = B.RetVal 
From @YourTable A 
Cross Apply (Select * from [dbo].[udf-Str-Parse](A.Col1,',') where RetSeq=A.Col2) B 

返回

Col1 Col2 Col3 
A  1  A 
B,C  1  B 
B,C  2  C 
A,C  1  A 
A,C  2  C 
B,A,D 1  B 
B,A,D 2  A 
B,A,D 3  D 

的UDF如果需要

CREATE FUNCTION [dbo].[udf-Str-Parse] (@String varchar(max),@Delimiter varchar(10)) 
Returns Table 
As 
Return ( 
    Select RetSeq = Row_Number() over (Order By (Select null)) 
      ,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)'))) 
    From (Select x = Cast('<x>'+ Replace(@String,@Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A 
    Cross Apply x.nodes('x') AS B(i) 
); 
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',') 
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ') 

只是为了好玩,这是NON-UDF版本

Select A.* 
     ,Col3 = B.RetVal 
From @YourTable A 
Cross Apply (
       Select * From( 
        Select RetSeq = Row_Number() over (Order By (Select null)) 
          ,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)'))) 
        From (Select x = Cast('<x>'+ Replace(A.Col1,',','</x><x>')+'</x>' as xml).query('.')) as A 
        Cross Apply x.nodes('x') AS B(i) 
       ) C where RetSeq=A.Col2 
      ) B 
+0

感谢您的快速回复John。但我的帖子之前并不清楚,我只是编辑它。你可以请现在检查 – Vikram

+0

@Vikram仍然适用。 –

+0

@Vikram编辑没有改变任何东西,只是内容。你仍然需要一个函数和CROSS APPLY。并且你仍然*不应该*在字段中存储分隔值 –

-1

您可以创建一个函数,该函数获取字符串(col1)和文本索引(col2)并返回正确的值。查询应该是这样的:

SELECT col1, col2, GetElement(co1, col2) as col3 
FROM table 

GetElement可以使用WHILE循环找到该值。伪代码:

index = 1 
while index < col2 
charindex of the ',' 
if index == col2 substring to the next ',' and return 
else index = index + 1 and remove the substring from beginning to the charindex of ',' 
+0

感谢您的快速反应owczarek。但我的帖子之前并不清楚,我只是编辑它。你现在可以检查吗? – Vikram

+0

我的解决方案应该仍然有效 - 'GetElement'函数查找','字符的索引并获取子字符串。唯一缺少的是从每个迭代开始到第一个','的子串应该从'col1'的原始值中删除。 – owczarek

+0

@owczarek你还没有发布解决方案。只有建议使用'WHILE'循环。即使运行代码,这也是在T-SQL中拆分字符串的最慢方法。有很多关于如何拆分字符串的文章和重复问题。 –

1

这一个只适用于SQL 2016和Azure的SQL数据库(more on STRING_SPLIT

DECLARE @DaTable TABLE (Col1 varchar(32),Col2 int) 
INSERT INTO @DaTable 
VALUES 
('A',  1), 
('B,C', 1), 
('B,C', 2), 
('A,C', 1), 
('A,C', 2), 
('B,A,D', 1), 
('B,A,D', 2), 
('B,A,D', 3) 

SELECT S.* 
     ,Col3 = T.Value 
From @DaTable S 
Cross Apply (SELECT * FROM 
(Select Value, Row_Number() OVER (ORDER BY (SELECT 0)) AS RowNum from STRING_SPLIT(S.Col1,',')) AS ss 
where ss.RowNum = S.Col2) AS T 

注意对Azure的SQL数据库:

的STRING_SPLIT功能仅在兼容性可用级别130. [...] 请注意,即使在新的Azure SQL数据库中,兼容级别120也可能是默认级别。

+0

['STRING_SPLIT'](https://msdn.microsoft.com/en-us/library/mt684588.aspx)从SQL Server 2016开始。我提交了一个编辑到您的答案,以更正您的断言,它将在2012年和。 – SqlZim

相关问题