Based on your comment from the dupe post that was on DBA.StackExchange:
It's for a specific prefix among other conditions on the table as well. Prefixes are always the same length.
That makes this a much simpler problem to solve, with some CTE magic. First filter the table with a starts-with WHERE
clause for the prefix you care about, then use the SUBSTRING()
function to trim out the prefix and order by the remaining numbers of the value. Finally, you can use the ROW_NUMBER()
window function to find where the numerical value of the row doesn't match its ordinal, when sorted by that numerical value.
Example below:
WITH _ColNumbers AS
(
SELECT
CONVERT(INT, SUBSTRING(Col, 4, LEN(Col) - 3)) AS ColNumber, -- Strip out the prefix and convert the remaining numerical part to an integer
FROM MyTable
WHERE Col LIKE 'ABC%'
),
_ColNumbersSorted AS
(
SELECT
ColNumber,
ROW_NUMBER() OVER (ORDER BY ColNumber) AS SortId -- Generate a sequential set of integers in the same order as ColNumber
FROM _ColNumbers
),
_ColNumberLowestUnmatchedSortId AS
(
SELECT TOP 1 -- Return the single lowest mismatch
ColNumber,
SortId
FROM _ColNumbersSorted
WHERE ColNumber <> SortId -- Filter down to only the rows that don't match their ColNumber to their SortId
ORDER BY SortId -- Sort by the lowest SortId (aka lowest mismatch)
)
-- Join back to the list of all ColNumbers to get the row just before the lowest mismatch
-- The lowest available number is 1 more than the previous row's ColNumber
SELECT
CNS.ColNumber + 1 AS LowestAvailableNumber
FROM _ColNumbersSorted AS CNS
INNER JOIN _ColNumberLowestUnmatchedSortId AS CNLUSI
ON CNS.SortId = CNLUSI.SortId - 1;
At a quick glance, this may appear like a lot, but each CTE just represents each logical step for better readability. The performance should be pretty good, though one thing that could help performance is adding a computed column for CONVERT(INT, SUBSTRING(Col, 4, LEN(Col) - 3))
and creating an index on it, such that the calculation is already persisted (because it's not a SARGable expression). You can achieve that with the following code:
ALTER MyTable
ADD CONVERT(INT, SUBSTRING(Col, 4, LEN(Col) - 3)) AS ColNumber;
CREATE NONCLUSTERED INDEX IX_MyTable_Col_ColNumber ON MyTable (Col, ColNumber);
Not only would that help performance, doing so simplifies the code a little bit like so:
WITH _ColNumbersSorted AS
(
SELECT
ColNumber,
ROW_NUMBER() OVER (ORDER BY ColNumber) AS SortId -- Generate a sequential set of integers in the same order as ColNumber
FROM MyTable
WHERE Col LIKE 'ABC%'
),
_ColNumberLowestUnmatchedSortId AS
(
SELECT TOP 1 -- Return the single lowest mismatch
ColNumber,
SortId
FROM _ColNumbersSorted
WHERE ColNumber <> SortId -- Filter down to only the rows that don't match their ColNumber to their SortId
ORDER BY SortId -- Sort by the lowest SortId (aka lowest mismatch)
)
-- Join back to the list of all ColNumbers to get the row just before the lowest mismatch
-- The lowest available number is 1 more than the previous row's ColNumber
SELECT
CNS.ColNumber + 1 AS LowestAvailableNumber
FROM _ColNumbersSorted AS CNS
INNER JOIN _ColNumberLowestUnmatchedSortId AS CNLUSI
ON CNS.SortId = CNLUSI.SortId - 1;