将分号分隔值与SQL中的百分比拆分为新行

时间:2017-02-12 13:02:47

标签: sql sql-server

我在MS-SQL(使用SQL Server 2014 Standard)中有一个数据表,其中包含以下说明性布局(遗憾的是无法更改):

<div ng-form="parentForm">

    <div ng-repeat="item in items" ng-form="itemForm">
         <input type="text" ng-model="item.name" required/> 
    </div>

    <input type="submit" ng-disabled="parentForm.$invalid" />
</div>

我需要使用以下输出创建查询:

ID   Score   Segmentation
 1 |  500  | GROUP 1 - SET A - 50%; GROUP 2 - 25%; GROUP 1 - SET G - 25%
 2 |  200  | GROUP 1 - SET B - 25%; GROUP 5 - SET A - SET B - 50%; GROUP 6 - 25%

也就是说,结果集应该显示每个唯一的组,总计得分,由组在每一行中的总百分比构建(即组1总分为500 * 0.5 + 500 * 0.25 + 200 * .25 = 425)。数据上的参数是;

  • 每行中的所有组总是加起来为100%
  • 没有静态的群组列表
  • 每组可以多列一次
  • 在使用过程中可能会添加新组
  • 组可能有多个集合,但不需要在结果中考虑
  • 群组名称可能包含空格和&#39;&amp;&#39;字符

我之前在https://blog.sqlauthority.com/2015/04/21/sql-server-split-comma-separated-list-without-using-a-function/发现了一个解决方案,但它似乎有所突破,并且不喜欢&#39;&amp;&#39;字符。

我很感激有关如何解决此问题的任何想法。

谢谢!

4 个答案:

答案 0 :(得分:1)

在SQL Server中基本上有三种方法:

  • 用户定义的功能。
  • 递归CTE。
  • XML处理。

专业字符可能会影响XML处理,但它们应该适用于前两个。

例如:

with cte as (
      select id, score,
             left(segmentation, charindex(';', segmentation + ';') - 1) as segment,
             substring(segmentation, charindex(';', segmentation + ';') + 1, len(segmentation)) + ';' as rest
      from t
      union all
      select id, score,
             left(rest, charindex(';', rest + ';') - 1) as segment,
             substring(rest, charindex(';', rest) + 1, len(rest))
      from cte
      where rest like '%;'
     )
select left(segment, charindex(' - ', segment)) as segmentation,
       sum(score * cast(replace(right(segment, charindex(' ', reverse(segment, ' ')) - 1), '%', '') as float) / 100.0) as TotalScore
from cte
group by left(segment, charindex(' - ', segment));

这样做了很多奇怪的字符串处理,因为数据结构很糟糕。我鼓励你努力修复数据结构,以便数据更有用。

答案 1 :(得分:1)

SQL Fiddle

MS SQL Server 2014架构设置

CREATE TABLE t(ID INT ,   Score INT ,   Segmentation VARCHAR(1000))
INSERT INTO t VALUES
 (1 ,  500  , 'GROUP 1 - SET A - 50%; GROUP 2 - 25%; GROUP 1 - SET G - 25%'),
 (2 ,  200  , 'GROUP 1 - SET B - 25%; GROUP 5 - SET A - SET B - 50%; GROUP 6 - 25%')

查询1

SELECT Groups 
      ,SUM(CAST( Score * REPLACE(Percentage , '%','') /100.0 AS decimal(18,2))) Total_Score
FROM 

(
    SELECT  ID
           ,Score 
           ,LEFT(RTRIM(LTRIM(Split.a.value('.', 'VARCHAR(100)'))), 8) Groups
           ,RIGHT(RTRIM(LTRIM(Split.a.value('.', 'VARCHAR(100)'))),4) Percentage
    FROM   
    (SELECT    ID
             , Score
             , Cast ('<X>' + Replace(Segmentation, ';', '</X><X>') + '</X>' AS XML) AS Data
    FROM    t
    ) AS t CROSS APPLY Data.nodes ('/X') AS Split(a) 
) q
GROUP BY Groups

<强> Results

|   Groups | Total_Score |
|----------|-------------|
| GROUP 1  |         425 |
| GROUP 2  |         125 |
| GROUP 5  |         100 |
| GROUP 6  |          50 |

答案 2 :(得分:0)

使用CROSS APPLY的另一个选项。我应该注意,这个拆分/解析是XML安全的。

我不知道第3组来自哪里(也许是一个错字?)

Declare @YourTable table (ID int,Score int,Segmentation varchar(100))
Insert Into @YourTable values
(1 ,  500  , 'GROUP 1 - SET A - 50%; GROUP 2 - 25%; GROUP 1 - SET G - 25%'),
(2 ,  200  , 'GROUP 1 - SET B - 25%; GROUP 5 - SET A - SET B - 50%; GROUP 6 - 25%')

Select B.Segmentation
      ,Total_Score    = sum((A.Score*B.Value)/100)
 From  @YourTable A
 Cross Apply (
                Select Segmentation = left(RetVal,charindex(' - ',RetVal))
                      ,Value        = cast(replace(reverse(left(reverse(RetVal),charindex(' - ',reverse(RetVal)))),'%','') as float)
                 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((Select replace(A.Segmentation,';','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A 
                        Cross Apply x.nodes('x') AS B(i)
                       ) C1
             ) B
 Group By B.Segmentation

返回

Segmentation    Total_Score
GROUP 1         425
GROUP 2         125
GROUP 5         100
GROUP 6         50

答案 3 :(得分:0)

您可以尝试遵循基于XML / XQuery的解决方案:

DECLARE @Table1 TABLE (
    ID INT, 
    Score INT, 
    Segmentation VARCHAR(1000)
)
INSERT INTO @Table1
VALUES
 (1 ,  500  , 'GROUP 1 - SET A - 50%; GROUP 2 - 25%; GROUP 1 - SET G - 25%'),
 (2 ,  200  , 'GROUP 1 - SET B - 25%; GROUP 5 - SET A - SET B - 50%; GROUP 6 - 25%')

 SELECT t.GroupName, TotalScore = SUM((t.Score * t.GroupPercent) / 100.00)
 FROM (
     SELECT Score       = y.Score,
            GroupName   = LTRIM(z.XmlCol.value('(item/text())[1]', 'VARCHAR(100)')),
            GroupPercent= TRY_CONVERT(NUMERIC(5, 2), REPLACE(LTRIM(z.XmlCol.value('(item/text())[last()]', 'VARCHAR(100)')), '%', ''))
     FROM (
     SELECT *, SegmentationAsXML = CONVERT(XML, '<row><group><item>' + REPLACE(REPLACE(x.Segmentation, ';', '</item></group><group><item>'), '-', '</item><item>') + '</item></group></row>') 
     FROM   @Table1 x
     ) y
     OUTER APPLY y.SegmentationAsXML.nodes('row/group') z(XmlCol)
 ) t
 GROUP BY t.GroupName

注意#1:我假设源字符串(分段不包含XML保留字符)。

如果源字符串包含XML保留字符,那么您可以测试以下函数:

REPLACE(x.Segmentation, ';',

- &GT;

REPLACE( (SELECT x.Segmentation AS '*' FOR XML PATH('')), '; '

注意#2:它将Segmentation(s)字符串转换为XML,因此: 'GROUP 1 - SET A - 50%; GROUP 2 - 25%'变成了 '<row><group><item>GROUP 1</item><item>SET A</item><item>50%</item></group><group><item>GROUP 2</item><item>25%</item>'

注意#3:它使用last() XQuery函数来访问每个item内的最后group