SQL Server - 选择关键字列表和同义词

时间:2011-04-14 20:24:32

标签: sql sql-server sql-server-2005 tsql

我有两张桌子:

关键字

我存储唯一关键字。

CREATE TABLE [dbo].[Keywords]
[KeywordID] [int] IDENTITY(1,1) NOT NULL,
[Description] [varchar](200) NOT NULL

select * from Keywords   

  1 MVC  
  2 HTML  
  3 C#  
  4 ASP.NET MVC  
  5 MVC3

KeywordSynonymous

我将某些关键字指向其他关键字的同义词。

CREATE TABLE [dbo].[KeywordSynonymous]
    [KeywordID] [int] NOT NULL,
    [KeywordSynonymousID] [int] NOT NULL

这两个字段都是关键字表格的FK,两个组合字段在此表格中用作PK。

在这里我要说'MVC'和'MVC3'是同义词,也许'MVC3'和'ASP.NET MVC'也是同义词。

select * from KeywordSynonymous  

1 5  
5 4  

CONCEPTS

1)

如果关键字'MVC'是'MVC3'的同义词 'MVC3'是'ASP.NET MVC'的同义词

  
    

然后概念上MVC ALSO 'ASP.NET MVC'的同义词

  

2)

如果关键字'MVC'是'MVC3'的同义词

  
    

那么 VICEVERSA 也是如此,'MVC3是'MVC'的同义词

  

问题

想象一下,在我的网站上,我正在进行搜索,用户可以输入任何内容,但对于我们的示例,他可以键入“MVC”或“MVC3”......

如何使用一个SQL语句获取所有可能的同义词,以确保满足Concept 1和2?

意思是:

>> if the user types 'MVC',        my sql should return 'MVC, MVC3', 'ASP.NET MVC'.  
>> if the user types 'MVC3',       my sql should return 'MVC, MVC3', 'ASP.NET MVC'.  
>> if the user types 'ASP.NETMVC', my sql should return 'MVC, MVC3', 'ASP.NET MVC'.  

============================================ ====================
更新
我觉得我必须补充一下我正在开发的网站。这是一个市场,年轻的专业人​​士将能够以新的方式推销自己的服务。

由于我们想允许任何职业,我现在无法预见“关键字”将更好地定义每个职业。所以我将允许用户定义这些关键字。

我的问题是我需要允许UserX按专业和关键字搜索这些年轻的专业人​​士。我需要允许这些用户将搜索到的关键字与现有关键字进行匹配,以便当前和将来的搜索会自动匹配正确的配置文件。

这就是为什么我没有预先提供所有关键字,肯定无法识别未来的关键字及其各自的同义词。我也不能指望用户将所有现有的关键字与所有相关的关键字匹配......这就是我需要Concept 1工作的原因。

============================================ ====================
STACKOVERFLOW TAGS
关键字的模块应该与StackOverflow标签(关键字)非常相似,如果我将TAGS设置为SQL,那么你们正在搜索TSQL或SQL SERVER ......也应该看到这篇帖子。

:-)

5 个答案:

答案 0 :(得分:3)

你绝对应该使用Common Table Expressions。这是解决您问题的理想解决方案,因为它不会更改您当前的数据库架构,最重要的是,由于KeywordSynonymous表具有递归性,CTE是一种优雅且合理的解决方案。

要实现这一点,最好先创建一个视图,在两个方向上选择KeywordSynonymous中的所有行。在您的情况下,此表返回行

select * from KeywordSynonymous  

1 5  
5 4  

以下视图将显示

select * from KeywordSynonymousAll   

1 5      0   
2 NULL   0
3 NULL   0
4 NULL   0
4 5      1
5 1      1
5 4      0

此视图是简化递归查询的数据结构。它添加了第三列以确定何时进行了还原。这是满足您的概念2所必需的。

所以,这是视图:

create view KeywordSynonymousAll as
    select KeywordID, KeywordSynonymousID, 0 as reversed
      from KeywordSynonymous
     union
    select K.KeywordID, null as KeywordSynonymousID, 0 as reversed
      from Keywords K
     where not exists(select null
                        from KeywordSynonymous
                       where KeywordID = K.KeywordID)
     union
     select KeywordSynonymousID, KeywordID, 1 as reversed
       from KeywordSynonymous

