我有两张桌子:
关键字
我存储唯一关键字。
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 ......也应该看到这篇帖子。
:-)
答案 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
然后,要添加MVC3
和ASP.NET MVC
也是同义词,您只需在同义词表中添加一行(1,4)。
如果那时 - 由于未知原因但我们假设 - 您希望将MVC3
和C#
组合为同义词,则必须使用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)