Configurar
Recentemente trabalhei em uma otimização para uma Consulta SQL que retornou o Produto de Prescrição usado mais recentemente por um Paciente. A consulta original tratou isso por meio de 3 aninhados CTE
e teve um desempenho muito ruim após a migração para um novo servidor. Em vez de tentar fazer a versão original funcionar com ajustes de servidor ou alterações de índice, modificamos a consulta usando ROW_NUMBER()
partições. Isso levou o tempo de execução da consulta de cerca de 10 minutos para cerca de 6 segundos para aproximadamente 215.000 linhas na saída final.
Pergunta
Eu estou olhando para ver se há uma maneira melhor do que como eu o reescrevi, já que na minha mente isso quase parece um hack. Eu só quero ver se isso só está funcionando bem por causa do tamanho do nosso conjunto de dados ou se isso realmente é uma boa solução. Eu diria que preferimos usar algum tipo de solução que utiliza alguns JOIN
s diferentes que encontram esse tipo de informação em vez de usar ROW_NUMBER()
e um arquivo CTE
.
detalhes adicionais
Por causa desta pergunta, vou deixar de fora alguns detalhes em nosso banco de dados que são menos do que desejáveis (chaves primárias compostas, usando VarChar(1)
em vez de um BIT
, etc...) para que a raiz da pergunta possa ser melhor abordada.
Temos 4 tabelas no escopo:
Patient
- vamos apenas retornar a Coluna de Identidade desta tabelaOrderHeader
- contém informações sobre o pacote, como o endereço para o qual está indo. E é o registro pai paraOrderDetail
. Esta tabela está vinculada porPatient
meio de umaPatientID
coluna.OrderDetail
- cria uma relação entreOrderHeader
eProduct
para indicar quais Produtos foram na caixa real quando foi enviadoProduct
- mantém a lista de produtos
Estamos precisando de uma consulta que retorne para cada paciente, o OrderDetail
registro superior, para o OrderHeader
registro superior, onde o Product
registro vinculado ao OrderDetail
registro Product.IsRx
= 1.
Infelizmente, não podemos simplesmente obter o MAX
valor de OrderDetail.ID
where Product.IsRx
= 1, pois isso OrderDetail.ID
pode não pertencer ao MAX
valor de OrderHeader.ID
. Também não podemos confiar no MAX
valor para OrderHeader.ID
, pois não é garantido que tenha um registro, OrderDetail
nem nenhum registro lá é garantido que tenha a Product.IsRx
= 1
Resolvi esse problema com a consulta abaixo:
WITH CTE
(
PatientID,
OrderHeaderID,
OrderDetaillID,
ProductNDCCode,
ProductNDCDescription,
RowNumber
)
AS
(
SELECT P.ID AS 'PatientID',
H.ID AS 'OrderHeaderID',
D.ID AS 'OrderDetaillID',
P.NDCCode AS 'ProductNDCCode',
P.NDCDescription AS 'ProductNDCDescription',
ROW_NUMBER()
OVER
(
PARTITION BY P.ID
ORDER BY
P.ID ASC, --This part in the ORDER BY may not be needed, I apologize if it is unnecessary
SH.OrderHdrID DESC,
SD.OrderDtlID DESC
) AS 'RowNumber'
FROM Patient P
INNER JOIN OrderHeader H
ON P.ID = H.PatientID
INNER JOIN OrderDetail D
ON H.ID = D.OrderHeaderID
INNER JOIN Product PR
ON PR.ID = D.ProductID
WHERE PR.IsRx = 1
)
SELECT PatientID,
OrderHeaderID,
OrderDetaillID,
ProductNDCCode,
ProductNDCDescription
FROM CTE
WHERE RowNumber = 1
Uma orientação ou experiência que eu poderia obter seria muito útil.
Eu não consideraria isso um hack. As funções de janelas, incluindo ROW_NUMBER, são bastante eficientes e geralmente têm desempenho igual ou melhor que as alternativas. Em novas instâncias do SQL Server, presumo que FIRST_VALUE teria mais desempenho, mas não confirmei isso especificamente.
Observação: vi LAST_VALUE produzir resultados incorretos e recomendo evitar isso, optando por usar FIRST_VALUE com a classificação oposta.
Uma palavra de cautela, no entanto. Se seus critérios de pedido não forem específicos o suficiente para produzir um único "melhor" registro único (por exemplo, suponha que você omitiu OrderDtlID, qualquer um dos registros de detalhes para esse pedido pode ser o primeiro), então qual item é escolhido é funcionalmente aleatório (ou, mais precisamente , com base em variáveis ocultas que você não pode descobrir nem controlar facilmente em muitos casos). Se você mudar de ROW_NUMBER para RANK e sua candinalidade mudar (você começa a obter um número diferente de resultados), isso é um bom indicador de um problema. Também vale ressaltar que isso pode ser um problema com outros meios de escolher o registro mais recente/melhor, não apenas ROW_NUMBER.
Mesmo se você for indiferente ao que seria selecionado, os resultados de tal consulta podem não ser repetíveis, embora você não possa vê-lo imediatamente (as variáveis ocultas geralmente não mudam após cada consulta). Isso pode causar problemas de solução de problemas mais tarde e, se você executar qualquer tipo de ambiente paralelo (dev/qa), impossibilitar uma comparação precisa entre os dois.