Eu tenho uma função que pesquisa e classifica para ser usada em estoque e números de peças. Tentamos a pesquisa de texto completo e a pesquisa cognitiva do Azure e foi uma longa jornada. Preciso corresponder e classificar palavras arbitrárias definidas como "Delimitado por espaço". Em tabelas com mais de 75 mil registros, a consulta leva de 3 a 5 segundos.
O seguinte demonstra o que tenho funcionando, mas gostaria de otimizar:
Configurar:
IF OBJECT_ID(N'dbo.MyTable', N'U') IS NOT NULL DROP TABLE MyTable;
CREATE TABLE MyTable (
Id int NOT NULL,
Data varchar(255),
PRIMARY KEY (Id)
);
INSERT INTO MyTable VALUES (1, 'A CAR'), (2, 'A BIKE'), (3, 'CAR WASH'), (4, 'CAR BIKE RACK'), (5, 'A HOUSE');
IF OBJECT_ID('dbo.fn__SearcRankMyTable') IS NOT NULL DROP FUNCTION fn__SearcRankMyTable
GO
A função:
CREATE FUNCTION [dbo].[fn__SearcRankMyTable]
(
@Id INT,
@Query VARCHAR(MAX)
)
RETURNS INT
AS
BEGIN
DECLARE @Result INT;
WITH T AS (
SELECT B.[value] FROM MyTable
CROSS APPLY STRING_SPLIT([Data], ' ') as A
CROSS JOIN STRING_SPLIT(@Query, ' ') AS B
WHERE Id = @id AND A.value LIKE b.[value] + '%'
)
SELECT @Result = COUNT(Distinct(value)) FROM T
RETURN @Result
END
GO
Consulta de uso pretendido:
SELECT S.*, Rank FROM MyTable S
CROSS APPLY ( SELECT [dbo].[fn__SearcRankMyTable](S.iD, 'CAR BIKE RACK')) ca (Rank)
WHERE RANK > 0
ORDER BY Rank DESC;
Esta consulta acima para "CAR BIKE RACK"
produz o seguinte conforme desejado:
Comportamento de pesquisa definido:
- Suponha que a linha já tenha calculado a coluna (dados) de palavras distintas:
- Para cada palavra de consulta que corresponda ao início de uma palavra na linha de dados, incremente a classificação. A classificação mais alta possível será o número de palavras na consulta.
- A consulta de pesquisa pode ter no máximo 4 palavras, pode ser passada dividida em palavras do lado do servidor como passada se ajudar.
Próximas coisas para tentar:
- Eu li que as funções com valor escalar são lentas (mais) / thread único. Tente converter para a função Table Valued.
- Dentro do
CROSS APPLY STRING_SPLIT([Data], ' ') as A
e o últimoDistinct
seria bom ter uma maneira de resgatar. Esta será minha próxima abordagem.
Existem outras considerações/tecnologias/abordagens que devo considerar?
Otimização com RETURN SELECT usando WHERE EXISTS (60% mais lento, infelizmente)
CREATE FUNCTION [dbo].[fn__SearcRankMyTable]
(
@Id INT,
@Query VARCHAR(MAX)
)
RETURNS INT
AS
BEGIN
RETURN (SELECT COUNT(*) FROM STRING_SPLIT(@Query, ' ') as A
WHERE EXISTS (SELECT value FROM STRING_SPLIT((SELECT DATA FROM MyTable WHERE Id = @Id), ' ') WHERE [value] LIKE a.[value] + '%' ));
END
GO
Para os seguintes dados de exemplo
No WPR, seu método atual se parece com isso
Há uma classificação para a classificação final e outras classificações internas para a contagem distinta, bem como o tempo de CPU gasto na divisão da string (potencialmente piorada ao lidar com tipos de dados LOB)
Um método mais rápido que funciona com sua estrutura existente seria
Esse
@Query
como um tipo de dados não máximo, pois não parece necessáriomax
e pode ser mais lento.@Query
uma vez em vez de refazer a divisão para cada linha emMyTable
RANK
para remover alguns operadores do plano de execução - incluindo a classificação interna.CHARINDEX
pesquisa (NB: Se você puder garantir que todos osData
serão armazenados em uma forma canônica de maiúsculas, faça-o e remova essaUPPER(Data)
chamada para evitar gastar tempo de CPU fazendo isso em tempo de execução)Com esses dados fictícios, descobri que o método revisado é executado em cerca de 100 ms na minha máquina (o método original era de cerca de 3 segundos).
A cláusula de agrupamento binário fez uma redução de 500ms para 100ms, mesmo com a sobrecarga adicional de chamada,
UPPER
mas se os dados puderem ser armazenados em letras maiúsculas na tabela, isso economizará algum tempo adicional de CPU, pois não há necessidade de chamar,UPPER(Data)
o que removerá a operação destacada (reduz para ~ 65 ms para mim).você pode tentar uma tabela de contagem para uma chamada de função de valor de tabela ...
então você pode chamar a função de valor da tabela semelhante a como você chama a função string_split
Basicamente, peguei sua tabela de amostra e criei uma tabela de registros de 100k no SQL Express e obtive um conjunto de resultados em 1 segundo. Se você puder fazer o pedido na camada do aplicativo, isso também pode ajudar no desempenho.
ao usar a função de divisão de string, quanto mais longa a string que você passar, pior será o plano de execução, porque o mecanismo não tem ideia de quantas palavras há em sua string.
[B.Ozar fez uma postagem no blog sobre isso recentemente.][1]