SQL:按集合理论匹配记录

时间:2011-10-23 06:24:04

标签: sql linkage cross-join set-theory

我最近一直在努力解决复杂的SQL查询问题 我有以下表格:

[dbo].[User] ~ {ID,nickname}  
[dbo].[Property] ~ {ID,title}  
[dbo].[Property_Values] ~ [ID,propertyID,title}  
[dbo].[Property_Values_User_Linkage] ~ {UserID,PropertyID,valueID}  

它基本上是一个用户为每个属性选择值的项目。每个属性可以是单值或多值。例如,用户可能会为Property {ID = 1,title = Hobbies}选择多个值,但必须为Property {ID = 2,title = HairColor}选择一个值。

使用另一个表 - [dbo]。[Search_Property_Values_User_Linkage] - {UserID,PropertyID,valueID}我正在选择我想要的属性,我希望找到匹配的用户。但是,如果我没有选择一个值(或多值),让我们说HairColor,我应该得到所有用户(因为我不想通过HairColor过滤)。

到目前为止,这很容易,但我似乎无法解决的问题是前方存在多值或没有用户定义值的情况。例如,我希望所有用户都使用HairColor = Brown和Hobbies IN(篮球,足球)。

我可以检索所有符合其中一个条款的用户(以及那些拥有其他属性的用户,因为我没有选择过滤它们),但我不能只获得绝对的用户符合我的标准。

要将代码放入文字中,我们只需说我需要以下所有用户:

  • 匹配我选择的所有属性值
  • 可能还有其他属性,例如EyesColor,但由于我没有选择过滤值,因此也可以检索它们。
  • 可能根本没有设置任何属性,但由于我没有为此属性选择值,因此它们是有效的!
  • 将所有选定的属性作为群组匹配,而不仅仅是我选择的一个属性(喜欢篮球但有“红色”的用户) HairColor是无效的!

我遇到了一个解决方案,在其中我创建了一个虚拟表,它按比特“完成”未选择的值。例如(不是实际代码):

DECLARE @isMatches bit  
SET @isMatches=0    
if [propertyIsChosen]=1  
{    
 if [userInProperty]=1 SET @isMatches=1   
}  
else SET isMatches=1

我基本上CROSS-JOIN [属性]与[用户]
和LEFT-OUTER-JOIN其余表格以匹配选择 我得到了所有用户和他们与房产的匹配。这不够好,因为我有棕色头发的用户,喜欢篮球/足球的用户,而不是那些与两者相匹配的用户(当然还有任何其他未定义的属性)。
这很重,但这是我到目前为止分析问题所得到的。

我将不胜感激任何帮助。 10年前,我想我的数学课上缺少一些东西......

编辑:db pic:http://i51.tinypic.com/2n1cfwg.png

1 个答案:

答案 0 :(得分:0)

数据模型

从您的评论中,我看到您在问题中没有提到的其他与属性相关的表格。我假设我们现在可以忽略这些,只关注表示属性值,这可以通过下面的简化模型来完成:

enter image description here

DDL SQL(MS SQL Server):

CREATE TABLE [USER] (
    USER_ID int NOT NULL,
    NICKNAME nvarchar(50) NOT NULL,
    CONSTRAINT PK_USER PRIMARY KEY CLUSTERED (USER_ID)
);

CREATE TABLE USER_PROPERTY_VALUE(
    USER_ID int NOT NULL,
    PROPERTY_NAME nvarchar(50) NOT NULL,
    PROPERTY_VALUE_NO int NOT NULL,
    PROPERTY_VALUE nvarchar(255) NOT NULL,
    CONSTRAINT PK_USER_PROPERTY_VALUE PRIMARY KEY CLUSTERED (
        USER_ID,
        PROPERTY_NAME,
        PROPERTY_VALUE_NO
    )
);

ALTER TABLE USER_PROPERTY_VALUE ADD CONSTRAINT FK_USER_PROPERTY_VALUE_USER FOREIGN KEY(USER_ID)
REFERENCES [USER] (USER_ID);

多值由USER_PROPERTY_VALUE中的多行表示,共享相同的PROPERTY_NAME,但每个行都有不同的PROPERTY_VALUE_NO。

