用于提取列名的SQL Server字符串操作 - 循环

时间:2017-02-23 10:21:58

标签: string loops tsql

我希望对T-SQL编码提出一些帮助。我有一个列有条件列表的列(WHERE语句)。我需要使用别名提取不同的列名列表。

示例数据: ISNULL(ABC.Premium,0)< 0,ISNULL(ABC.Date,101)< 19

结果: ABC.Premium,ABC.Date

我希望做一个字符串操作: 1.搜索'。' 2.字符串操作以提取名称。

我不知道如何让LOOP搜索多个'。'并提取列名称。

CREATE DATABASE TEST
GO
    USE [Test]
    GO

    CREATE TABLE [dbo].[StringRetrival](
        [ID] [int] NOT NULL,
        [Condition] [varchar](4000) NULL
    ) ON [PRIMARY]

    GO

    INSERT [dbo].[StringRetrival] ([ID], [Condition]) VALUES (1, N'ISNULL(ABC.Premium, 0) < 0,ISNULL(ABC.Date, 101) < 19')
    GO
    INSERT [dbo].[StringRetrival] ([ID], [Condition]) VALUES (2, N'ISNULL(DEF.ColB, 101) < 25,ISNULL(DEF.ColB, 101) < 25,ISNULL(XYZ.ColB, 101) > 5, MSN.ColA < 5')
    GO
    INSERT [dbo].[StringRetrival] ([ID], [Condition]) VALUES (3, N'RTY.ColA')
    GO

感谢您对此的帮助。

谢谢

2 个答案:

答案 0 :(得分:2)

以下代码

  • 分割点上的字符串
  • LEFTRIGHT剪切,直到第一个非简单字符(PATINDEX('%[^a-z,A-Z,0-9]%'
  • 将每个部分的开头(列名)与上一部分的结尾(表名)连接起来

试试这个

WITH Splitted AS
(
    SELECT ID
          ,CAST('<x>' + REPLACE((SELECT Condition AS [*] FOR XML PATH('')),'.','</x><x>') + '</x>' AS XML) AS Part
    FROM StringRetrival
)
,AllParts AS
(
    SELECT ID
          ,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT NULL)) AS Nr
          ,p.value('.','nvarchar(max)') AS OnePart
    FROM Splitted
    CROSS APPLY Part.nodes('x') AS A(p)
)
,Parsed AS
(
    SELECT ROW_NUMBER() OVER(ORDER BY ID,Nr) AS SortInx,*
    FROM
    (
        SELECT ID,Nr,OnePart,NULL AS ColumnName
                ,RIGHT(OnePart,CASE WHEN Position.BreakingChar<1 THEN 999 ELSE Position.BreakingChar END) AS TableName 
        FROM AllParts 
        CROSS APPLY(SELECT PATINDEX('%[^a-z,A-Z,0-9]%',REVERSE(OnePart))-1 AS BreakingChar) AS Position
        WHERE Nr=1
        UNION ALL 
        SELECT ID,Nr,OnePart
                ,LEFT(OnePart,CASE WHEN Position.FirstBreakingChar<1 THEN 999 ELSE Position.FirstBreakingChar END)  
                ,RIGHT(OnePart,CASE WHEN Position.SecondBreakingChar<1 THEN 999 ELSE Position.SecondBreakingChar END) 
        FROM AllParts
        CROSS APPLY(SELECT PATINDEX('%[^a-z,A-Z,0-9]%',OnePart)-2 AS FirstBreakingChar
                            ,PATINDEX('%[^a-z,A-Z,0-9]%',REVERSE(OnePart))-1 AS SecondBreakingChar) AS Position
        WHERE Nr>1
    ) AS tbl
)
SELECT DISTINCT
        p1.ID
        ,ISNULL(p2.TableName + '.','') + p1.ColumnName
FROM Parsed AS p1
INNER JOIN Parsed AS p2 ON p1.ID=p2.ID AND p2.Nr=p1.Nr-1;

结果

ID  ColumnName
1   ABC.Date
1   ABC.Premium
2   DEF.ColB
2   MSN.Col
2   XYZ.ColB
3   RTY.ColA

答案 1 :(得分:2)

好的,这是 HIGHLY ,不适合在任何类型的生产环境中使用,但这是一个有趣的小挑战,适用于您的测试数据。我强烈建议您只需查找整个问题的替代解决方案,这会导致您在数据库表中保留where子句。

这对于在文本字符串中包含where个字符的.子句不起作用,也不会依赖于将字符串拆分为.个字符的任何其他解决方案,而不需要花费太多精力检查该字符是字符串值的一部分。

利用Jeff Moden's string splitting function,您可以执行以下操作:

declare @StringRetrival table(ID int,Condition varchar(4000));
insert into @StringRetrival(ID,Condition) values
 (1,N'ISNULL(ABC.Premium, 0) < 0,ISNULL(ABC.Date, 101) < 19')
,(2,N'ISNULL(DEF.ColB, 101) < 25,ISNULL(DEF.ColB, 101) < 25,ISNULL(XYZ.ColB, 101) > 5, MSN.ColA < 5')
,(3,N'RTY.ColA');

with s1 as
(
    select r.ID
            ,r.Condition
            ,s.ItemNumber
            ,max(s.ItemNumber) over (partition by r.ID) as MaxItemNumber
            ,reverse(s.Item) as Item
    from @StringRetrival r
        cross apply dbo.DelimitedSplit8K(r.Condition,'.') s
),s2 as
(
    select r.ID
            ,r.Condition
            ,s.ItemNumber
            ,max(s.ItemNumber) over (partition by r.ID) as MaxItemNumber
            ,reverse(s.Item) as Item
    from @StringRetrival r
        cross apply dbo.DelimitedSplit8K(reverse(r.Condition),'.') s
)
select distinct s1.ID
                ,reverse(left(s1.Item,patindex('%[^a-zA-Z]%',s1.Item + ',')-1)) + '.' + left(s2.Item,patindex('%[^a-zA-Z]%',s2.Item + ',')-1) as Col
from s1
    join s2
        on s1.ID = s2.ID
            and s1.ItemNumber = s2.ItemNumber
where s1.ItemNumber <> s1.MaxItemNumber
order by s1.ID;

将输出:

+----+-------------+
| ID |     Col     |
+----+-------------+
|  1 | ABC.Date    |
|  1 | ABC.Premium |
|  2 | DEF.ColA    |
|  2 | DEF.ColB    |
|  2 | MSN.ColB    |
|  2 | XYZ.ColB    |
|  3 | RTY.ColA    |
+----+-------------+

用于创建拆分功能的SQL:

CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l

GO