2016-09-28 4 views
4

У меня есть запрос, который выглядит примерно так:Улучшение производительности «обратный» LIKE запрос

SELECT CustomerId 
FROM Customer cust 
WHERE 'SoseJost75G' LIKE cust.ClientCustomerId + '%' -- ClientCustomerId is SoseJost 

тэк, что это делает, я получаю значение от клиента, который мой ClientCustomerId но с неизвестным количеством дополнительных символов, прикрепленных к концу.

Так в моем примере, клиент дает мне SoseJost75G, но моя база данных имеет только SoseJost (без 75G на конце.)

Мой запрос работает работает. Но это займет минуту, чтобы бежать. Это связано с тем, что он не может использовать индекс, который находится на ClientCustomerId.

Кто-нибудь знает, как улучшить производительность такого типа запросов?

+0

Я не могу многое сделать. Здесь нужно будет отсканировать индекс. Есть ли какой-то способ, которым вы можете заставить их передать вам действительный номер вместо действительного числа с каким-то другим материалом, застрявшим в конце? –

+0

Имеет ли бит на конце какой-либо согласованный шаблон? например, всегда 3 символа или всегда начинается с числа (а идентификатор - нет)? – Bohemian

+0

Можете ли вы вернуть похожие совпадающие записи, и пользователь может выбрать правильный? 'WHERE ClientCustomerId LIKE LEFT ('SoseJost75G', 5) + '%'' – seagulledge

ответ

3

Вы могли бы попробовать что-то вроде этого:

DECLARE @var VARCHAR(100)='SoseJost75G'; 

WITH pre_selected AS 
(SELECT * FROM Customer WHERE ClientCustomerId LIKE LEFT(@var,6) + '%') 
SELECT * 
FROM pre_selected WHERE @var LIKE ClientCustomerId +'%'; 

С в LIKE с начала починки -search будет использоваться существующий индекс ClientCustomerId.

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

Если заказ выполнен не так, как вы ожидаете этого, вы можете вставить результат первого CTE-запроса в объявленную переменную (только столбец с идентификатором), а затем продолжить эту крошечную таблицу ...

Нечто подобное

DECLARE @var VARCHAR(100)='SoseJost75G'; 

DECLARE @CustIDs TABLE(ClientCustomerID VARCHAR(100)); 
INSERT INTO @CustIDs(ClientCustomerID) 
SELECT ClientCustomerID FROM Customer WHERE ClientCustomerId LIKE LEFT(@var,6) + '%'; 

--Use this with an IN-clause then 
SELECT ClientCustomerId 
FROM @CustIDs WHERE @var LIKE ClientCustomerID +'%' 
1

Если вы можете определить минимальную длину ClientCustomerId, например, она никогда не может быть менее четырех символов, вы можете ограничить результаты следующим образом:

WHERE ClientCustomerId like left('SoseJost75G', 4) + '%' 

Здесь индекс может быть использован, чтобы получить совпадающие записи. Ваши критерии

AND ClientCustomerId <= 'SoseJost75G' and ClientCustomerId 

необходимо искать только в записях, которые уже найдены.

Полный запрос:

SELECT CustomerId 
FROM Customer cust 
WHERE ClientCustomerId like left('SoseJost75G', 4) + '%' 
AND ClientCustomerId <= 'SoseJost75G' and ClientCustomerId; 

BTW: Ваши критерии также могут быть записаны как

ClientCustomerId = left('SoseJost75G', length(ClientCustomerId)) 

, но я полагаю, что это не быстрее, чем версия.

+0