和查询

declare @search varchar(200);

set @search = 'MVC3'; -- TEST HERE for different search keywords

with Synonymous (keywordID, SynKeywordID) as ( 

    -- initial state: Get the keywordId and KeywordSynonymousID for the description as @search
    select K.keywordID, KS.KeywordSynonymousID
      from Keywords K
     inner join KeywordSynonymous KS on KS.KeywordID = K.keywordId
     where K.Description = @search

    union all

    -- also initial state but with reversed columns (because we want lookup in both directions)
    select KS.KeywordSynonymousID, K.keywordID
      from Keywords K
     inner join KeywordSynonymous KS on KS.KeywordSynonymousID = K.keywordId
     where K.Description = @search

     union all

    select S.SynKeywordID, KS.KeywordSynonymousID
      from Synonymous S
     inner join KeywordSynonymousAll KS on KS.KeywordID = S.SynKeywordID
     where KS.reversed = 0 -- to avoid infinite recursion

     union all

    select KS.KeywordSynonymousID, S.SynKeywordID 
      from Synonymous S
     inner join KeywordSynonymousAll KS on KS.KeywordID = S.KeywordID
     where KS.reversed = 1 -- to avoid infinite recursion

) 

-- finally output the result
select distinct K.Description
  from Synonymous S
 inner join Keywords K on K.KeywordID = S.keywordID

对于set @search = 'MVC3',结果集为

  ASP.NET MVC
  MVC
  MVC3

set @search = 'MVC'set @search = 'ASP.NET MVC'

的结果集相同

对于set @search = 'C#'set @search = 'HTML',您什么都得不到

修改

在我之前的帖子中,我说过C#和HTML的结果集是空的。如果您还想返回这些值,请将查询的最后部分更改为:

-- finally output the result
select distinct T.Description
  from (
    select K.Description
      from Synonymous S
     inner join Keywords K on K.KeywordID = S.keywordID

    union

    select Description
      from Keywords
     where Description = @search) T

现在,对于set @search = 'C#',结果集为

  C#

set @search = 'HTML',结果集为

  HTML

希望这有帮助

答案 1 :(得分:2)

要达到至少#1,您可以使用recursive Common Table Expressions (CTE)
定义here

答案 2 :(得分:2)

由于您的情况(概念),Synonymous表未规范化。这是您的问题的主要来源以及解决问题所需的复杂查询/触发器。

我会保留关键字表:

CREATE TABLE [dbo].[Keywords]
[KeywordID] [int] IDENTITY(1,1) NOT NULL,
[Description] [varchar](200) NOT NULL

select * from Keywords   

  1 MVC  
  2 HTML  
  3 C#  
  4 ASP.NET MVC  
  5 MVC3
  6 C sharp

并使Synonymous表格不同:

CREATE TABLE [dbo].[KeywordSynonymity]
    [SynonymityID] [int] NOT NULL,
    [KeywordID] [int] NOT NULL

select * from KeywordSynonymous  

