SQL Server:选择 4 个非空列并将它们连接起来

Posted

技术标签:

【中文标题】SQL Server:选择 4 个非空列并将它们连接起来【英文标题】:SQL Server: SELECT 4 non-empty columns and concatenate them 【发布时间】:2016-09-22 19:20:13 【问题描述】:

假设我有以下设置

MailsTable
Mail1 Varchar(40)
Mail2 Varchar(40)
Mail3 Varchar(40)
Mail4 Varchar(40)

现在我正在构建一个查询,该查询应包括至少有 1 个非空邮件(Mail1 或 Mail2...等)的行。并用';'连接所有非空邮件 然后填充缺失的空白字符,总共达到 163 个字符(所有 4 封邮件 + ;)

例子:

1)在Mail1和Mail3存在的一行中:

george@net.com;louis@net.com (以及 163-28=135 个空白字符来填充总共 163 个字符)

2)Mail2、Mail3、Mail4存在的一行:

mail2@gmail.com;mail3@gmail.com;mail4@gmail.com(以及许多空白字符来填充 163)

但是当我尝试构建 Select 时它变得有点棘手,我考虑使用 CASE WHEN THEN 子句,但最终有太多的情况需要处理。以及“填充空白字符”问题。

欢迎任何帮助。 提前致谢。

【问题讨论】:

你试过什么?您应该包括一些示例数据和预期结果。另外,当您说填充空白字符时,您只是想要 CHAR(163) 还是想要空格? 我一直在使用 Convert(CHAR(163), column) 处理填充物,但这些实际上是具有许多不同场景的 4 列,所以我不确定如何包装整个内容。至于数据,我会尝试添加更多示例。 糟糕的桌子设计。只有一个邮件栏。 (如果需要,可以使用多行。) 正如@jarlh 所说,您需要对此进行规范化。一旦你对数据结构进行了适当的规范化,查询就会变得非常简单。 这完全是一片混乱。这甚至不是完整的表格,但这就是我最近找到这个数据库的方式。我会提出警告,希望我们以后可以改变它。 【参考方案1】:

我真的不明白为什么要填充这些最多 163 个字符,但这里有一个示例,显示了空地址和非空地址的所有可能组合:

declare @MailsTable table
(
    RowNumber int,
    Mail1 varchar(40),
    Mail2 varchar(40),
    Mail3 varchar(40),
    Mail4 varchar(40)
);

insert @MailsTable values
    (0, null, null, null, null),
    (1, 'Addr1', null, null, null),
    (2, null, 'Addr2', null, null),
    (3, 'Addr1', 'Addr2', null, null),
    (4, null, null, 'Addr3', null),
    (5, 'Addr1', null, 'Addr3', null),
    (6, null, 'Addr2', 'Addr3', null),
    (7, 'Addr1', 'Addr2', 'Addr3', null),
    (8, null, null, null, 'Addr4'),
    (9, 'Addr1', null, null, 'Addr4'),
    (10, null, 'Addr2', null, 'Addr4'),
    (11, 'Addr1', 'Addr2', null, 'Addr4'),
    (12, null, null, 'Addr3', 'Addr4'),
    (13, 'Addr1', null, 'Addr3', 'Addr4'),
    (14, null, 'Addr2', 'Addr3', 'Addr4'),
    (15, 'Addr1', 'Addr2', 'Addr3', 'Addr4');

with ConcatenatedAddressesCTE as
(
    select
        RowNumber,
        Mails =
            case when Mail1 != '' then ';' + Mail1 else '' end +
            case when Mail2 != '' then ';' + Mail2 else '' end +
            case when Mail3 != '' then ';' + Mail3 else '' end +
            case when Mail4 != '' then ';' + Mail4 else '' end
    from
        @MailsTable
    where
        Mail1 != '' or Mail2 != '' or Mail3 != '' or Mail4 != ''
)
select
    RowNumber,
    FormattedMails = substring(Mails, 2, 200) + replicate(' ', 164 - len(Mails))
from
    ConcatenatedAddressesCTE;

结果:

RowNumber  FormattedMails
1          Addr1                                                                                                                                                              
2          Addr2                                                                                                                                                              
3          Addr1;Addr2                                                                                                                                                        
4          Addr3                                                                                                                                                              
5          Addr1;Addr3                                                                                                                                                        
6          Addr2;Addr3                                                                                                                                                        
7          Addr1;Addr2;Addr3                                                                                                                                                  
8          Addr4                                                                                                                                                              
9          Addr1;Addr4                                                                                                                                                        
10         Addr2;Addr4                                                                                                                                                        
11         Addr1;Addr2;Addr4                                                                                                                                                  
12         Addr3;Addr4                                                                                                                                                        
13         Addr1;Addr3;Addr4                                                                                                                                                  
14         Addr2;Addr3;Addr4                                                                                                                                                  
15         Addr1;Addr2;Addr3;Addr4                                                                                                                                            

CTE 为每个非空地址添加一个分号前缀,这样它就不必担心两个非空地址之间可能会出现多少个空地址。这意味着它产生的每个结果都以分号开头,这就是为什么查询的最后部分会去掉最左边的字符,以及为什么它在长度计算中使用 164 而不是 163。

我在这里假设当您说“空”邮件时,您可能指的是 null 或空字符串。

【讨论】:

哇,我印象深刻! (甚至没有注意到子字符串摆脱了前导;很好!)。另外,我很高兴您将 empy 假定为 null 或“空”。不清楚是我的错。谢谢一百万!【参考方案2】:

尚未对此进行测试,但希望它对您有用。

SELECT ISNULL( Mail1 + ';', '') + ISNULL( Mail2 + ';', '') + ISNULL( Mail3 + ';', '') + ISNULL( Mail4 + ';', '')  AS Result
FROM MailsTable

如果您的表格有空字符串或空白字符串,您的表达式会变得有点复杂:

SELECT ISNULL( CASE WHEN RTRIM(Mail1) = '' THEN NULL ELSE Mail1 END + ';', '') + 
        ISNULL( CASE WHEN RTRIM(Mail2) = '' THEN NULL ELSE Mail2 END + ';', '') + 
        ISNULL( CASE WHEN RTRIM(Mail3) = '' THEN NULL ELSE Mail3 END + ';', '') + 
        ISNULL( CASE WHEN RTRIM(Mail4) = '' THEN NULL ELSE Mail4 END + ';', '')  AS Result
FROM MailsTable

我错过了对字符串填充的要求。使用字符串填充查询将如下所示:

SELECT  CONVERT(CHAR(163), 
        (
            LEFT(
                    ISNULL( CASE WHEN RTRIM(Mail1) = '' THEN NULL ELSE Mail1 END + ';', '') + 
                    ISNULL( CASE WHEN RTRIM(Mail2) = '' THEN NULL ELSE Mail2 END + ';', '') + 
                    ISNULL( CASE WHEN RTRIM(Mail3) = '' THEN NULL ELSE Mail3 END + ';', '') + 
                    ISNULL( CASE WHEN RTRIM(Mail4) = '' THEN NULL ELSE Mail4 END + ';', '') + SPACE(163), 163)
                ))   AS Result 
FROM MailsTable

【讨论】:

【参考方案3】:

Edit 修改为将空格、null 和空字符串视为相同。注意向乔大喊,提醒我把分号放在前面。当你这样做时,你可以使用一些东西来用空白字符替换它。 Cast to CHAR(163) 会自动将字符串的右侧填充为 163 个字符。

SELECT
    *
    ,CAST(
       STUFF(
          CASE WHEN COALESCE(RTRIM(Mail1),'') <> '' THEN ';' + Mail1 ELSE '' END
          + CASE WHEN COALESCE(RTRIM(Mail2),'') <> '' THEN ';' + Mail2 ELSE '' END
          + CASE WHEN COALESCE(RTRIM(Mail3),'') <> '' THEN ';' + Mail3 ELSE '' END
          + CASE WHEN COALESCE(RTRIM(Mail4),'') <> '' THEN ';' + Mail4 ELSE '' END
       ,1,1,'')
    AS CHAR(163))
FROM
    @MailsTable
WHERE
    COALESCE(RTRIM(Mail1),'') <> ''
    OR COALESCE(RTRIM(Mail2),'') <> ''
    OR COALESCE(RTRIM(Mail3),'') <> ''
    OR COALESCE(RTRIM(Mail4),'') <> ''

这是测试数据:

DECLARE @MailsTable AS TABLE (Id INT IDENTITY(1,1), Mail1 VARCHAR(40), Mail2 VARCHAR(40), Mail3 VARCHAR(40), Mail4 VARCHAR(40))
INSERT INTO @MailsTable VALUES
('Mail1@M1.Com','Mail2@M2.Com','Mail3@M3.com','Mail4@M4.com')
,(NULL,'Mail2@M2.Com','Mail3@M3.com','Mail4@M4.com')
,('Mail1@M1.Com',NULL,'Mail3@M3.com','Mail4@M4.com')
,('Mail1@M1.Com','Mail2@M2.Com',NULL,'Mail4@M4.com')
,('Mail1@M1.Com','Mail2@M2.Com','Mail3@M3.com',NULL)
,('Mail1@M1.Com',NULL,NULL,NULL)
,('Mail1@M1.Com','Mail2@M2.Com',NULL,NULL)
,(NULL,'Mail2@M2.Com',NULL,'Mail4@M4.com')
,(NULL,'Mail2@M2.Com',NULL,NULL)
,('Mail1@M1.Com',NULL,NULL,'Mail4@M4.com')
,(NULL,NULL,NULL,'Mail4@M4.com')
,(NULL,NULL,NULL,NULL)
,(NULL,NULL,NULL,'')
,('Mail1@M1.Com','',NULL,'Mail4@M4.com')
,(' ',NULL,'   ','Mail4@M4.com')
,(NULL,NULL,NULL,'   ')

【讨论】:

谢谢马特!我没有澄清空我的意思是空字符串和空字符串(所以考虑到我的数据,它不能 100% 为我工作)。仍然非常感谢您的帮助。制造+拯救了我的一天:D @J_Ocampo 是的,我想我的解决方案只部分处理了空字符串。我将其更改为能够处理空白(crlf、制表符、空格)、空值和空字符串。通过窃取 joes 的想法,在地址前面加上 ;然后你可以使用东西并去掉前导字符,使它比使用另一个 cte 或复制日志连接语句更整洁....

以上是关于SQL Server:选择 4 个非空列并将它们连接起来的主要内容,如果未能解决你的问题,请参考以下文章

在一列中获取火花数据帧的所有非空列

左连接 ON 非空列不能选择非空列

选择 PL/SQL 中两个非空列值之间的行集

如何根据另一列的值创建空列或非空列?

怎样用SQL语句对指定字段建立非空约束?

检查可空列是不是稀疏或不在 SQL Server 中查询