К сожалению.Кажется, это примерно тот же ответ, что и у Shnugo :-( –

2

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

DECLARE @customerIdSubstring varchar(255) = 'SoseJost75G' 
DECLARE @customerIdSubstringLength INT 
DECLARE @results TABLE 
(
    CustomerId varchar(255) 
) 


DECLARE @FoundResults BIT = 0; 

WHILE (@FoundResults = 0) 
BEGIN 

    INSERT INTO @results (CustomerId) 
    SELECT CustomerId 
    FROM Customer cust 
    WHERE CustomerId = @customerIdSubstring 


    SELECT @FoundResults = CASE 
           WHEN EXISTS(SELECT * FROM @results) THEN CAST(1 AS BIT) 
           ELSE CAST(0 AS BIT) 
          END 

    SET @customerIdSubstringLength = LEN(@customerIdSubstring) 

    -- We don't want to match on fewer than 3 chars. (May not be correct at that point.) 
    IF (@customerIdSubstringLength < 3) 
     BREAK; 

    SET @customerIdSubstring = LEFT(@customerIdSubstring, @customerIdSubstringLength - 1) 
END 

SELECT CustomerId 
FROM @results 

Хотя возможно, что я буду запускать запрос много раз. Inpractice, это будет 3-6 раз за значение. Я думаю, что 3-6 запросов индекса лучше, чем 1 поиск и 1 сканирование.

Это также имеет дополнительное преимущество в возврате только самых «LIKE» рядов. (Это означает, что строки, которые имеют SanJos, не возвращаются, если есть строки, которые имеют SanJost.)

+0

Это ответ на вашу проблему? – Andrew

+0

Вы можете удалить этот 'CASE', просто сделав' SELECT @FoundResults = COUNT (*) FROM @ results'. – Andrew

+0

Вы также можете упростить его, просто используя 'LEN' один раз в начале, а затем просто уменьшая переменную длины. Я бы также использовал' WHILE (@FoundResults = 0 AND @customerIdSubstringLength> = 3) 'для удаления этого' break' и сделать код более читаемым. – Andrew

1

Мне понравился ваш подход, Vaccano. Я просто упростили его немного, в случае, если вы заинтересованы:

DECLARE @customerIdSubstring varchar(255) = 'SoseJost75G' 
DECLARE @results TABLE 
(
    CustomerId varchar(255) 
) 

DECLARE @FoundResults BIT = 0 
DECLARE @customerIdSubstringLength INT = LEN(@customerIdSubstring) 

WHILE (@FoundResults = 0 AND @customerIdSubstringLength >= 3) 
BEGIN 
    INSERT INTO @results 
    SELECT CustomerId 
    FROM Customer 
    WHERE CustomerId = @customerIdSubstring 

    -- Make @FoundResults = 1 if there's at least one record 
    SELECT TOP 1 @FoundResults = 1 FROM @results 

    SET @customerIdSubstringLength = @customerIdSubstringLength - 1 
    SET @customerIdSubstring = LEFT(@customerIdSubstring, @customerIdSubstringLength) 
END 

SELECT CustomerId 
FROM @results 

Если вы полностью уверены, что только один идентификатор будет соответствовать, вы можете упростить это еще дальше путем удаления таблицы результатов, которые будут иметь только одну строку , Я также удалил задание @customerIdSubstring в цикле:

DECLARE @customerIdSubstring varchar(255) = 'SoseJost75G' 
DECLARE @customerIdFound varchar(255) 

DECLARE @customerIdSubstringLength INT = LEN(@customerIdSubstring) 

WHILE (@customerIdFound IS NULL AND @customerIdSubstringLength >= 3) 
BEGIN 
    SELECT @customerIdFound = CustomerId 
    FROM Customer 
    WHERE CustomerId = LEFT(@customerIdSubstring, @customerIdSubstringLength) 

    SET @customerIdSubstringLength = @customerIdSubstringLength - 1 
END 

SELECT @customerIdFound 
+0

Я думал об этом с CTE, но думаю, что это невозможно. – Andrew

0

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

SARG = Поиск Довод

sargable запросов позволяет оптимизатору использовать индексы, в то время как для не sargable запросов оптимизатор будет сканировать все строки в таблице, даже индексы доступны.

LIKE с% в конце является способным к переносу. ЛЮБОВЬ, потому что% в начале НЕ ПОДХОДИТ. Применение такой функции, как LEFT ([Column], 4) + '%' в предложении WHERE делает запрос неприемлемым. По крайней мере, это говорит о документации SARG.

[COLUMN] LIKE 'abc%' -> sargable 
[COLUMN] LIKE '%abc' -> not sargable 
[COLUMN] LIKE LEFT('ABCDE', 4) -> not sargable 

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

Это тем более предпочтительный процесс, потому что вы не знаете, какие данные вы получаете.

Смежные вопросы