1 1               --- for the 1 (MVC) and 5 (MVC3)
1 5               --- being synonymous
2 3               --- for the 3 (C#) and 6 (C sharp)
2 6               --- being synonymous

然后,要添加MVC3ASP.NET MVC也是同义词,您只需在同义词表中添加一行(1,4)。

如果那时 - 由于未知原因但我们假设 - 您希望将MVC3C#组合为同义词,则必须使用SynonymityID = 2(与C#同义)更改所有行to = 1(与MVC同义)。

但是随着表的规范化,所有查询都会更简单。

答案 3 :(得分:1)

1称为Symmetric Relation,2称为Transitive Relation

我建议您在添加新关键字时对其进行维护。你可以这样做。将关键字添加到数据库时,如果已经没有它的同义词,请将其指定为“master”关键字。否则将new关键字链接到现有的master关键字。

以下是以这种方式添加新关键字的存储过程:

CREATE PROCEDURE [dbo].[AddKeyword] 
    @newKeyword [varchar](200), 
    @synonymKeyword [varchar](200) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    set transaction isolation level serializable

    begin transaction

        if EXISTS (select 1 from Keywords where [Description] = @newKeyword)
        begin
            commit transaction
            return
        end

        declare @masterKeywordId int

        select 
            @masterKeywordId = ISNULL(KeywordSynonymous.KeywordID, Keywords.KeywordID) 
        from
            Keywords
        left join
            KeywordSynonymous
        on
            Keywords.KeywordID = KeywordSynonymous.KeywordSynonymousID
        where
            [Description] = @synonymKeyword

        insert into Keywords VALUES (@newKeyword)

        if @masterKeywordId is not null
            insert into KeywordSynonymous VALUES (@masterKeywordId,SCOPE_IDENTITY())

    commit transaction

END

在此存储过程中,您传递一个新关键字以进行添加,并且您也可以选择传递已知的同义词。这个同义词不一定是“主人”。如果它存在,它将被查找“master”关键字id,并且新创建的关键字将与该“master”id链接。

这就是你最终选择它们的方式:

CREATE PROCEDURE [dbo].[GetSynonymKeywords]
    @keyword [varchar](200)
AS
BEGIN
    SET NOCOUNT ON;

    declare @masterKeywordId int

    select 
        @masterKeywordId = ISNULL(KeywordSynonymous.KeywordID, Keywords.KeywordID) 
    from
        Keywords
    left join
        KeywordSynonymous
    on
        Keywords.KeywordID = KeywordSynonymous.KeywordSynonymousID
    where
        [Description] = @keyword

    select 
        KeywordId,[Description]
    from
        Keywords
    where
        KeywordId = @masterKeywordId
    union
    select 
        Keywords.KeywordId,[Description]
    from
        KeywordSynonymous
    join
        Keywords
    on
        KeywordSynonymous.KeywordSynonymousID = Keywords.KeywordId
    where
        KeywordSynonymous.KeywordId = @masterKeywordId

END

此存储过程首先查找给定传递关键字的关键字ID。然后它会查找此ID的“master”关键字。然后它返回master关键字和所有与该master关键字同义的关键字。

添加新单词的示例:

EXEC [dbo].[AddKeyword] @newKeyword = N'MVC'
EXEC [dbo].[AddKeyword] @newKeyword = N'ASP.NET MVC',   @synonymKeyword = 'MVC'
EXEC [dbo].[AddKeyword] @newKeyword = N'MVC3',  @synonymKeyword = 'ASP.NET MVC'

请注意,在第三行中您可以将“MVC”指定为同义词,它也可以正常工作。

检索关键字的示例:

[dbo].[GetSynonymKeywords]  @keyword = N'MVC3'
[dbo].[GetSynonymKeywords]  @keyword = N'ASP.NET MVC'
[dbo].[GetSynonymKeywords]  @keyword = N'MVC3'

所有三个都返回相同的值列表。

我将隔离级别放在AddKeyword SP中进行序列化,以确保没有并发问题随意根据您的并发模型进行修改,序列化可能不适合您。

如果您愿意,也可以将GetMasterId(两个SP中出现的块)拉出到UDF中,或者执行适合您特定场景的任何其他修改。

答案 4 :(得分:0)

好的,那么这个怎么样:

DECLARE @TempKeywordID TABLE (KeywordID int)
INSERT INTO @TempKeywordID (KeywordID)(select KeywordID from Keywords where [Description] = @SearchKeyword)

DECLARE @intFlag INT
SET @intFlag = 1

WHILE (@intFlag <=(Select Count(KeywordSynonymousID) from KeywordSynonymous)) --Loop for all records in KeywordSynonymous
BEGIN
    INSERT INTO @TempKeywordID (KeywordID)(Select KeywordSynonymousID from KeywordSynonymous where KeywordID in (Select KeywordID from @TempKeywordID))
    INSERT INTO @TempKeywordID (KeywordID)(Select KeywordID from KeywordSynonymous where KeywordSynonymousID in (Select KeywordID from @TempKeywordID))    

    SET @intFlag = @intFlag + 1
END

SELECT * FROM Keywords WHERE KeywordID IN (SELECT * FROM @TempKeywordID)