【问题标题】:Comma Separated Values in one table to return results from another table一个表中的逗号分隔值以从另一个表返回结果
【发布时间】:2020-12-06 20:23:23
【问题描述】:

我有两个表和一个相当复杂的 SQL 查询来从这些表中提取数据 - 这一切正常,直到它遇到具有多个 id 的列中的值 - 逗号分隔。因此,为了简化我正在努力解决的领域,让我们假设以下内容

表 1 (T1)

ID         First Name     Last Name   Active  
--------------------------------------------
101        Fred           Bloggs      1  
102        John           Smith       0  
103        Elizabeth      Dawson      1  
104        Amy            Johnson     1

表 2 (T2)

ID         Postcode       HouseNo  
-----------------------------------
101        TS15 9AZ         42   
102        TQ1 4TF           3  
103, 104   WA1 4AA           7  

所以假设我想返回谁住在哪个地址的结果,我加入 ID 上的表并编写一个相当简单的查询,例如

select 
    T1.FirstName + ' ' + T1.Lastname as fullname, T2.Postcode, T2.HouseNo
from 
    T1
join 
    t2 on t1.id = t2.id
where 
    t1.active = 1

此查询正常工作,直到它返回错误时遇到逗号分隔值:

将 varchar 值 '103,104' 转换为数据类型 int 时转换失败

它应该返回的是

Fullname                         PostCode      HouseNo
-------------------------------------------------------
Fred Blogs                       TS15 9AZ        42
Elizabeth Dawson Amy Johnson     TQ1 4TF          3

关于如何进行这项工作的任何想法?

【问题讨论】:

  • 在单个数据库单元格中存储 comma-separated 值列表是一个很大的 NO-NO 并且会 - 正如您在此处看到的那样 - 只会导致你的悲伤和心痛。您应该遵守数据库设计的第一范式 - 单个单元格最多包含一个原子值 - 以适当的关系方式处理多个值
  • 我想你会在表 2 的设计中看到 cmets。您收到的错误是由于 T1.ID 是 INT 而 T2.ID 是字符串(以“,”为例)。加入这两列将失败。将 T2 更改为 ID 103 和 104 有两个单独的行 - 然后 JOIN 将起作用。

标签: sql sql-server csv tsql sql-server-2012


【解决方案1】:

首先:不要在单个列中存储多个值;不要使用字符串来存储数字。您可以查看this famous SO post,了解有关不鼓励这样做的更多详细信息。

也就是说,在 CSV 列表中搜索值的简单(尽管效率低下)解决方案是:

select t1.FirstName + ' ' + t1.Lastname as fullname, t2.Postcode, t2.HouseNo
from t1
join t2 on concat(', ', t2.id, ', ') like concat('%, ', t1.id, ', %')
where t1.active = 1

这假设您始终使用逗号 + 空格 (', ') 作为列表元素之间的分隔符。

【讨论】:

  • 感谢大家的回复 - 非常感谢。我使用了这个解决方案,因为虽然它排除了具有多个 ID 的结果,但对于那些使用不同字段的人来说,重新运行是一件容易的事。
【解决方案2】:

您的查询失败,因为您的表返回的数据在返回类型中似乎不一致,因为它既有整数值(103、104)也有非整数值,就像您在所需输出中提到的那样。

这里的解决方案是将它们全部转换为单一类型。我认为字符串结果类型可能是这里的最佳选择:

select
    T1.FirstName + ' ' + T1.Lastname as fullname, 
    CONVERT(NVARCHAR(10), T2.Postcode), -- you can change the value 10 to anything else
    CONVERT(NVARCHAR(10), T2.HouseNo)
from 
    T1
join 
    t2 on t1.id = t2.id
where 
    t1.active = 1

希望这会有所帮助))


更新

@marc_s 在这里绝对正确,尽量避免在表格列中使用逗号分隔值。他们违反了 SQL 规范化规则(官方文档here)。

【讨论】:

    【解决方案3】:

    正如上面多次建议的那样,最好将值单独存储在 ID 列中。这就是说在 SQL Server 中你可以这样做:

    select 
        T1.FirstName + ' ' + T1.Lastname as fullname, T2.Postcode, T2.HouseNo
    from 
        T1
    join 
    (
        select t2.*, value as id_new
        from t2
        CROSS APPLY STRING_SPLIT(id, ',')
    ) t2 on t1.id = t2.id_new
    where 
        t1.active = 1
    

    【讨论】:

    • 那会奏效的。但问题标记为 SQL Server 2012:string_split() 仅从 2016 版开始可用。
    【解决方案4】:

    你可以试试下面的查询

    SELECT T1.FirstName + ' ' + T1.LastName AS FullName, T2.PostCode, T2.HouseNo
    FROM T1
    JOIN (
        SELECT LTRIM(RTRIM(ID)), PostCode, HouseNo
        FROM T2
        CROSS APPLY STRING_SPLIT(ID, ',')
    ) T2 ON T2.ID = T1.ID
    WHERE T1.active = 1
    

    【讨论】:

    • 问题标记为 SQL Server 2012:string_split() 仅从 2016 版开始可用。
    【解决方案5】:

    有时它可能是我们继承的数据,您只需要修复报告即可。不久前,我在互联网上发现了这个 splitstring 函数(向编写它的人致敬),它会将逗号分隔的 ID 值拆分(如果您使用的数据库版本早于 SQL Server 2016,其中 STRING_SPLIT 不可用),其中您也许可以放入另一张桌子并使用它?不过,我同意其他 cmets 的观点,在字段中保留单个值是 100% 的好举措。

    CREATE or alter FUNCTION [dbo].[SplitString]  
    (  
       @Input NVARCHAR(MAX),  
       @Character CHAR(1)  
    )  
    RETURNS @Output TABLE (  
       Item NVARCHAR(1000)  
    )  
    AS  
    BEGIN  
        DECLARE @StartIndex INT, @EndIndex INT  
        SET @StartIndex = 1  
    
        IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character  
        BEGIN  
            SET @Input = @Input + @Character  
        END  
    
        WHILE CHARINDEX(@Character, @Input) > 0  
        BEGIN  
            SET @EndIndex = CHARINDEX(@Character, @Input)  
    
            INSERT INTO @Output(Item)  
                SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)  
                SET @Input = SUBSTRING(@Input, @EndIndex + 1, LEN(@Input))  
        END  
    RETURN  
    END 
    
    GO
    
    --=======================================================
    DROP TABLE IF EXISTS newTable;
    
    SELECT * INTO newTable 
    FROM
    (
        SELECT '123,456' as id, 'TS15 9AZ' AS postcode UNION
        SELECT '456,789' as id, 'TQ1 4TF' AS postcode
    ) AS IDS
    CROSS APPLY
    DBO.[SPLITSTRING](ID, ',') AS SPLIT;
    
    SELECT * FROM newTable;
    --=======================================================
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-04
      • 2022-11-03
      • 1970-01-01
      • 2018-10-05
      • 2021-12-11
      相关资源
      最近更新 更多