2011-08-12 40 views
0

我有一个带有标签列的产品表,每个产品都有以这种格式存储的多个标签:“| technology | mobile | acer | laptop |” ...第二个产品的标签可能看起来像这样“|电脑|笔记本电脑| toshiba |”我正在使用MS SQL Server 2008和存储过程,我想知道如何传递像“| computer | laptop |”这样的字符串。并得到两个记录返回,因为他们都有标签笔记本电脑在他们,如果我通过“|电脑|”只有第二个记录会返回,因为它是唯一一个标记该标记的人。SQL单独传递的字符串

这样做没有使用存储过程的性能损失的最佳方式是什么?

到目前为止,我还没有运用我在互联网上找到的不同代码,我真的希望你们能帮助我,谢谢。

+0

是你的标签按字母顺序存储? – hova

回答

1

我同意其他海报将数据存储在像这样的列会引起头痛。您真的想将这些标签存储在子表中,以便您可以轻松高效地加入它们。如果它是一个继承的系统或者你不能马上重构的东西,你可以编写一个拆分函数。

典型的sql split实现在多语句TVF中使用while循环和表变量。每次迭代都会导致更多的I/O和CPU开销。 SQL 2005 SP1上的性能测试表明,这种开销对于I/O统计和查询计划是隐藏的。剖析代码将揭示真实成本。

将该函数重写为内联TVF效率更高。内联和多语句TVF之间的主要区别在于查询优化器将在处理之前将内联函数合并到查询中;这消除了函数调用的开销。而且,由于不需要表变量,所以额外的I/O成本被消除。最后,你避免了昂贵的迭代处理。

这是我能想出的最快,最具扩展性的拆分功能,包括单元测试和总结。

此功能需要一个数字表:

CREATE TABLE dbo.Numbers 
(
    NUM INT PRIMARY KEY CLUSTERED 
) 

;WITH Nbrs (n) AS 
(
    SELECT 1 UNION ALL 
    SELECT 1 + n FROM Nbrs WHERE n < 10000 
) 
INSERT INTO dbo.Numbers 
SELECT n FROM Nbrs 
OPTION (MAXRECURSION 10000) 

功能的来源是在这里:

IF EXISTS (
    SELECT 1 
    FROM dbo.sysobjects 
    WHERE id = object_id(N'[dbo].[ParseString]') 
     AND xtype in (N'FN', N'IF', N'TF')) 
BEGIN 
    DROP FUNCTION [dbo].[ParseString] 
END 
GO 

