【问题标题】:Separating Text that has spaces into separate columns将具有空格的文本分隔为单独的列
【发布时间】:2023-03-19 20:40:01
【问题描述】:

我有一个如下所示的 Textcol:

文本示例:

Note:test note
Phone Call: Fairview Wines & Spirits
Number Called: 1 604  601-8000
Phone Description: Main
Subject: Arrange meeting
Result: Arranged interview
Duration: 00:00:09

如果可能的话,我想将主题、结果和注释分成四个单独的列。我尝试了两种方法:

第一个例子:

SELECT "Note"=SUBSTRING(TextCol,1,25),"Subject"=SUBSTRING(TextCol,110,25)
 ,"Result"=SUBSTRING(TextCol,135,33) FROM AMGR_Notes where type = 2

第二个例子:

 SELECT
    RTRIM(LTRIM(REPLACE(REPLACE(SUBSTRING(T.TextCol, A.SubjectStart + 8, A.ResultStart - A.SubjectStart - 8), CHAR(10), ''), CHAR(13), ''))) AS [Subject]
    ,RTRIM(LTRIM(REPLACE(REPLACE(SUBSTRING(T.TextCol, A.ResultStart + 7, A.DurationStart - A.ResultStart - 7), CHAR(10), ''), CHAR(13), ''))) AS Result
    ,RTRIM(LTRIM(REPLACE(REPLACE(SUBSTRING(T.TextCol, 6, A.PhoneStart - 6), CHAR(10), ''), CHAR(13), ''))) AS Note
FROM AMGR_Notes T
    CROSS APPLY
    (
        VALUES
        (
            CHARINDEX('Phone Call:', T.TextCol)
            ,CHARINDEX('Subject:', T.TextCol)
            ,CHARINDEX('Result:', T.TextCol)
            ,CHARINDEX('Duration:', T.TextCol)

        )
    ) A (PhoneStart, SubjectStart, ResultStart, DurationStart);

第二个例子报错:

消息 537,级别 16,状态 3,行 1 传递给的长度参数无效 LEFT 或 SUBSTRING 函数。

当谈到 SQL 时,我不是一个超级天才,所以我不明白这个错误。 我也想知道 CASE 语句在这种情况下是否会更好

任何帮助将不胜感激

【问题讨论】:

  • 请标记您正在使用的正确数据库,我假设它是 T-sql
  • 你能提供预期的输出吗?
  • 很简单,不是吗?运行select,它会显示所有字符串、它们的len()s,以及您尝试在substring() 中使用的索引。现在仔细看看它们。结果可能令人惊讶!
  • 如果您将数据转换为可用的形式,查询会容易得多。您可以将数据转换为 XML 或 JSON,而不是原始文本。 SQL Server 自 2005 年起支持 XML,自 2016 年起支持 JSON。或者您可以添加计算列,根据您已经使用的公式返回各个字段。您可以通过将 : 替换为 " : ",将换行符替换为 ", 并将结果包装在 { } 中,将现有数据转换为 JSON。
  • 预期输出为:对于列:注意 |结果 |主题 |持续时间测试说明 |安排面试 |安排会议 | 00:00:09

标签: sql sql-server string tsql split


【解决方案1】:

用下面的case语句弄清楚了:

SELECT
CASE
    WHEN A.ResultStart - A.SubjectStart >= 8
    THEN RTRIM(LTRIM(REPLACE(REPLACE(SUBSTRING(T.TextCol, A.SubjectStart + 8, A.ResultStart - A.SubjectStart - 8), CHAR(10), ''), CHAR(13), '')))
    ELSE ''
END
AS [Subject],
CASE
    WHEN A.DurationStart - A.ResultStart >= 7
    THEN RTRIM(LTRIM(REPLACE(REPLACE(SUBSTRING(T.TextCol, A.ResultStart + 7, A.DurationStart - A.ResultStart - 7), CHAR(10), ''), CHAR(13), '')))
    ELSE ''
END
AS Result,
CASE
    WHEN A.DurationStart - A.ResultStart >= 7
    THEN RTRIM(LTRIM(REPLACE(REPLACE(SUBSTRING(T.TextCol, A.DurationStart + 9, A.DurationStart - A.ResultStart - 9), CHAR(10), ''), CHAR(13), '')))
    ELSE ''
END
AS Duration,
CASE
    WHEN A.PhoneStart >= 6
    THEN RTRIM(LTRIM(REPLACE(REPLACE(SUBSTRING(T.TextCol, 6, A.PhoneStart
- 6), CHAR(10), ''), CHAR(13), '')))
    ELSE ''