以下示例数据......

Rob:   HairColor={brown}, Hobby={basketball,football}
Bob:   HairColor={brown}, Hobby={basketball}
Steve: Hobby={basketball,football}

...在数据库中表示如下:

USER:

USER_ID NICKNAME
1       Rob
2       Bob
3       Steve

USER_PROPERTY_VALUE:

USER_ID PROPERTY_NAME   PROPERTY_VALUE_NO   PROPERTY_VALUE
1       HairColor       1                   brown
1       Hobby           1                   basketball
1       Hobby           2                   football
2       HairColor       1                   brown
2       Hobby           1                   basketball
3       Hobby           1                   basketball
3       Hobby           2                   football

示例查询

选择棕色头发的用户:

SELECT *
FROM [USER]
WHERE
    EXISTS (
        SELECT *
        FROM USER_PROPERTY_VALUE 
        WHERE
            USER_PROPERTY_VALUE.USER_ID = [USER].USER_ID
            AND PROPERTY_NAME = 'HairColor'
            AND PROPERTY_VALUE = 'brown'
    )

结果:

USER_ID NICKNAME
1       Rob
2       Bob

选择业余爱好包括篮球和足球的用户:

SELECT *
FROM [USER]
WHERE
    EXISTS (
        SELECT *
        FROM USER_PROPERTY_VALUE 
        WHERE
            USER_PROPERTY_VALUE.USER_ID = [USER].USER_ID
            AND PROPERTY_NAME = 'Hobby'
            AND PROPERTY_VALUE = 'basketball'
    )
    AND EXISTS (
        SELECT *
        FROM USER_PROPERTY_VALUE 
        WHERE
            USER_PROPERTY_VALUE.USER_ID = [USER].USER_ID
            AND PROPERTY_NAME = 'Hobby'
            AND PROPERTY_VALUE = 'football'
    )

结果:

USER_ID NICKNAME
1       Rob
3       Steve

选择头发颜色为棕色且兴趣爱好包括篮球和足球的用户。

SELECT *
FROM [USER]
WHERE
    EXISTS (
        SELECT *
        FROM USER_PROPERTY_VALUE 
        WHERE
            USER_PROPERTY_VALUE.USER_ID = [USER].USER_ID
            AND PROPERTY_NAME = 'HairColor'
            AND PROPERTY_VALUE = 'brown'
    )
    AND EXISTS (
        SELECT *
        FROM USER_PROPERTY_VALUE 
        WHERE
            USER_PROPERTY_VALUE.USER_ID = [USER].USER_ID
            AND PROPERTY_NAME = 'Hobby'
            AND PROPERTY_VALUE = 'basketball'
    )
    AND EXISTS (
        SELECT *
        FROM USER_PROPERTY_VALUE 
        WHERE
            USER_PROPERTY_VALUE.USER_ID = [USER].USER_ID
            AND PROPERTY_NAME = 'Hobby'
            AND PROPERTY_VALUE = 'football'
    )

结果:

USER_ID NICKNAME
1       Rob

Etc等......

根据另一个表的内容查询

假设您有一个包含过滤条件的表:

CREATE TABLE PROPERTY_FILTER (
    PROPERTY_NAME nvarchar(50) NOT NULL,
    PROPERTY_VALUE nvarchar(255) NOT NULL,
    CONSTRAINT PK_PROPERTY_FILTER PRIMARY KEY (PROPERTY_NAME, PROPERTY_VALUE)
)

以下查询将仅返回满足该表中当前包含的条件的用户:

SELECT *
FROM [USER] U
WHERE
    NOT EXISTS (
        SELECT F.PROPERTY_NAME, F.PROPERTY_VALUE
        FROM PROPERTY_FILTER F
        EXCEPT
        SELECT P.PROPERTY_NAME, P.PROPERTY_VALUE
        FROM USER_PROPERTY_VALUE P
        WHERE P.USER_ID = U.USER_ID
    )

简单来说:如果有一个过滤器属性不是用户的属性,请忽略该用户。

顺便说一句,这在并发多用户环境中不起作用 - 您需要在PROPERTY_FILTER表中引入一个额外字段来标识“会话”,或者使用temporary tables(如果您不需要)持久性)。