CREATE FUNCTION dbo.ParseString (@String VARCHAR(8000), @Delimiter VARCHAR(10)) 
RETURNS TABLE 
AS 
/******************************************************************************************************* 
* dbo.ParseString 
* 
* Creator:  MagicMike 
* Date:   9/12/2006 
* 
* 
* Outline:  A set-based string tokenizer 
*     Takes a string that is delimited by another string (of one or more characters), 
*     parses it out into tokens and returns the tokens in table format. Leading 
*     and trailing spaces in each token are removed, and empty tokens are thrown 
*     away. 
* 
* 
* Usage examples/test cases: 
       Single-byte delimiter: 
        select * from dbo.ParseString2('|HDI|TR|YUM|||', '|') 
        select * from dbo.ParseString('HDI| || TR |YUM', '|') 
        select * from dbo.ParseString(' HDI| || S P A C E S |YUM | ', '|') 
        select * from dbo.ParseString2('HDI|||TR|YUM', '|') 
        select * from dbo.ParseString('', '|') 
        select * from dbo.ParseString('YUM', '|') 
        select * from dbo.ParseString('||||', '|') 
        select * from dbo.ParseString('HDI TR YUM', ' ') 
        select * from dbo.ParseString(' HDI| || S P A C E S |YUM | ', ' ') order by Ident 
        select * from dbo.ParseString(' HDI| || S P A C E S |YUM | ', ' ') order by StringValue 

       Multi-byte delimiter: 
        select * from dbo.ParseString('HDI and TR', 'and') 
        select * from dbo.ParseString('Pebbles and Bamm Bamm', 'and') 
        select * from dbo.ParseString('Pebbles and sandbars', 'and') 
        select * from dbo.ParseString('Pebbles and sandbars', ' and ') 
        select * from dbo.ParseString('Pebbles and sand', 'and') 
        select * from dbo.ParseString('Pebbles and sand', ' and ') 
* 
* 
* Notes: 
        1. A delimiter is optional. If a blank delimiter is given, each byte is returned in it's own row (including spaces). 
         select * from dbo.ParseString3('|HDI|TR|YUM|||', '') 
        2. In order to maintain compatibility with SQL 2000, ident is not sequential but can still be used in an order clause 
        If you are running on SQL2005 or later 
         SELECT Ident, StringValue FROM 
        with 
         SELECT Ident = ROW_NUMBER() OVER (ORDER BY ident), StringValue FROM 
* 
* 
* Modifications 
* 
* 
********************************************************************************************************/ 
RETURN (
SELECT Ident, StringValue FROM 
    (
     SELECT Num as Ident, 
      CASE 
       WHEN DATALENGTH(@delimiter) = 0 or @delimiter IS NULL 
        THEN LTRIM(SUBSTRING(@string, num, 1)) --replace this line with '' if you prefer it to return nothing when no delimiter is supplied. Remove LTRIM if you want to return spaces when no delimiter is supplied 
      ELSE 
       LTRIM(RTRIM(SUBSTRING(@String, 
        CASE 
         WHEN (Num = 1 AND SUBSTRING(@String,num ,DATALENGTH(@delimiter)) <> @delimiter) THEN 1 
         ELSE Num + DATALENGTH(@delimiter) 
        END, 
        CASE CHARINDEX(@Delimiter, @String, Num + DATALENGTH(@delimiter)) 
         WHEN 0 THEN LEN(@String) - Num + DATALENGTH(@delimiter) 
         ELSE CHARINDEX(@Delimiter, @String, Num + DATALENGTH(@delimiter)) - Num - 
          CASE 
           WHEN Num > 1 OR (Num = 1 AND SUBSTRING(@String,num ,DATALENGTH(@delimiter)) = @delimiter) 
             THEN DATALENGTH(@delimiter) 
           ELSE 0 
          END 
         END 
        ))) 
       End AS StringValue 
     FROM dbo.Numbers 
     WHERE Num <= LEN(@String) 
      AND (
        SUBSTRING(@String, Num, DATALENGTH(ISNULL(@delimiter,''))) = @Delimiter 
        OR Num = 1 
        OR DATALENGTH(ISNULL(@delimiter,'')) = 0 
       ) 
    ) R WHERE StringValue <> '' 
) 

对于你的情况,你可以使用这样的:

--SAMPLE DATA 
CREATE TABLE #products 
(
    productid INT IDENTITY PRIMARY KEY CLUSTERED , 
    prodname VARCHAR(200), 
    tags VARCHAR(200) 
) 

INSERT INTO #products (prodname, tags) 
SELECT 'toshiba laptop', '|laptop|toshiba|notebook|' 
UNION ALL 
SELECT 'toshiba netbook', '|netbook|toshiba|' 
UNION ALL 
SELECT 'Apple macbook', '|laptop|apple|notebook|' 
UNION ALL 
SELECT 'Apple mouse', '|apple|mouse' 


--Actual solution 

DECLARE @searchTags VARCHAR(200) 
SET @searchTags = '|apple|laptop|' --This would the string that would get passed in if it were a stored procedure 

--First we convert the supplied tags into a table for use later 
--My (2005) dev box raised a severe error attempting to do the search in 1 step 
--hence the temp table 
CREATE TABLE #tags 
(
    tag VARCHAR(200) PRIMARY KEY CLUSTERED 
) 

INSERT INTO #tags --The function splits the string up into one record for each value 
SELECT stringValue 
FROM dbo.parsestring(@searchTags,'|') --SQL 2005 has a real problem joining to a TVF twice, apparently 


