Spiga

(MS SQL)如何实现相关文章功能(多关键字匹配)

2004-10-21 19:28 by 灵感之源, 3945 visits, 收藏, 编辑
前言:大家或许会觉得很惊讶:为什么灵感之源会讨论SQL?或许应该这样说吧:搞业务系统,不跟SQL扯上关系似乎比较难。

场景:在开发某系统的过程中,我遇到了要实现“相关文章”功能:任何文章都可以定义“关键字”,每篇文章依靠这个“关键字”来确定其它文章是否跟它相关,如果没有定义关键字,则可能需要使用全文检索来实现了,这是别的话题了。

思考:因为允许关键字可以通过“,”分隔符号来定义多个,所以加大了难度。经过思考,可以通过在保存文章的时候便分解关键字,建立一关键字表,把所有关键字逐个按对应的文章ID来保存。并决定采用纯SQL存储过程的办法,因为这种重复的操作,与其用通用函数,倒不如用预编译的存储过程更加快,这样能把所有处理都留給SQL Server。

解决方案:首选要做的是在原有文章表(Details)的基础上建立相关文章表(RelatedDetails),字段包括ItemID(主键)、DetailID(文章ID)和Keyword(关键字)。以下是主要存储过程:

1、UpdateRelatedDetails:更新相关文章关键字

CREATE procedure dbo.UpdateRelatedDetails

@DetailID 
INT,
@Keywords 
NVARCHAR(500)

AS

EXEC DeleteRelatedDetails @DetailID

DECLARE @I INT
DECLARE @Keyword NVARCHAR(50)

SET @Keywords=REPLACE(@Keywords,''',')
SET @Keywords=REPLACE(@Keywords,''',')
SET @Keywords=RTRIM(LTRIM(@Keywords))

SET @I=CHARINDEX(',', @Keywords)

WHILE @I>=1
    
BEGIN
        
SET @Keyword=LEFT(@Keywords, @I-1)
        
INSERT INTO DetailKeywords (DetailID, Keyword) VALUES(@DetailID, @Keyword)
        
SET @Keywords=SUBSTRING(@Keywords, @I+1,LEN(@Keywords)-@I)
        
SET @I=CHARINDEX(',', @Keywords)
    
END

IF @Keywords<>''
    
INSERT INTO DetailKeywords (DetailID, Keyword) VALUES(@DetailID, @Keywords)
GO

2、DeleteRelatedDetails:删除原有相关文章关键字
CREATE PROCEDURE dbo.DeleteRelatedDetails

@DetailID 
INT

AS

DELETE FROM DetailKeywords WHERE DetailID=@DetailID
GO

3、GetRelatedDetails:获取相关文章,其中Details就是文章表
CREATE procedure dbo.GetRelatedDetails

@DetailID 
INT

AS

DECLARE @Keywords NVARCHAR(500)

SELECT @Keywords=Keywords FROM Details WHERE ItemID=@DetailID

IF @Keywords<>''
    
BEGIN
        
SELECT DISTINCT d.ItemID, d.Subject, d.ItemFile
        
FROM Details d RIGHT OUTER JOIN DetailKeywords k ON k.DetailID=d.ItemID         WHERE d.ItemID <> @DetailID AND @Keywords LIKE '%'+k.Keyword+'%'
    
END
GO


代码很简单,但希望能給大家带来一点思考:)

p.s.因为前台是使用.NET写的,那这个帖子也算是部分.NET技术了,呵呵。
Add your comment

