【问题标题】:SQL: Nested SELECT with multiple values in a single fieldSQL:在单个字段中具有多个值的嵌套 SELECT
【发布时间】:2011-07-27 23:28:49
【问题描述】:

在我的 SQL 2005 数据库中,我有一个表,其中的值存储为与其他表有关系的 ID。因此,在我的 MyDBO.warranty 表中,我存储的是 product_id 而不是 product_name 以节省空间。 product_name 存储在 MyDBO.products 中。

当营销部门拉取人口统计信息时,查询会从相关表中为每个 ID 选择相应的名称(为简洁起见):

SELECT w1.warranty_id AS "No.",
       w1.created AS "Register Date" 
       w1.full_name AS "Name", 
       w1.purchase_date AS "Purchased", 
       (
           SELECT p1.product_name
           FROM WarrDBO.products p1 WITH(NOLOCK)
           WHERE p1.product_id = i1.product_id
       ) AS "Product Purchased",
       i1.accessories
FROM WarrDBO.warranty w1
LEFT OUTER JOIN WarrDBO.warranty_info i1
    ON i1.warranty_id = w1.warranty_id
ORDER BY w1.warranty_id ASC

现在,我的问题是warranty_info 表上的“附件”列存储了几个值:

No.     Register Date    Name             Purchased       Accessories
---------------------------------------------------------------------
1500    1/1/2008         Smith, John      Some Product    5,7,9
1501    1/1/2008         Hancock, John    Another         2,3
1502    1/1/2008         Brown, James     And Another     2,9

我需要对“附件”执行与“产品”类似的操作,并从 MyDBO.accessories 表中提取 accessory_name使用 accessory_id。我不确定从哪里开始,因为首先我需要提取 ID,然后以某种方式将多个值连接成一个字符串。所以每一行都会有“accessoryname1,accessoryname2,accessoryname3”:

No.     Register Date    Name             Purchased       Accessories
---------------------------------------------------------------------
1500    1/1/2008         Smith, John      Some Product    Case,Bag,Padding
1501    1/1/2008         Hancock, John    Another         Wrap,Label
1502    1/1/2008         Brown, James     And Another     Wrap,Padding

我该怎么做?

编辑>>发布我的最终代码:

我创建了这个函数:

CREATE FUNCTION SQL_GTOInc.Split
(
  @delimited varchar(50),
  @delimiter varchar(1)
) RETURNS @t TABLE
(
-- Id column can be commented out, not required for sql splitting string
  id INT identity(1,1), -- I use this column for numbering splitted parts
  val INT
)
AS
BEGIN
  declare @xml xml
  set @xml = N'<root><r>' + replace(@delimited,@delimiter,'</r><r>') + '</r></root>'

  insert into @t(val)
  select
    r.value('.','varchar(5)') as item
  from @xml.nodes('//root/r') as records(r)

  RETURN
END

并相应地更新了我的代码:

SELECT w1.warranty_id, 
       i1.accessories,
       (
           CASE
               WHEN i1.accessories <> '' AND i1.accessories <> 'NULL' AND LEN(i1.accessories) > 0 THEN
                   STUFF(
                       (
                           SELECT ', ' + a1.accessory
                           FROM MyDBO.accessories a1
                           INNER JOIN MyDBO.Split(i1.accessories, ',') a2
                           ON a1.accessory_id = a2.val
                           FOR XML PATH('')
                       ), 1, 1, ''
                   )
               ELSE ''
           END
        ) AS "Accessories"
FROM MyDBO.warranty w1
    LEFT OUTER JOIN MyDBO.warranty_info i1
    ON i1.warranty_id = w1.warranty_id

【问题讨论】:

  • 嗯,它(应该)不言而喻,但你不应该像这样在warranty_info 中存储附件列。您应该有一个连接表 Warranty_info_accessories,它包含两列:warranty_id 和 accessory_id。这将大大简化您的问题
  • 我同意,如果我建造了这个结构,我会这样做。不幸的是,数据库作者竭尽全力节省空间,这是他使用的方法之一。
  • 感谢所有帮助的人,如果我可以选择多个答案,我会的。显然我只是没有仔细阅读并在我的声明中遗漏了FOR XML PATH(''),从而导致了错误。我已经采纳了所有建议,但效果并不理想。

