SQL Server:按CSV字符串过滤结果

时间:2013-06-14 16:00:42

标签: sql sql-server arrays join

这个非常棘手......

Products表格 - >产品有多种颜色..

我想要一个带有某些颜色的产品的存储过程..

例如,圣诞老人帽会有“绿色”和“红色”..我希望所有产品都有“绿色”和“红色”..不仅仅是“绿色”或“红色”,而是两个......

这就是我到目前为止......

问题

  1. 它带回了既有颜色又有颜色的产品..
  2. 重复记录..
  3. 代码:

    DECLARE @COLORS VARCHAR(MAX) = 'Red, Green'
    
    SELECT * 
    FROM Products p
    LEFT JOIN Product_Colors_Bridge b ON b.ProductID = p.ProductID
    LEFT JOIN Product_Colors c on c.ID = b.ColorID
    CROSS JOIN dbo.SplitString(@COLORS, ',', NULL)
    WHERE CHARINDEX(token, Color) <> 0
    

    Picture of this...

3 个答案:

答案 0 :(得分:0)

这是set-within-sets子查询的示例。我喜欢用聚合来解决这些问题。这里的代码有点棘手,因为你在字符串中列出了颜色。

select pcp.ProductId, p.ProductName
from Product p join
     Product_Colors_Bridge pcb
     on p.id = pcb.ProductId join
     Product_Colors pc
     on pcb.ColorId = pc.Id
group by pcp.ProductId, p.ProductName
having count(distinct (case when charindex(pc.Color, @Colors) > 1 then pc.Color end)) =
       (1 + len(@Colors) - len(replace(@Colors, ',', '')))

关键是having子句。第一部分计算产品在列表中的颜色数。第二个计算颜色的总数,通过使用不带逗号的字符串长度的差异来计算。

如果您愿意,也可以在case之前移动group by条件 - 假设您不关心其他颜色。

select pcp.ProductId, p.ProductName
from Product p join
     Product_Colors_Bridge pcb
     on p.id = pcb.ProductId join
     Product_Colors pc
     on pcb.ColorId = pc.Id join
     dbo.SplitString(@COLORS, ',', NULL) ssc
     on ssc.token = pc.Color
group by pcp.ProductId, p.ProductName
having count(*) =
       (1 + len(@Colors) - len(replace(@Colors, ',', '')))

答案 1 :(得分:0)

假设您使用的是SQL-Server 2008或更高版本,我建议不要在SQL中使用分隔字符串。如果要将多个值作为参数传递,请使用Table-valued parameters

所以你首先要让你的类型存储多种颜色(给定一个通用名称,以便可以重复使用):

CREATE TYPE dbo.StringList AS TABLE (Value NVARCHAR(MAX));

这不应该是创建参数的更多努力,但是避免了昂贵的(程序性)拆分方法,您的查询就会变成:

DECLARE @Colors dbo.StringList;
INSERT @Colors VALUES ('Red'), ('Green');

SELECT  p.ProductID, p.ProductName
FROM    Products p
        INNER JOIN Product_Colors_Bridge b 
            ON b.ProductID = p.ProductID
        INNER JOIN Product_Colors c 
            ON c.ID = b.ColorID
        INNER JOIN @colors co
            ON co.Value = c.Color
GROUP BY p.ProductID, p.ProductName
HAVING  COUNT(DISTINCT c.Color) = (SELECT COUNT(DISTINCT Value) FROM @Colors);

-- HAVING IS KEY HERE, STATING THAT THE COUNT OF DIFFERENT COLOURS ASSOCIATED 
-- WITH THE PRODUCT IS THE SAME AS THE NUMBER OF DIFFERENT COLOURS PASSED TO
-- THE QUERY IN THE PARAMETER

答案 2 :(得分:0)

以下是使用like执行此操作的简单方法:

declare @colors varchar(max) = 'Red,Green'; 

select p.ProductID, p.ProductName
from Products p
    join Product_Colors_Bridge b on b.ProductID = p.ProductID
    join Product_Colors c on c.ID = b.ColorID
where ',' + @colors + ',' like '%,' + c.Color + ',%'
group by p.ProductID, p.ProductName
having count(*) = len(@colors) - len(replace(@colors, ',', '')) + 1;

/*
  ProductID ProductName
----------- --------------
          2 Santa Hat
*/

这将使所有产品至少具有搜索字符串中的所有颜色。

请注意,我已经删除了搜索字符串中的空格,并且我正在添加逗号(在查询本身中)以通过搜索“Blue”来消除“BabyBlue”匹配的可能性。我还假设你的表或搜索字符串中没有重复的颜色或颜色分配。

您必须在您的环境中进行测试,以确定此技术是否比使用UDF进行拆分或以不同方式传递参数(XML,table-valued parameters)更快。也就是说,我通常会远离数据库中的分隔字符串(虽然我们没有将它们存储在这里,但这是一个滑坡无处可去),宁可使用TVP或在应用程序中动态创建查询(使用参数,而不是动态SQL)如果可能的话(可以利用索引)。