13 条回复

  1. #1楼 iCeSnaker2004-10-21 19:32
    可以留作参考之用,顺便占个座
     回复 引用   
  2. #2楼 boy119      2004-10-21 19:39
    提问:
    灵感为什么不在文章表(Details)中加入关键字字段而是新建一个表呢?
     回复 引用 查看   
  3. #3楼 boy119      2004-10-21 19:43
    不好意思,刚才没有看明白,现在明白了。之所以加入一个新表是为了把多个关键字分开,这样的数据库设计更加合理。
     回复 引用 查看   
  4. #4楼 Rover      2004-10-21 19:52
    数据库是合理了,但一个表膨胀的太厉害了,当我要查询一个同一个关键字的所有文章,要扫描这个表,这个表的记录太大了,效率高吗?
     回复 引用 查看   
  5. #5楼 boy119      2004-10-21 20:06
    但是,除了这个方法外,你还有效率更高的方法吗?
     回复 引用 查看   
  6. #6楼 吕震宇      2004-10-21 21:05
    一篇文章可能会有多个关键字,按照文章的比较仅仅是片面的比较。如果实现更复杂的功能的化,可以考虑使用关联分析,通过统计的方法进行关联度分析。所谓的全文检索,实际操作过程便是:分词、词频统计、根据词频统计信息建立反向检索库。当文章聚类时,实际上便是词频向量的聚类(这个过程可能需要降维)。使用神经网络也可以达到这个目的,CC4神经网络就是一个不错的算法。

    呵呵,以前做过一个项目,叫《模糊信息优化技术在异构数据库中的应用》,谈谈体会。
     回复 引用 查看   
  7. #7楼[楼主] unruledboy(灵感之源)      2004-10-21 21:09
    呵呵,晕吖,震宇兄,确实惭愧,我仅仅是实现最基本的功能,跟智能技术确实不能比较,呵呵。
     回复 引用 查看   
  8. #8楼 msolap      2004-10-26 23:08
    关键字使用单独的表来存储是必要的,特别是吕兄的方案。
    只是最后一个存储过程GetRelatedDetails的做法值得商榷,Rover的担心好像还是有点道理的。
    1. Details.Keywords字段和DetailKeywords表内的信息冗余,如果这个冗余能带来查询性能上的提升,那还可以考虑,但这个好像看不出...
    2. LIKE关键字导致表扫描,且DetailKeywords表Keyword字段上的索引派不上用场,可以预见这个执行效率会很低。事实上,关键字匹配的所有信息已经在DetailKeywords表里有了,就没有必要和Details表Join了,所以相信有更好的算法...
    3. 对写MS SQL存储过程再提点建议:存储过程开头最好加上
    SET NOCOUNT ON
     回复 引用 查看   
  9. #9楼 jeckey[未注册用户]2005-10-27 09:03
    我想问一下 如何在c++中实现括号的匹配问题.括号包含有:{},[];(). 谢谢大家
     回复 引用   
  10. #10楼 simeonz[未注册用户]2005-12-04 20:44
    最近正弄这个,gogole一下看到这贴...
    这问题好象不用存储过程,业务用select串直接解决好象很容易的可以用循环生成条件串where keyword like ... or keyword like ...
    但用存储过程还真有些难弄啊..呵呵..
    建个单独的表来存储好象没必要吧,直接用临时表算了..也就解决了Rover所担心问题..

    改进了一下上面的存储过程如下..参数@TopNum为显示的最大相关文章条数

    CREATE PROCEDURE [dbo].[SP_DOC_GetRelatedDocument]
    (
    @ID int, -- DocumentID
    @TopNum int
    )
    AS

    DECLARE @Keys nvarchar(100)
    DECLARE @I int
    DECLARE @Keyword nvarchar(50)

    CREATE TABLE #Temp (Keyword nvarchar(100))
    SET @Keys= (select Keyword from DB_DOC_Document where ID=@ID)
    SET @I=CHARINDEX('|', @Keys)

    WHILE @I>=1
    BEGIN
    SET @Keyword=LEFT(@Keys, @I-1)
    INSERT INTO #Temp ( Keyword) VALUES(@Keyword)
    SET @Keys=SUBSTRING(@Keys, @I+1,LEN(@Keys)-@I)
    SET @I=CHARINDEX('|', @Keys)
    END

    IF @Keys<>''
    INSERT INTO #Temp (Keyword) VALUES(@Keys)

    set rowcount @TopNum
    SELECT DISTINCT
    ID,
    Title,
    Author,
    Hits,
    AddedDateTime
    FROM
    DB_DOC_Document as d, #Temp as t
    WHERE
    d.Keyword LIKE '%'+t.Keyword+'%'
    and
    ID <>@ID
    and
    Audit =1
    ORDER BY
    AddedDateTime DESC
    set rowcount 0
    GO
     回复 引用   
  11. #11楼 网上购物[未注册用户]2006-04-10 16:20
    又学了一点小东东
     回复 引用   
  12. #12楼 net[未注册用户]2007-02-23 10:30
    不错 很受启发
     回复 引用   
  13. #13楼 simeonz[未注册用户]2008-04-12 22:48
    呵呵,一晃两年多了,没想到内容还在,这个问题还可以简化的,过两天有时间再来写个..
     回复 引用