标签: sql sql-server sql-server-2005 select


【解决方案1】:

您只需要使用 SQL Server 的FOR XML 功能即可轻松地对字符串进行分类:

链接博客文章中的示例:

SELECT
  STUFF(
    (
    SELECT
      ' ' + Description
    FROM dbo.Brands
    FOR XML PATH('')
    ), 1, 1, ''
  ) As concatenated_string

要解析已经以逗号分隔的字段,您必须编写一个 UDF 来解析该字段并返回一个表,然后可以在 WHERE 子句中与 IN 谓词一起使用该表。看看here for startershere

【讨论】:

  • 好的,所以这将从附件表中选择所有附件并将它们放在一个字符串中。但是如何从逗号分隔的字符串“2,3,9”中获取附件 ID?一旦我有了它,我如何将它们用作 STUFF 中 SELECT 的条件?
  • 谢谢,我去看看。
【解决方案2】:

您可以编写一个表值函数,简单地将逗号分隔的字符串拆分为 XML,并将 XML 节点转换为行。

见: http://www.kodyaz.com/articles//t-sql-convert-split-delimeted-string-as-rows-using-xml.aspx

通过函数调用的结果加入附件,并将结果填充回逗号分隔的名称列表中。

未经测试的代码:

SELECT w1.warranty_id AS "No.",
       w1.created AS "Register Date" 
       w1.full_name AS "Name", 
       w1.purchase_date AS "Purchased", 
       (
           SELECT p1.product_name
           FROM WarrDBO.products p1 WITH(NOLOCK)
           WHERE p1.product_id = i1.product_id
       ) AS "Product Purchased",
       STUFF(
         (
           SELECT 
             ', ' + a.name
           FROM [table-valued-function](i1.accessories) acc_list 
             INNER JOIN accessories a ON acc_list.id = a.id
           FOR XML PATH('')
         ), 1, 1, ''
       ) AS [accessories]
FROM WarrDBO.warranty w1
  LEFT OUTER JOIN WarrDBO.warranty_info i1
    ON i1.warranty_id = w1.warranty_id
ORDER BY w1.warranty_id ASC  

【讨论】:

  • 试过这个,我得到一个错误:Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, &lt;, &lt;= , &gt;, &gt;= or when the subquery is used as an expression. 如果我放一个 where 子句并且只从列表中选择一个配件,它工作正常。我认为它不会让我在子查询中使用该函数作为 STUFF() 的一部分
【解决方案3】:

与您的问题无关。请注意,您的原始查询也可以编写,将子查询移动到连接,如:

SELECT w1.warranty_id AS "No.",
       w1.created AS "Register Date" 
       w1.full_name AS "Name", 
       w1.purchase_date AS "Purchased", 
       p1.product_name AS "Product Purchased",
       i1.accessories
FROM WarrDBO.warranty w1
INNER JOIN WarrDBO.products p1 
    ON p1.product_id = i1.product_id 
LEFT OUTER JOIN WarrDBO.warranty_info i1
    ON i1.warranty_id = w1.warranty_id
ORDER BY w1.warranty_id ASC

【讨论】:

  • 感谢您告诉我。我一直在尝试将所有这些选择移至 INNER JOIN,但我一直难以形成那部分。我可能有 5 个类似的表,并且添加超过 2 个 INNER JOIN 会导致我的查询不返回任何内容。不知道为什么。
  • SELECT 列表中的子查询通常可以转换为LEFT 连接。不是INNER 加入。
【解决方案4】:

这似乎是一个连接聚合函数的工作。 在 SQL 中可以使用 CLR 进行部署

http://www.mssqltips.com/tip.asp?tip=2022

【讨论】:

    猜你喜欢
    • 2011-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-14
    • 2017-09-26
    • 2019-08-31
    • 1970-01-01
    相关资源
    最近更新 更多