【问题标题】:How to parse values from a sql column in a loop using a stored procedure如何使用存储过程在循环中解析 sql 列中的值
【发布时间】:2017-03-01 06:47:27
【问题描述】:

我有多个结构相同的表,每个表代表不同的客户。在这些表中的每一个中,都有多个包含各种数据字符串的列,其中值由空格分隔,如下所示:

Domain_Name | Account_ID | SiteID | Account_Name | BotExceptions | BlockCountries | BlockedURLs |BlockedIPs
www.Domain1.com | 123456 |987654  | account1 | TR Sites24x7 Tools/ | UK FR IR| www.url1.com/someRsc www.url2.com/someRsc|46.218.22.145 64.129.104.70 92.44.168.111
www.Domain2.com | 345678|321098| account2 | TR Sites24x7 Tools/ | ES GR AX BE| www.url1.com/someRsc www.url2.com/someRsc|46.218.22.145 64.129.104.70 92.44.168.111

因此,在每一列中,所有项目(IP/机器人名称/国家/地区/URL)都连接到由单个空格分隔的单个字符串。例如,“TR Sites24x7 Tools/”实际上是“TR”、“Sites24x7”和“Tools/”。我正在寻找一种方法来构建存储过程并使用 sql / Tsql,解析每列中的字符串并将项目放入类似的行中:

Domain_Name | Account_ID | SiteID | Account_Name | BotExceptions | BlockCountries | BlockedURLs |BlockedIPs
www.Domain1.com| 123456  | 987654 | account1     | TR            | UK             | www.url1.com/someRsc       | 46.218.22.145
www.Domain1.com| 123456  | 987654 | account1     | Sites24x7     | FR             | www.url2.com/someRsc       | 64.129.104.70

换句话说,将每个值单独放置在一行中,同时在整个表中保持前 4 列不变。由于数据已经在 sql server 2012 DB 的表中,我认为拥有一个可以进行这种解析的 SP 或函数会更简单,但我对该语言不是很精通,可以使用一些帮助。谢谢你。

【问题讨论】:

  • 这是一个糟糕的设计。首先,从不在单个列中保留多个值,其次,几乎没有理由让多个表具有相同的结构和相同的含义。你的目标设计也不好。正确的设计是为您当前具有多个值的每一列添加一个表,该表将包含主表的 forgiegn 键和单个值。此外,将不同的客户表统一到一个表中。
  • 我同意@ZoharPeled 的观点,但我希望您提出这个问题的部分原因是因为您正试图对数据进行一点标准化。实在是太惨了……
  • 我同意原始表的设计不如最优。这是我无法控制的外部脚本的结果,因此这些原始数据默认位于我上面显示的结构中。在解析之后,将我的目标设计更改为多个表,每个表对应于原始表中的一列,这可能是值得的。

标签: sql-server tsql parsing stored-procedures sql-server-2012


【解决方案1】:

创建并填充示例表(请在您以后的问题中保存我们这一步)

DECLARE @T AS TABLE 
(
    Domain_Name varchar(200), -- or whatever length suits your needs
    Account_ID int, 
    SiteID int, 
    Account_Name varchar(200),    
    BotExceptions varchar(200), 
    BlockCountries varchar(200)
)

INSERT INTO @T VALUES 
('www.Domain1.com', 123456, 987654, 'account1', 'TR Sites24x7 Tools/', 'UK FR IR'),
('www.Domain2.com', 21478, 987654, 'account1', 'TR Sites24x7 Tools/', 'USA IT FR')

以下是我将如何创建目标表

CREATE TABLE DomainAccounts
(
    Id int identity(1,1),
    Domain_Name varchar(200), -- or whatever length suits your needs
    Account_ID int, 
    SiteID int, 
    Account_Name varchar(200),-- or whatever length suits your needs
    CONSTRAINT PK_DomainAccounts PRIMARY KEY (Id)
)

CREATE TABLE BotExceptions
(
    DomainAccounts_Id int,
    BotName varchar(200),  -- or whatever length suits your needs
    CONSTRAINT FK_BotExceptions_DomainAccounts FOREIGN KEY (DomainAccounts_Id) REFERENCES DomainAccounts(Id)
)

CREATE TABLE BlockCountries
(
    DomainAccounts_Id int,
    CountryName varchar(200),  -- or whatever length suits your needs
    CONSTRAINT FK_BlockCountries_DomainAccounts FOREIGN KEY (DomainAccounts_Id) REFERENCES DomainAccounts(Id)
)

另外,再创建 2 个表以同样的方式 - BlockedURLs 和 BlockedIPs

然后,创建一个字符串拆分函数。我使用了一个基于 Jeff Moden 拆分器的函数,该拆分器取自 Aaron Bertrand 的 Split strings the right way – or the next best way。随意使用不同的。