END
AS Note
FROM AMGR_Notes T
CROSS APPLY (
    VALUES
    (
    CHARINDEX('Phone Call:', T.TextCol)
    ,CHARINDEX('Subject:', T.TextCol)
    ,CHARINDEX('Result:', T.TextCol)
    ,CHARINDEX('Duration:', T.TextCol)
    )
) A (PhoneStart, SubjectStart, ResultStart, DurationStart)


where t.type = '2'

【讨论】:

  • 很高兴您找到了自己的解决方案。但请注意,如果输入并不总是按预期顺序(或其他元素介于两者之间),这将中断。我的回答被否决了,不知道为什么,但它更宽容......
【解决方案2】:

我认为这可以通过使用 CROSS APPLY 来超级干净和高效地完成。这是处理单个字符串的一些即时内联函数逻辑(请原谅我的命名 - 我很着急):

DECLARE @string varchar(8000) = 
'Phone Call: Fairview Wines & Spirits
Number Called: 1 604  601-8000
Phone Description: Main
Subject: Arrange meeting
Result: Arranged interview
Duration: 00:00:09';

SELECT  
  [Phone Call] = MAX(CASE s.n WHEN 1 THEN here.yougo END),
  [Subject]    = MAX(CASE s.n WHEN 2 THEN here.yougo END),
  [Result]     = MAX(CASE s.n WHEN 3 THEN here.yougo END),
  [Duration]   = MAX(CASE s.n WHEN 4 THEN here.yougo END)
FROM (VALUES (@string)) t(s) -- target.string
CROSS APPLY (VALUES (1,'Phone Call:'),(2,'Subject:'),(3,'Result:'),(4,'Duration:')) s(n,t) -- search.text
CROSS APPLY (VALUES (CHARINDEX(s.t, t.s), LEN(s.t))) st(l,ln)                              -- start.location, start.length
CROSS APPLY (VALUES (ISNULL(NULLIF(CHARINDEX(char(10), t.s, st.l+st.ln+1),0),8000))) e(ln) -- end line
CROSS APPLY (VALUES (SUBSTRING(t.s, st.l+st.ln, e.ln-(st.l+st.ln)))) here(yougo);

这会返回:

Phone Call                 Subject          Result              Duration 
-------------------------- ---------------- ------------------- ---------
Fairview Wines & Spirits   Arrange meeting  Arranged interview  00:00:09

执行计划非常棒 - 总子树成本为 0.0000071(在我的 PC 上)。要将此逻辑应用于表,您可以这样做:

