Настройка производительности по запросу сопоставления с образцом - PullRequest
0 голосов
/ 24 января 2019

У меня есть две таблицы:

Таблица 1: 100 строк

Таблица 2: 10 миллионов строк

Пример:

Таблица 1: tb100

create table tb100
(
    name varchar(50)
);

insert into tb100 values('Mak John'),('Will Smith'),('Luke W')......100 rows.

Таблица 2: tb10mil

create table tb10mil
(
    name varchar(50)
);

insert into tb10mil values('John A Mak'),('K Smith Will'),('James Henry')......10 millions rows.    

create nonclustered index nci_tb10mil_name  on tb10mil(name);

Примечание. Я хочу сопоставить имя между двумя таблицами, если оно есть WORD (Джон, СмитБудут представлены в другой таблице.Например, John присутствует в John A Mark.

Моя попытка:

Сначала я создал пользовательскую функцию для разбиения name из tb100 на строки.

Функция: udf_Split

CREATE FUNCTION [dbo].[udf_Split]
(
@InputString VARCHAR(8000), 
@Delimiter VARCHAR(50)
)
RETURNS @Items TABLE (ID INTEGER IDENTITY(1,1), Item VARCHAR(8000))

AS
BEGIN
      IF @Delimiter = ' '
      BEGIN
            SET @Delimiter = ','
            SET @InputString = REPLACE(@InputString, ' ', @Delimiter)
      END
      IF (@Delimiter IS NULL OR @Delimiter = '')
            SET @Delimiter = ','

      DECLARE @Item VARCHAR(8000)
      DECLARE @ItemList VARCHAR(8000)
      DECLARE @DelimIndex INT

      SET @ItemList = @InputString
      SET @DelimIndex = CHARINDEX(@Delimiter, @ItemList, 0)
      WHILE (@DelimIndex != 0)
      BEGIN
            SET @Item = SUBSTRING(@ItemList, 0, @DelimIndex)
            INSERT INTO @Items VALUES (@Item)

            SET @ItemList = SUBSTRING(@ItemList, @DelimIndex+1, LEN(@ItemList)-@DelimIndex)
            SET @DelimIndex = CHARINDEX(@Delimiter, @ItemList, 0)
      END 

      IF @Item IS NOT NULL 
      BEGIN
            SET @Item = @ItemList
            INSERT INTO @Items VALUES (@Item)
      END

      ELSE INSERT INTO @Items VALUES (@InputString)

      RETURN

END 

И тогда я написал следующий запрос:

;with splitdata as
(
    select f.item as data
    from tb100 t
    cross apply dbo.udf_split(t.name,' ') f
)
select t2.name 
from tb10mil t2
inner join splitdata c on charindex(c.data,t2.name)>0
group by t2.name

Выше выполнения запроса занимает более 20 минут времени для выполнения.

Ответы [ 3 ]

0 голосов
/ 24 января 2019

Давайте поговорим о производительности

  • Первый пункт: старайтесь избегать скалярных функций и старайтесь избегать TVF-операторов с несколькими утверждениями , если это возможно.Единственный быстрый подход - это inline-TVF ( однострочный оператор ).

  • Второй момент: избегайте петель, если когда-либовозможно!

  • Третий пункт ( самый первый на самом деле ): попытайтесь сохранить ваши данные в формате, оптимизированном для быстрых запросов.Хранение более одного значения в одной ячейке нарушает 1NF и приводит к огромной скорости убийцы.

Вы можете попробовать это:

Имитация ваших таблиц

CREATE TABLE #t100(ID INT IDENTITY,SomeName VARCHAR(200));
CREATE TABLE #t1M (ID INT IDENTITY,SomeName VARCHAR(200));

INSERT INTO #t100 VALUES('james smith'),('mak john'),('Luke W');
GO
INSERT INTO #t1M values('John A Mak'),('K Smith Will'),('James Henry'),('James John'),('Some other');
GO 

- Создать таблицы для отдельного хранения фрагментов имени (именно этот формат вы должны использовать вместо этого на самом деле)

CREATE TABLE #t100Splitted(ID INT IDENTITY PRIMARY KEY,ID_t100 INT,Fragment NVARCHAR(200));

--Use an inline XML-splitter
INSERT INTO #t100Splitted(ID_t100,Fragment)
SELECT ID
      ,B.frg.value('text()[1]','nvarchar(200)')
FROM #t100
CROSS APPLY(SELECT CAST('<x>' + REPLACE((SELECT SomeName AS [*] FOR XML PATH('')),' ','</x><x>') + '</x>' AS XML)) A(CastedToXml)
CROSS APPLY A.CastedToXml.nodes('/x[text()]') B(frg);

--add indexes
CREATE INDEX IX_t100_ID_t100 ON #t100Splitted(ID_t100);
CREATE INDEX IX_t100_Fragment ON #t100Splitted(Fragment);

--The same for the second table
CREATE TABLE #t1MSplitted(ID INT IDENTITY PRIMARY KEY,ID_t1M INT,Fragment NVARCHAR(200));

INSERT INTO #t1MSplitted(ID_t1M,Fragment)
SELECT ID
      ,B.frg.value('text()[1]','nvarchar(200)')
FROM #t1M
CROSS APPLY(SELECT CAST('<x>' + REPLACE((SELECT SomeName AS [*] FOR XML PATH('')),' ','</x><x>') + '</x>' AS XML)) A(CastedToXml)
CROSS APPLY A.CastedToXml.nodes('/x[text()]') B(frg);

CREATE INDEX IX_tM_ID_t100 ON #t1MSplitted(ID_t1M);
CREATE INDEX IX_tM_Fragment ON #t1MSplitted(Fragment);
GO

- Check the intermediate results
SELECT * FROM #t100Splitted;
SELECT * FROM #t1MSplitted;
GO

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

SELECT t1.ID_t1M
FROM #t1MSplitted t1
INNER JOIN #t100Splitted t2 ON t1.Fragment=t2.Fragment
GROUP BY t1.ID_t1M
GO

- Очиститьup

DROP TABLE #t100;
GO
DROP TABLE #t1M;
GO
DROP TABLE #t100Splitted;
GO
DROP TABLE #t1MSplitted;
GO

В моей системе 1 строка Mio была обработана примерно за 2 минуты.

UPDATE - Тесты производительности с 100 строками против 10M рядов

(с очень большим количеством обращений)

  • Просто разделение , чтобы преобразовать ваши данные в лучшую форму: ~ 17 минут

  • Окончательный выбор (только поиск): <1 минута </em>

  • Окончательный выбор но ищем идентификаторы маленькой таблицы: несколько секунд
    (после преобразования данных это будет нормальная скорость )

  • Подход PSK (с разделением строк): ~ 30 минут

0 голосов
/ 24 января 2019

Я попытался СОХРАНИТЬ ПАМЯТЬ и, таким образом, сэкономить время на обработку памяти, избегая соединений. Я пытался смоделировать вашу проблему на ~420k записях с 4 значениями в меньшей таблице.

Подход состоит в том, чтобы избежать объединения и ограничить проблемную область памяти от m x n до как минимум большего значения m & n.

select DISTINCT t2.name
from tb10mil  t2
where (SELECT TOP(1) 1 FROM #splitdata where CHARINDEX(data,t2.Problem)>0)=1 

РЕЗУЛЬТАТ: половина времени, которое потребовалось подходом, используемым в вопросе. (reduced from ~28 s to ~14s)

CON: Подход выгоден только в том случае, если одна из таблиц значительно мала

0 голосов
/ 24 января 2019

Вы можете попробовать следующим образом.

  ;WITH splitdata 
     AS (SELECT splitname 
         FROM   (SELECT *, 
                        Cast('<X>' + Replace(F.Name, ' ', '</X><X>') + '</X>' AS XML) 
                        AS  xmlfilter 
                 FROM   tb100 F)F1 
                CROSS apply (SELECT fdata.d.value('.', 'varchar(50)') AS splitName 
                             FROM   f1.xmlfilter.nodes('X') AS fdata(d)) O) 
SELECT DISTINCT t2.NAME 
FROM   tb10mil t2 
       INNER JOIN splitdata S 
               ON T2.NAME LIKE '%' + S.splitname + '%' 
...