Mysql
 sql >> Database >  >> RDS >> Mysql

PHP MySQL trova il numero mancante più piccolo nella colonna

Se l'Order colonna è indicizzata, è possibile ottenere il primo numero mancante con SQL, senza leggere la tabella completa utilizzando un LEFT JOIN escluso:

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

o (forse più 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

La seconda query verrà convertita da MySQL nella prima. Quindi sono praticamente uguali.

Aggiorna

Strawberry ha menzionato un buon punto:il primo numero mancante potrebbe essere 1 , che non è coperto dalla mia query. Ma non sono riuscito a trovare una soluzione, che sia allo stesso tempo elegante e veloce.

Potremmo andare nella direzione opposta e cercare il primo numero dopo uno spazio vuoto. Ma sarebbe necessario unirmi di nuovo al tavolo per trovare l'ultimo numero esistente prima di quel divario.

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 (nel mio caso MariaDB 10.0.19) non è in grado di ottimizzare correttamente quella query. Ci vuole circa un secondo su una tabella di righe indicizzate (PK) da 1 milione, anche se il primo numero mancante è 9. Mi aspetto che il server smetta di cercare dopo t1.Order=10 , ma sembra non farlo.

Un altro modo, che è veloce ma sembra brutto (IMHO), è utilizzare la query originale in una sottoselezione solo se Order=1 esiste. Altrimenti restituisci 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

Oppure 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