Отдельная строка SQL передается - PullRequest
0 голосов
/ 13 августа 2011

У меня есть таблица продуктов со столбцом тегов, каждый продукт имеет несколько тегов, сохраненных в этом формате: «| технология | мобильный | acer | ноутбук |»... теги второго продукта могут выглядеть так: «| компьютер | ноутбук | toshiba |»

Я использую MS SQL Server 2008 и хранимую процедуру, я хотел бы знать, как я могу передать строку вроде «|компьютер | ноутбук |»и вернуть обе записи, так как в них есть тег ноутбука, и если я передал «| computer |»вернется только вторая запись, так как она содержит только этот тег.

Каков наилучший способ сделать это без потери производительности при использовании хранимой процедуры?

Мне до сих пор не повезлос разными кодами, которые я нашел в интернете, я очень надеюсь, что вы, ребята, возможно, поможете мне с этим, спасибо.

Ответы [ 5 ]

1 голос
/ 13 августа 2011

Я согласен с другими авторами, что хранение данных в такой колонке вызовет головную боль. Вы действительно хотите хранить эти теги в дочерней таблице, чтобы вы могли легко и эффективно присоединиться к ним. Если это унаследованная система или что-то, что вы не можете сразу же реорганизовать, вы можете написать функцию разделения.

Типичная реализация sql split использует цикл while и табличную переменную в TVF с несколькими операторами. Каждая итерация влечет за собой больше операций ввода-вывода и загрузки процессора. Тестирование производительности в SQL 2005 с пакетом обновления 1 (SP1) показало, что эти издержки скрыты от статистики ввода-вывода и плана запросов. Профилирование кода покажет истинную стоимость.

Переписать эту функцию в встроенный TVF гораздо эффективнее. Основное различие между встроенным TVF и оператором с несколькими утверждениями заключается в том, что Оптимизатор запросов объединит встроенную функцию в запрос перед обработкой; это устраняет накладные расходы при вызове функции. Кроме того, поскольку нет необходимости в табличной переменной, дополнительная стоимость ввода-вывода исключается. Наконец, вы избегаете дорогостоящей итеративной обработки.

Вот самая быстрая и наиболее масштабируемая функция разделения, которую я мог придумать, включая модульные тесты и сводку.

Для этой функции требуется таблица чисел:

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-многие для упрощенного примера) Это будет выглядеть так:

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 )  
1 голос
/ 13 августа 2011

Это не очень хороший дизайн. Комбинируя подобные термины в одно поле и разделяя их разделитель, такой как вертикальная полоса не очень хорошо масштабируется и это очень ограничивает.

Я рекомендую вам прочитать о том, как проектировать базы данных. Лучшая книга, которую я когда-либо покупал по проектированию баз данных была Проектирование баз данных для простых смертных Майкл Hernandez ISBN: 0-201-69471-9. Листинг Amazon Я заметил, что у него есть второе издание.

Он проведет вас через весь процесс (от начала до конца) проектирования базы данных. Я рекомендую вам начать с этой книгой.

Вы должны научиться смотреть на вещи в группах или куски. Проектирование баз данных имеют простые строительные блоки, так же, как программирование делает. Если вы получаете полное понимание этих простых строительных блоков вы можете справиться с любой дизайн базы данных.

В программировании у вас есть:

  • Если конструкты
  • If Else конструктов
  • Do While Loops
  • не делать до Loops
  • Case Создаёт

С базами данных у вас есть:

  • Таблицы данных
  • таблица поиска
  • один к одному отношения
  • один ко многим отношений
  • Многие до многих отношений
  • Первичные ключи
  • Внешние ключи

Чем проще вы делаете вещи, тем лучше. База данных не более чем место, где вы положили данные в cubbie отверстия. Начните с определения, что это за дырки в кубиках, и какие вещи вы в них хотите.

Вы никогда не создадите идеальный дизайн базы данных с первой попытки. Это факт. Ваш дизайн будет проходить несколько доработок в процессе. Иногда вещи не кажутся очевидными, пока вы не начнете вводить данные, и тогда у вас будет аааа момент.

Интернет приносит свои собственные проблемы. Bandwith проблемы. Безгражданства. Ошибочные данные от процессов, которые запускаются, но никогда не заканчиваются.

1 голос
/ 13 августа 2011

У вас есть отношение многие ко многим между товарами и тегами. Лучший способ сделать это - перепроектировать вашу базу данных. Создайте таблицу тегов и таблицу соединений, которая связывает продукты с тегами.

0 голосов
/ 13 августа 2011

Я бы предложил вам написать дополнительную пару таблиц с "правильным дизайном, Заполните эти таблицы из существующего не очень хорошо разработанного бита - таким образом, наш поиск будет работать правильно, покупая другие, используя старые | Подход к трубе не заметит, пока у вас не будет времени на рефакторинг

0 голосов
/ 13 августа 2011

сделать разделение с помощью функции CLR, вернуть таблицу со значением или передать в виде 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
...