SELECT DISTINCT p.* 
FROM #products P --we join the products table with the function to get a row for each tag so we can compare with the temp table 
    CROSS APPLY (SELECT stringValue FROM dbo.parsestring(P.tags,'|')) T 
WHERE EXISTS(SELECT * FROM #tags WHERE tag = T.stringValue) --we compare the rows with our temp table and if we get matches, the products are returned 
/*This will return the Apple Macbook and the Toshiba Laptop because they both contain 
the 'laptop' tag and the Apple mouse because it contains the 'apple' tag. The 
toshiba netbook contains neither tag so it won't be returned.*/ 

但是,将您的代码放在建议的单独表格中(1-many代表简单示例)它看起来像这样:

SELECT * FROM Products P 
WHERE EXISTS (SELECT * 
        FROM tags T 
         INNER JOIN dbo.parsestring(@tags,'|') Q 
          ON T.tag = Q.StringValue 
        WHERE T.productid = P.productiId) 
+0

我真的无法解决这个问题,我想我可能在其他地方看过这个代码,但它仍然无法使用它。 – Cindro

+0

我添加了评论来解释解决方案。尝试一下,运行前两个块来创建解决方案将要使用的函数,然后运行第三个块来查看它的实际运行情况。在担心第一个和第二个街区之前,试着理解第三个街区。 –

+0

我只是做了详细的跟踪,但是我得到了以下错误: 关键字'DECLARE'附近的语法错误。 这是怎么了我的存储过程开始: CREATE PROCEDURE [DBO] [SPRelated] DECLARE @searchTags VARCHAR(200) SET @ searchTags = '|苹果|笔记本|'。 – Cindro

1

产品和标签之间有多对多的关系。这样做的最好方法是重新设计你的数据库。创建一个标签表和一个将产品链接到标签的联结表。

0

使与CLR函数分割返回一个表值或通过为XML,并将其加载到一个表varible的制作加入

create procedure search 
(
@data xml 
) 
AS 
BEGIN 

    --declare @data xml 
    declare @LoadData table 
    (
    dataToFind varchar(max) 
) 
    --set @data= cast(
    --'<data> 
    -- <item>computer</item> 
    -- <item>television</item> 
    --</data>' as xml) 

    insert into @LoadData 
    SELECT T2.Loc.value('.','varchar(max)') 
    FROM (select @data as data)T 
    CROSS APPLY data.nodes('/data/item') as T2(Loc) 

    select * from @LoadData--use for join 

END 
+0

我该怎么做?存储过程绝对不是我最强的一点。 – Cindro

1

这不是一个很好的设计。将类似术语合并到一个字段中并用分隔符(例如垂直条)分隔它们并不能很好地扩展,这是非常有限的。

我建议你阅读如何设计数据库。我曾经购买过的关于数据库设计的最好的书是单纯人生的数据库设计作者:Michael Hernandez ISBN:0-201-69471-9。 Amazon Listing我注意到他有第二版。

他会引导您完成设计数据库的整个过程(从开始到结束)。我建议你从这本书开始。

你必须学会​​以团体或组块的形式看待事物。数据库设计与编程一样具有简单的构建块。如果您深入了解这些简单构建模块,则可以解决任何数据库设计问题。

在编程,有:

  • if结构
  • 如果else结构
  • do-while循环
  • 做,直到循环
  • 案例构造

你所拥有的数据库:

  • 数据表
  • 查找表
  • 一对一关系
  • 一对多关系
  • 多对多关系
  • 主键
  • 外键

的越简单,你就越好。数据库只不过是将数据放入cubbie孔的地方。首先确定这些cubbie孔是什么以及你想要什么样的东西。

你永远不会在第一次尝试时创建完美的数据库设计。这是事实。在此过程中,您的设计将经历几次改进。有时在你开始输入数据之前,事情似乎并不明显,然后你有一个时刻。

网络带来了它自己的一套挑战。带宽问题。无国籍。来自进程的错误数据,但始终没有完成。

0

我建议你写表以“适当的设计, 填充从现有不是精心设计的位这些表的额外couplle - 这样Ÿ我们的搜索将正常工作,购买使用旧人|管道方法将不会通知,直到你有时间重构