选择与同一表上的其他列值组合匹配的列值

时间:2014-06-09 15:26:06

标签: sql sql-server sql-server-2008

我有一个名为Ads的表和另一个名为AdDetails的表,用于以属性/值样式存储每个广告的详细信息。以下是使用虚拟代码的简化示例:

[AdDetailID], [AdID], [PropertyName], [PropertyValue]
 2            28      Color           Red
 3            28      Speed           100
 4            27      Color           Red
 5            28      Fuel            Petrol  
 6            27      Speed           70

如何选择与PropertyName和PropertyValue的多种组合匹配的Ads,例如:

where PropertyName='Color' and PropertyValue='Red'
And
where PropertyName='Speed' and CAST(PropertyValue AS INT) > 60

4 个答案:

答案 0 :(得分:2)

你可能会做很多这样的事情,所以我会先制作一个将所有属性折叠成一行的视图。

create view vDetail
as
select AdID,
max(case PropertyName 
when 'Color' then PropertyValue end) as Color,
cast(max(case PropertyName 
when 'Speed' then PropertyValue end) as Int) as Speed,
max(case PropertyName 
when 'Fuel' then PropertyValue end) as Fuel
from AdDetails
group by AdID

这种方法也解决了将速度转换为int的问题。

然后,如果我select * from vDetails

enter image description here

这使得在加入父表时很容易处理。你说你需要一个可变数量的"匹配" - 请注意下面的where子句。 @MatchesNeeded将是非空变量数的计数。

        select *
        from Ads a
        inner join vDetails v
        on a.AdID = v.AdID
        where case when v.Color = @Color then 1 else 0 end +
          case when v.Spead > @Speed then 1 else 0 end +
          case when v.Fuel = @Fuel then 1 else 0 end = @MatchesNeeded

答案 1 :(得分:1)

我认为你有两个主要问题需要解决。

1)您需要能够将varchar值CAST为整数,其中某些值不会是整数。

如果您使用的是SQL 2012,则可以使用TRY_CAST()(sql server - check to see if cast is possible)。由于您使用的是SQL 2008,因此需要CASE和ISNUMERIC()的组合。

2)您需要一种有效的方法来检查是否存在多个属性。

我经常看到联接和where子句的组合,但我认为这很快就会变得混乱,因为你检查的属性数量会超过......比如说。相反,使用EXISTS子句往往更整洁,我认为它为SQL Optimizer提供了更好的线索。

 SELECT AdID
 FROM   Ads
 WHERE  1 = 1
    AND EXISTS (
            SELECT  1
            FROM    AdDetails
            WHERE   AdID = Ads.AdID
                AND ( PropertyName='Color' and PropertyValue='Red' )
        )
    AND EXISTS (
            SELECT  1
            FROM    AdDetails
            WHERE   AdID = Ads.AdID
                AND PropertyName='Speed'
                AND 
                    (
                        CASE
                        WHEN ISNUMERIC(PropertyValue) = 1
                        THEN CAST(PropertyValue AS INT)
                        ELSE 0
                        END
                    )
                    > 60
        )

您可以根据需要添加任意数量的EXISTS子句,而不会使查询特别难以阅读。

答案 2 :(得分:0)

这样的事情可能适用于2个条件,你必须根据条件的数量进行调整

select a.*
from ads as a
    join addetails as d1 on d1.adid = a.id
    join addetails as d2 on d2.adid = a.id

where (d1.PropertyName='Color' and d1.PropertyValue='Red')
        and (d2.PropertyName='Speed' and d2.CAST(PropertyValue AS INT) > 60)

答案 3 :(得分:0)

DECLARE @AdDetails TABLE
( 
    AdDetailID INT, 
    AdID INT, 
    PropertyName VARCHAR(20), 
    PropertyValue VARCHAR(20)
)

INSERT INTO @AdDetails
( AdDetailID, AdID, PropertyName, PropertyValue )
VALUES
(2,           28,      'Color',           'Red'),
(3,           28,      'Speed',           '100'),
(4,           27,      'Color',           'Red'),
(5,           28,      'Fuel',           'Petrol'),
(6,           27,      'Speed',           '70');


--Col1
DECLARE @ColorValue VARCHAR(20) = 'Red'

--Col2
DECLARE @SpeedValue INT  = 90
DECLARE @SpeedType VARCHAR(2) = '>'

--Col3
DECLARE @FuelValue VARCHAR(20) = null

SELECT DISTINCT a.AdID FROM @AdDetails a
INNER JOIN
(
    SELECT * 
    FROM @AdDetails 
    WHERE @ColorValue IS NULL 
        OR @ColorValue = PropertyValue
) Color
    ON Color.AdID = a.AdID
INNER JOIN
(
    SELECT * 
    FROM @AdDetails 
    WHERE @SpeedType IS NULL
    UNION
    SELECT * 
    FROM @AdDetails
    WHERE PropertyName = 'Speed'
        AND ((@SpeedType = '>' AND CONVERT(INT, PropertyValue) > @SpeedValue)
        OR (@SpeedType = '<' AND CONVERT(INT, PropertyValue) < @SpeedValue)
        OR (@SpeedType = '=' AND CONVERT(INT, PropertyValue) = @SpeedValue))
) AS Speed
    ON Speed.AdID = a.AdID
INNER JOIN
(
    SELECT * 
    FROM @AdDetails 
    WHERE @FuelValue IS NULL 
        OR (@FuelValue = PropertyValue)
) AS Fuel
    ON Fuel.AdID = a.AdID

我为每个属性类型添加一个内部连接子句(带有一些覆盖),你的sql查询将一次性传递所有可能的属性类型信息,将他们不想要的东西归零。代码非常难看,但随着它的增长。