CREATE FUNCTION dbo.SplitStrings
(
   @List NVARCHAR(MAX),
   @Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
  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),
       E2(N)        AS (SELECT 1 FROM E1 a, E1 b),
       E4(N)        AS (SELECT 1 FROM E2 a, E2 b),
       E42(N)       AS (SELECT 1 FROM E4 a, E2 b),
       cteTally(N)  AS (SELECT 0 UNION ALL SELECT TOP (DATALENGTH(ISNULL(@List,1))) 
                         ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E42),
       cteStart(N1) AS (SELECT t.N+1 FROM cteTally t
                         WHERE (SUBSTRING(@List,t.N,1) = @Delimiter OR t.N = 0))
  SELECT Item = SUBSTRING(@List, s.N1, ISNULL(NULLIF(CHARINDEX(@Delimiter,@List,s.N1),0)-s.N1,8000))
    FROM cteStart s;

最后,将源表中的记录插入到目标表中:

INSERT INTO DomainAccounts (Domain_Name, Account_ID, SiteID, Account_Name)
SELECT Domain_Name, Account_ID, SiteID, Account_Name
FROM @T

INSERT INTO BotExceptions (DomainAccounts_Id, BotName)
SELECT da.Id, Item
FROM @T t 
INNER JOIN DomainAccounts da ON t.Domain_Name = da.Domain_Name
                            AND t.Account_ID = da.Account_ID
                            AND t.SiteID = da.SiteID
                            AND t.Account_Name = da.Account_Name

CROSS APPLY dbo.SplitStrings(BotExceptions, ' ') 
WHERE LEN(LTRIM(RTRIM(Item))) > 0

INSERT INTO BlockCountries (DomainAccounts_Id,CountryName)
SELECT da.Id, Item
FROM @T t 
INNER JOIN DomainAccounts da ON t.Domain_Name = da.Domain_Name
                            AND t.Account_ID = da.Account_ID
                            AND t.SiteID = da.SiteID
                            AND t.Account_Name = da.Account_Name

CROSS APPLY dbo.SplitStrings(BlockCountries, ' ') 
WHERE LEN(LTRIM(RTRIM(Item))) > 0

【讨论】:

    【解决方案2】:

    你可以使用这样的函数:

    CREATE FUNCTION [dbo].[UDF_StringDelimiter]
    /*********************************************************
    **  Takes Parameter "LIST" and transforms it for use    **
    **  to select individual values or ranges of values.    **
    **                                                      **
    **  EX: 'This,is,a,test' = 'This' 'Is' 'A' 'Test'       **
    *********************************************************/
        (
              @LIST             VARCHAR(8000)
             ,@DELIMITER        VARCHAR(255)
        )
    
    RETURNS @TABLE TABLE 
        ( 
             [RowID] INT IDENTITY
            ,[Value] VARCHAR(255)
        )
    WITH SCHEMABINDING  
    AS 
    BEGIN
        DECLARE 
             @LISTLENGTH AS SMALLINT
            ,@LISTCURSOR AS SMALLINT
            ,@VALUE AS VARCHAR(255)
        ;
        SELECT 
             @LISTLENGTH = LEN(@LIST) - LEN(REPLACE(@LIST,@DELIMITER,'')) + 1
            ,@LISTCURSOR = 1
            ,@VALUE = ''
        ;
        WHILE @LISTCURSOR <= @LISTLENGTH
        BEGIN
    
            INSERT INTO @TABLE (Value)
            SELECT 
                CASE
                    WHEN @LISTCURSOR < @LISTLENGTH
                        THEN SUBSTRING(@LIST,1,PATINDEX('%' + @DELIMITER + '%',@LIST) - 1)
                    ELSE SUBSTRING(@LIST,1,LEN(@LIST))
                END
            ;
            SET @LIST = STUFF(@LIST,1,PATINDEX('%' + @DELIMITER + '%',@LIST),'')
            ;       
            SET @LISTCURSOR = @LISTCURSOR + 1
            ;
        END
        ;
        RETURN
        ;
    END
    ;
    

    在这种情况下,您可以这样使用它:

    SELECT * FROM dbo.UDF_StringDelimiter(BotExceptions,' ')
    ;
    

    这将返回一个像这样的值列表:

    RowID         Value
    --------------------
    1             TR
    2             Sites24x7 
    3             Tools
    

    使用各种方法(JOIN 或 CROSS APPLY),您可以使用它向表中插入新行。

    我想你会想加入这些函数结果WHERE RowID &gt;= 2,它只会添加第二、第三、第四、第 n 行。保存原始字符串的第一行可以使用相同的函数WHERE RowID = 1 更新以删除第二批中的所有后续值。

    会有帮助吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多