DECLARE @AMGR_Notes TABLE (someid int identity primary key, somestring varchar(8000));
INSERT @AMGR_Notes VALUES 
('Phone Call: ACME Treats
Number Called: 1 604 555-9988
Phone Description: Old School Landline
Subject: Buy Ice Cream
Result: He sold me some
Duration: 00:00:01'),
('Phone Call: Fairview Wines & Spirits
Number Called: 1 604  601-8000
Phone Description: Main
Subject: Arrange meeting
Result: Arranged interview
Duration: 00:00:09');

SELECT 
  t.someid,
  [Phone Call] = MAX(CASE s.n WHEN 1 THEN here.yougo END),
  [Subject]    = MAX(CASE s.n WHEN 2 THEN here.yougo END),
  [Result]     = MAX(CASE s.n WHEN 3 THEN here.yougo END),
  [Duration]   = MAX(CASE s.n WHEN 4 THEN here.yougo END)
FROM @AMGR_Notes tt
CROSS APPLY (VALUES (tt.someid,tt.somestring)) t(someid,s) -- target.string
CROSS APPLY (VALUES (1,'Phone Call:'),(2,'Subject:'),(3,'Result:'),(4,'Duration:')) s(n,t) -- search.text
CROSS APPLY (VALUES (CHARINDEX(s.t, t.s), LEN(s.t))) st(l,ln)                              -- start.location, start.length
CROSS APPLY (VALUES (ISNULL(NULLIF(CHARINDEX(char(10), t.s, st.l+st.ln+1),0),8000))) e(ln) -- end line
CROSS APPLY (VALUES (SUBSTRING(t.s, st.l+st.ln, e.ln-(st.l+st.ln)))) here(yougo)
GROUP BY t.someid;

MAX-CASE 逻辑是另一种旋转方式,但 IMO 更简洁、更容易(感谢 Jeff Moden 教我这项技术。)这里性能的关键(也适用于使用旋转)是确保有您分组的列上的索引,在我的示例中,它是 someid 列。

【讨论】:

  • 这是一个非常好的和聪明的方法!
  • 顺便说一句:这第一个CROSS APPLY 只是使用t.st.someid 而不是tt.somestringtt.someid?我很确定这不是必需的......
  • 用我自己的方法测试过,你的更快(大约 x4)
【解决方案3】:

试试这样:

DECLARE @tbl TABLE(ID INT IDENTITY, TextCol VARCHAR(500));
INSERT INTO @tbl VALUES
 ('Phone Call: Fairview Wines & Spirits
    Number Called: 1 604  601-8000
    Phone Description: Main
    Subject: Arrange meeting
    Result: Arranged interview
    Duration: 00:00:09
');

WITH RowWise AS
(
    SELECT CAST('<x>' + REPLACE((SELECT REPLACE(TextCol,CHAR(13),'') AS [*] FOR XML PATH('')),CHAR(10),'</x><x>')  + '</x>' AS XML) AS OneRow
    FROM @tbl 
)
,EachRow AS
(
    SELECT LTRIM(RTRIM(r.value('text()[1]','nvarchar(max)'))) AS RowText
    FROM RowWise
    CROSS APPLY OneRow.nodes('/x[text()]') AS A(r)
)
SELECT LEFT(RowText,CHARINDEX(':',RowText)-1)
      ,SUBSTRING(RowText,CHARINDEX(':',RowText)+1,1000)
FROM EachRow;

结果

Phone Call           Fairview Wines & Spirits
Number Called        1 604  601-8000
Phone Description    Main
Subject              Arrange meeting
Result               Arranged interview
Duration             00:00:09

更新

添加了PIVOT 以便在一行中得到这个

WITH PlainLineBreak AS
(
    SELECT ID
          ,REPLACE(TextCol,CHAR(13),CHAR(10)) AS TextCol
    FROM @tbl
)
,LineWise AS
(
    SELECT CAST('<x>' + REPLACE((SELECT TextCol AS [*] FOR XML PATH('')),CHAR(10),'</x><x>')  + '</x>' AS XML) AS OneLine
    FROM PlainLineBreak 
)
,EachLine AS
(
    SELECT LTRIM(RTRIM(r.value('text()[1]','nvarchar(max)'))) AS LineText
    FROM LineWise
    CROSS APPLY OneLine.nodes('/x[text()]') AS A(r)
)
SELECT p.*
FROM
(
    SELECT LEFT(LineText,CHARINDEX(':',LineText)-1) AS ColumnName
          ,SUBSTRING(LineText,CHARINDEX(':',LineText)+1,1000) AS ColumnValue
    FROM EachLine
) AS t
PIVOT
(
    MAX(ColumnValue) FOR ColumnName IN([Note],[Phone Call],[Number Called],[Phone Description],[Subject],[Result],[Duration])
) As p

结果

+-----------+--------------------------+-----------------+-------------------+-----------------+--------------------+----------+
| Note      | Phone Call               | Number Called   | Phone Description | Subject         | Result             | Duration |
+-----------+--------------------------+-----------------+-------------------+-----------------+--------------------+----------+
| test note | Fairview Wines & Spirits | 1 604  601-8000 | Main              | Arrange meeting | Arranged interview | 00:00:09 |
+-----------+--------------------------+-----------------+-------------------+-----------------+--------------------+----------+

【讨论】:

  • 您好,downvoter,介意解释一下吗?没有理由的投反对票是 - 嗯 - 懦弱......
【解决方案4】:

最简单的方法可能是:

FROM AMGR_Notes T CROSS APPLY
     (VALUES (NULLIF(CHARINDEX('Phone Call:', T.TextCol), 0)
              NULLIF(CHARINDEX('Subject:', T.TextCol), 0),
              NULLIF(CHARINDEX('Result:', T.TextCol), 0),
              NULLIF(CHARINDEX('Duration:', T.TextCol), 0)
             )
     ) A(PhoneStart, SubjectStart, ResultStart, DurationStart);

【讨论】:

  • 谢谢 - 我试过了,我得到了错误:Msg 1011, Level 16, State 1, Line 8 在 FROM 子句中多次指定了相关名称“T”。
  • @JR83 。 . .这是 your FROM 子句。除了A 的内容,我没有更改任何内容。
  • 这只会返回每个单词的位置,并没有真正让 OP 更接近解决方案。
  • @AlanBurstein 。 . .不,这会返回 NULL 而不是无效的字符位置,因此查询不会返回任何错误。
  • 我的错。我错误地认为这是一个完整的解决方案。抱歉,戈登(我刚刚阅读了您的数据挖掘技术 - 第 3 版,非常好)
猜你喜欢
  • 2017-07-30
  • 1970-01-01
  • 1970-01-01
  • 2018-11-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多