简体   繁体   English

SQL 单独的字符串被传递

[英]SQL separate string being passed

I have a product table with a tag column, each product has multiple tags stored in this format: "|technology|mobile|acer|laptop|"...second product's tags could look like this "|computer|laptop|toshiba|"我有一个带有标签列的产品表,每个产品都有多个以这种格式存储的标签:“|technology|mobile|acer|laptop|”...第二个产品的标签可能看起来像这样“|computer|laptop|toshiba|”

I am using MS SQL Server 2008 and stored procedure, I would like to know how I could pass a string like "|computer|laptop|"我正在使用 MS SQL Server 2008 和存储过程,我想知道如何传递像“|computer|laptop|”这样的字符串and get both records returned as they both have the tag laptop in them and if I passed "|computer|"并返回两条记录,因为它们都带有笔记本电脑标签,如果我通过了“|computer|” only the second record would return as it is the only one comtainning that tag.只有第二条记录会返回,因为它是唯一包含该标签的记录。

What is the best way of doing this without performance penalties using stored procedure?使用存储过程在不影响性能的情况下执行此操作的最佳方法是什么?

I have so far had no luck with different codes i have found on the internet, I really hope you guys can maybe help me with this, thank you.到目前为止,我在互联网上找到的不同代码都没有运气,我真的希望你们能帮助我,谢谢。

You have a many-to-many relationship between products and tags.您在产品和标签之间存在多对多关系。 The best way of doing this is to redesign your database.最好的方法是重新设计您的数据库。 Create a table of tags and a junction table that links products with tags.创建一个标签表和一个将产品与标签链接起来的连接表。

That's not a very good design.这不是一个很好的设计。 Combining like terms into one field and separating them with a delimiter such as a vertical bar does not scale well and it is very limiting.将相似的术语组合到一个字段中并用分隔符(例如竖线)分隔它们不能很好地缩放并且非常有限。

I recommend you read up on how to design databases.我建议您阅读如何设计数据库。 The best book I ever bought regarding database design was Database Design for Mere Mortals by Michael Hernandez ISBN: 0-201-69471-9.我买过的关于数据库设计的最好的书是 Michael Hernandez 的Database Design for Mere Mortals ISBN:0-201-69471-9。 Amazon Listing I noticed he has a second edition. 亚马逊清单我注意到他有第二版。

He walks you through the entire process of (from start to finish) of designing a database.他将引导您完成(从头到尾)设计数据库的整个过程。 I recommend you start with this book.我建议你从这本书开始。

You have to learn to look at things in groups or chunks.你必须学会以组或块的形式看待事物。 Database design has simple building blocks just like programming does.就像编程一样,数据库设计具有简单的构建块。 If you gain a thorough understanding of these simple building blocks you can tackle any database design.如果您对这些简单的构建块有透彻的了解,您就可以应对任何数据库设计。

In programming you have:在编程中,您有:

  • If Constructs如果构造
  • If Else Constructs如果其他构造
  • Do While Loops执行 While 循环
  • Do Until Loops做直到循环
  • Case Constructs案例构造

With databases you have:使用数据库,您可以:

  • Data Tables数据表
  • Lookup Tables查找表
  • One to One relationships一对一关系
  • One to Many Relationships一对多关系
  • Many to Many relationships多对多关系
  • Primary keys主键
  • Foreign keys外键

The simpler you make things the better.你让事情变得越简单越好。 A database is nothing more than a place where you put data into cubbie holes.数据库只不过是将数据放入小房间的地方。 Start by identifying what these cubbie holes are and what kind of stuff you want in them.首先确定这些小房间的洞是什么,以及你想要什么样的东西。

You are never going to create the perfect database design the first time you try.第一次尝试时,您永远不会创建完美的数据库设计。 This is a fact.这是事实。 Your design will go through several refinements during the process.您的设计将在此过程中经过多次改进。 Sometimes things won't seem apparent until you start entering data, and then you have an ahh ha moment.有时,在您开始输入数据之前,事情似乎并不明显,然后您就有了一个哈哈大笑的时刻。

The web brings it's own sets of challenges. web 带来了自己的一系列挑战。 Bandwith issues.带宽问题。 Statelessness.无国籍状态。 Erroneous data from processes that start but never get finished.来自开始但从未完成的流程的错误数据。

I agree with the other posters that storing data in a column like that is going to cause headaches.我同意其他发帖人的观点,即在这样的列中存储数据会让人头疼。 You really want to store those tags in a child table so you can easily and efficiently join them.您真的想将这些标签存储在子表中,以便您可以轻松有效地加入它们。 If it's an inherited system or something you can't refactor right away you can write a split function.如果它是一个继承的系统或者你不能马上重构的东西,你可以编写一个拆分 function。

The typical sql split implementation uses a while loop and a table variable in a multi-statement TVF.典型的 sql 拆分实现在多语句 TVF 中使用 while 循环和表变量。 Every iteration incurs more I/O and CPU overhead.每次迭代都会产生更多的 I/O 和 CPU 开销。 Performance testing on SQL 2005 SP1 showed that this overhead is hidden from the I/O Stats and query plan.对 SQL 2005 SP1 的性能测试表明,此开销对 I/O 统计信息和查询计划是隐藏的。 Profiling the code will reveal the true cost.分析代码将揭示真正的成本。

Rewriting that function into a inline TVF is much more efficient.将 function 重写为内联 TVF 效率更高。 The primary difference between an inline and multi-statement TVF is the Query Optimizer will merge the inline function into the query before processing;内联和多语句 TVF 之间的主要区别在于查询优化器将在处理之前将内联 function 合并到查询中; this eliminates the overhead from the function call.这消除了 function 调用的开销。 Also, since there is no table variable required, the additional I/O cost is eliminated.此外,由于不需要表变量,因此消除了额外的 I/O 成本。 Finally, you avoid the costly iterative processing.最后,您避免了昂贵的迭代处理。

Here is the fastest, most scalable split function I could come up with including unit tests and summary.这是我能想到的最快、最具扩展性的拆分 function,包括单元测试和总结。

This function requires a numbers table:这个 function 需要一个数字表:

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 )

The source of the function is here: function的源码在这里:

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 <> ''
)

For your case, you could use it like this:对于您的情况,您可以像这样使用它:

--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.*/

But, with your tags in a separate table as suggested (1-many for a simplified example) It would look like this:但是,按照建议将您的标签放在单独的表格中(简化示例为 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 )  

make a split with CLR function return a table with the value or pass as xml and load it into a table varible an make a join使用 CLR function 进行拆分,返回具有该值的表或作为 xml 传递并将其加载到表变量中并进行连接

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

I would suggest you write an extra couplle of tables that with "proper design, Populate those tables from the existing not well designed bit - this way y our search will work properly buy others using the old | pipe approach won't notice till you have time to refactor我建议您编写一组额外的表格,这些表格具有“适当的设计,从现有的设计不佳的位中填充这些表格 - 这样我们的搜索将正常工作,使用旧的购买其他表格 | pipe 方法在您拥有之前不会注意到是时候重构了

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM