【问题标题】:SQL: Matching records by set theorySQL:通过集合论匹配记录
【发布时间】:2011-10-23 06:24:04
【问题描述】:

我最近一直在处理一个复杂的 SQL 查询。
我有以下表格:

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

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

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

到目前为止,这很容易,但我似乎无法解决的问题是前面有多个值或没有用户定义的值的情况。例如,我希望所有具有 HairColor=Brown 和 Hobbies IN(basketball,football) 的用户。

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

要将代码转化为文字,假设我需要所有满足以下条件的用户:

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

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

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

我基本上是与 [用户] 交叉连接 [属性]
和 LEFT-OUTER-JOIN 其余表以匹配选择。
我让所有用户和他们匹配到一个属性。这还不够好,因为我得到了棕色头发的用户,喜欢篮球/足球的用户,但没有同时匹配两者的用户(当然还有我的任何其他未定义属性)。
这很重,但这是我到目前为止分析问题的内容。

我将不胜感激。我想我在 10 年前的数学课上遗漏了一些东西......

编辑:数据库图片:http://i51.tinypic.com/2n1cfwg.png

【问题讨论】:

  • 属性的数量(单个或多个值)是否固定?我的意思是以后会添加或删除属性吗?
  • 请告诉我单值属性是否会随时更改为多值属性?是否所有用户都拥有相同数量的属性?此外,如果没有用户的存在,属性的存在是否有意义?我的意思是,如果还没有用户输入,那么记录说 property1:value=hobby、property2:value=children 等是否有意义?
  • 是的,单值可能会变成多值。所有用户都可以选择相同的属性,但可以选择只设置少数而不是全部。是的,属性显示在用户的个人资料中,他/她可以设置这些属性,两者之间没有相对联系。谢谢!!!
  • 你的外键是什么?具体来说,您是否使用识别外键(即您是否将父主键转换为子主键)?
  • 当然。每个存储 propertyID 的表实际上都链接到 [dbo].[Property].[ID]。每个存储 valueID 的表实际上都链接到 [dbo].[Property_Values].[ID]。等等......但我不太明白它与我正在寻找的查询有什么关系(?)

标签: sql linkage cross-join set-theory


【解决方案1】:

数据模型

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

DDL SQL(MS SQL 服务器):

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_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

等等等等……

根据另一个表的内容查询

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

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(如果您不需要持久性) .

【讨论】:

  • 感谢您的深刻回答!但是,对每个属性进行存在查询不允许动态查询,当然对于大规模应用程序来说效率低下。正如我所提到的,我们还有另一个表:[dbo].[Search_Property_Values_User_Linkage],它是 USER_PROPERTY_VALUE 的副本,但代表我要过滤的属性。我正在寻找您实现的确切想法,只是动态的(例如,使用两个表之间的连接)。再次感谢,我真的很感激!
  • @S_S 请澄清您的意思是“动态”?您实际上是指带有绑定参数的查询(并且其 SQL 文本不需要更改)吗?还是您指的是基于另一个表的内容进行过滤(如果是,您打算如何使其在多用户环境中工作)?顺便说一句,这些子查询中的每一个都可能非常有效(如果索引构造正确) - 这实际上是嵌套循环连接所做的模拟。如果你有大量的性能可能会成为一个问题(即你搜索许多属性)。是这样吗?
  • @S_S BTW,您的模型似乎允许数据不一致 - 例如Property_Values_User_Linkage.PropertyID 可以不同于 Property_Values.propertyID(通过 Property_Values_User_Linkage.valueID 链接),因此无论查询如何,您都应该考虑使用标识关系。
  • “你指的是基于另一个表的内容进行过滤”和“如果你有大量它们(即你搜索许多属性),性能可能会成为问题。是这样吗? ?” - 两者都是。动态我的意思是比较表格 - 因为每个表格将来可能会有额外的数据。
  • @S_S 请参阅我的答案中的“基于另一个表的内容进行查询”编辑。
猜你喜欢
  • 2012-03-21
  • 2021-04-23
  • 1970-01-01
  • 2016-11-13
  • 1970-01-01
  • 2021-08-16
  • 2014-10-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多