Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

PHP MySQL encontra o menor número ausente na coluna


Se o Order coluna é indexada, você pode obter o primeiro número ausente com SQL, sem ler a tabela completa usando um LEFT JOIN excluindo:
SELECT t1.`Order` + 1 AS firstMissingOrder
FROM tabla t1
LEFT JOIN tabla t2 ON t2.`Order` = t1.`Order` + 1
WHERE t2.`Order` IS NULL
  AND t1.`Order` <> (SELECT MAX(`Order`) FROM tabla)
ORDER BY t1.`Order`
LIMIT 1

ou (talvez mais intuitivo)
SELECT t1.`Order` + 1 AS firstMissingOrder
FROM tabla t1
WHERE NOT EXISTS (
    SELECT 1
    FROM tabla t2
    WHERE t2.`Order` = t1.`Order` + 1
) 
    AND t1.`Order` <> (SELECT MAX(`Order`) FROM tabla)
ORDER BY t1.`Order`
LIMIT 1

A segunda consulta será convertida pelo MySQL para a primeira. Então eles são praticamente iguais.

Atualizar

Strawberry mencionou um bom ponto:o primeiro número ausente pode ser 1 , que não é abordado na minha consulta. Mas não consegui encontrar uma solução, que seja elegante e rápida.

Poderíamos seguir o caminho oposto e procurar o primeiro número após um intervalo. Mas seria necessário juntar a tabela novamente para encontrar o último número existente antes dessa lacuna.
SELECT IFNULL(MAX(t3.`Order`) + 1, 1) AS firstMissingOrder
FROM tabla t1
LEFT JOIN tabla t2 ON t2.`Order` = t1.`Order` - 1
LEFT JOIN tabla t3 ON t3.`Order` < t1.`Order`
WHERE t1.`Order` <> 1
  AND t2.`Order` IS NULL
GROUP BY t1.`Order`
ORDER BY t1.`Order`
LIMIT 1

MySQL (no meu caso MariaDB 10.0.19) não é capaz de otimizar essa consulta corretamente. Demora cerca de um segundo em uma tabela de linhas indexada (PK) de 1 milhão, mesmo que o primeiro número ausente seja 9. Espero que o servidor pare de pesquisar após t1.Order=10 , mas parece não fazer isso.

Outra maneira, que é rápida, mas parece feia (IMHO), é usar a consulta original em uma subseleção somente se Order=1 existe. Caso contrário, retorne 1 .
SELECT CASE
    WHEN NOT EXISTS (SELECT 1 FROM tabla WHERE `Order` = 1) THEN 1
    ELSE (
        SELECT t1.`Order` + 1 AS firstMissingOrder
        FROM tabla t1   
        LEFT JOIN tabla t2 ON t2.`Order` = t1.`Order` + 1
        WHERE t2.`Order` IS NULL
          AND t1.`Order` <> (SELECT MAX(`Order`) FROM tabla)
        ORDER BY t1.`Order`
        LIMIT 1
    )
END AS firstMissingOrder

Ou Usando UNION
SELECT 1 AS firstMissingOrder FROM (SELECT 1) dummy WHERE NOT EXISTS (SELECT 1 FROM tabla WHERE `Order` = 1)
UNION ALL
SELECT firstMissingOrder FROM (
    SELECT t1.`Order` + 1 AS firstMissingOrder
    FROM tabla t1
    LEFT JOIN tabla t2 ON t2.`Order` = t1.`Order` + 1
    WHERE t2.`Order` IS NULL
      AND t1.`Order` <> (SELECT MAX(`Order`) FROM tabla)
    ORDER BY t1.`Order`
    LIMIT 1
) sub
LIMIT 1