Tenho os seguintes dados:
ID do usuário | SubID | EduId | SemOrd |
---|---|---|---|
1 | 701 | 500 | 10 |
1 | 702 | 255 | 11 |
1 | 703 | 500 | 12 |
1 | 704 | 500 | 14 |
1 | 705 | 500 | 15 |
1 | 706 | 500 | 16 |
1 | 707 | 500 | 17 |
2 | ... | ... | .. |
Dado um UserId
e SubId
, quero encontrar o menor SemOrd
na mesma EduId
cadeia.
Por exemplo: dado UserId = 1
e SubId = 706
, o resultado deve ser SemOrd = 12
.
Como isso implica uma cadeia, optei pela solução CTE recursiva.
;WITH Base AS (
SELECT
UserId,
SubId,
EduId,
SemOrd,
ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY SemOrd ASC) AS Ordinal,
ROW_NUMBER() OVER (PARTITION BY UserId, EduId ORDER BY SemOrd ASC) AS OrdinalParted
FROM /* ... */
), Chain AS (
SELECT
SemOrd AS SemOrd_First,
UserId,
SubId,
EduId,
SemOrd,
Ordinal,
OrdinalParted
FROM Base
UNION ALL
SELECT
Chain.SemOrd_First,
Base.UserId,
Base.SubId,
Base.EduId,
Base.SemOrd,
Base.Ordinal,
Base.OrdinalParted
FROM
Base
INNER JOIN Chain
ON Base.UserId = Chain.UserId
AND Base.EduId = Chain.EduId
AND Base.Ordinal = Chain.Ordinal + 1
AND Base.OrdinalParted = Chain.OrdinalParted + 1
), Minimal AS (
SELECT
UserId,
SubId,
MIN(SemOrd_First) AS SemOrd_First
FROM Chain
GROUP BY
UserId,
SubId
)
SELECT SemOrd_First FROM Minimal WHERE UserId = 1 AND SubId = 706
No papel e em tabelas pequenas, funciona.
Mas nas tabelas de produção, é basicamente inutilizável, pois precisa calcular primeiro todas as entradas CTE antes de fazer o WHERE
filtro. A consulta é muito lenta.
Claro que uma solução seria mover a WHERE
cláusula para dentro do 1º CTE:
;WITH Base AS (
SELECT
UserId,
SubId,
EduId,
SemOrd,
ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY SemOrd ASC) AS Ordinal,
ROW_NUMBER() OVER (PARTITION BY UserId, EduId ORDER BY SemOrd ASC) AS OrdinalParted
FROM /* ... */
WHERE UserId = 1 AND SubId = 706
), Chain AS (
/* ... */
E então é rápido. Mas eu gostaria de criar uma visualização a partir desta consulta.
Por isso, a WHERE
cláusula não pode estar dentro do 1º CTE, caso contrário, não podemos escolher o UserId
and SubId
ao chamar a visualização.
O problema é que o SQL Server não consegue incluir a WHERE
cláusula dentro do 1º CTE e reduzir a quantidade de trabalho no plano de execução.
Então as perguntas são:
- Existe uma maneira de fazer o SQL Server exibir a
WHERE
cláusula? - Existe uma alternativa para a solução recursiva CTE?
Se nada disso funcionar, acho que minha melhor aposta seria um procedimento armazenado.
Você está pensando demais nisso. Este é um tipo de problema de lacunas e ilhas, e existem muitas soluções mais simples.
Você pode fazer isso em uma única passagem pela tabela base, usando
LAG
para obter o valor anterior (decrescente), e uma condicional em janelaCOUNT
comparando cada linha com a anterior (para verificar se é uma cadeia) nos dará um ID exclusivo para a cadeia.Então, basta agrupá-los e pegar o mínimo
SemOrd
, usando apenas a cadeia que contém o valor que queremos.db<>violino
Seu resultado deve ser SemOrd=10 para UserId = 1 e SubId = 706?
Mas você pode tentar isto: