PARSENAME for Dotted Values: Build Numbers and Object Names

PARSENAME splits a dotted value into up to four parts and counts them from the right. That makes it handy for build numbers like 17.0.1000.7. But the parts come back as text, and text sorts in a way that surprises people.

A harmonica exposes separate chambers from one end, with a small vermilion accent on its casing

Parts are numbered from the right

Say your server inventory has a column of build numbers, and your manager asks, “Which server has the newest build?” You sort the column. The result is wrong, and nobody notices until the wrong server is patched.

PARSENAME is the tool for taking the number apart. Part 1 is the rightmost piece and part 4 is the leftmost. That backward numbering trips up almost everyone once. The build values below are made up for the demo. They are not a patch baseline.

DECLARE @Build nvarchar(128) = N'17.0.1000.7';

SELECT PARSENAME(@Build, 4) AS MajorPart,
       PARSENAME(@Build, 3) AS MinorPart,
       PARSENAME(@Build, 2) AS BuildPart,
       PARSENAME(@Build, 1) AS RevisionPart;

You get 17, 0, 1000 and 7, one per column. Each one is a string, not a number. Remember that, because it is the root of the next problem.

Text sorting puts 12 before 7

Two builds that differ only in the last part: revision 7 and revision 12. First sort them as text, then sort by the converted numbers.

DROP TABLE IF EXISTS #Builds;
CREATE TABLE #Builds (BuildText nvarchar(128));
INSERT #Builds (BuildText) VALUES (N'17.0.1000.7'), (N'17.0.1000.12');

SELECT BuildText FROM #Builds ORDER BY BuildText;

SELECT BuildText, TRY_CONVERT(int, PARSENAME(BuildText, 1)) AS RevisionNumber
FROM #Builds
ORDER BY TRY_CONVERT(int, PARSENAME(BuildText, 4)),
         TRY_CONVERT(int, PARSENAME(BuildText, 3)),
         TRY_CONVERT(int, PARSENAME(BuildText, 2)),
         TRY_CONVERT(int, PARSENAME(BuildText, 1));
Build strings sorted as text and by numeric revision
Text sorting puts revision 12 before 7. Numeric sorting puts 7 before 12.

The first grid is sorted as text. The character 1 comes before the character 7, so revision 12 lands above revision 7. The second grid sorts each part as a number, and 7 comes before 12, as you would expect. The fix is to convert every part before you sort, not just the last one.

Messy values need a plan

Real inventories contain surprises. Here are four on one line. A value with five parts returns NULL. A bad numeric piece gives NULL when you convert it. An empty piece between two dots also gives NULL. And an object name in brackets keeps its inner dot, because PARSENAME understands identifiers.

SELECT PARSENAME(N'[SalesDB].[dbo].[Order.Header]', 1) AS BracketedObject,
       PARSENAME(N'17.0.1000.7.1', 4) AS TooManyParts,
       TRY_CONVERT(int, PARSENAME(N'17.0.bad.7', 2)) AS InvalidNumericPart,
       PARSENAME(N'17..1000.7', 3) AS EmptyPart;

All three problems come back as NULL, and the bracketed name returns Order.Header as one part. That is useful, because a NULL is easy to test for. Your job is to test for it, and not to turn it into a zero.

Before you trust a parsed build

Reject the bad rows on purpose

Here is a small inventory with good and bad values. The query converts each part, marks a row as rejected when any part is missing, and sorts only by real numbers. The source text stays in the output so someone can correct it.

DROP TABLE IF EXISTS #Inventory;
CREATE TABLE #Inventory (ServerName varchar(20), BuildText nvarchar(128));
INSERT #Inventory (ServerName, BuildText)
VALUES ('Alpha', N'17.0.1000.12'), ('Bravo', N'17.0.1000.7'), ('Charlie', N'17.0.900.30'),
       ('Delta', N'17.0.1000'), ('Echo', N'17.0.abc.7'), ('Foxtrot', N'17..1000.7');

SELECT i.ServerName, i.BuildText, b.MajorPart, b.MinorPart, b.BuildPart, b.RevisionPart,
       CASE WHEN b.MajorPart IS NULL OR b.MinorPart IS NULL OR b.BuildPart IS NULL OR b.RevisionPart IS NULL
            THEN 'rejected' ELSE 'ok' END AS Status
FROM #Inventory AS i
CROSS APPLY (SELECT TRY_CONVERT(int, PARSENAME(i.BuildText, 4)) AS MajorPart,
                    TRY_CONVERT(int, PARSENAME(i.BuildText, 3)) AS MinorPart,
                    TRY_CONVERT(int, PARSENAME(i.BuildText, 2)) AS BuildPart,
                    TRY_CONVERT(int, PARSENAME(i.BuildText, 1)) AS RevisionPart) AS b
ORDER BY Status, b.MajorPart, b.MinorPart, b.BuildPart, b.RevisionPart, i.ServerName;

Three servers pass and sort correctly: Charlie, Bravo, then Alpha. Delta has too few parts, Echo has a letter where a number belongs, and Foxtrot has an empty part. All three are rejected, not guessed.

Look closely at Delta. Because PARSENAME counts from the right, its three parts shift. The 17 lands in MinorPart, and MajorPart is NULL. Those numbers are wrong, so never use them. Do not fill the gaps with zero, because that hides the bad data.

Know what PARSENAME is not

It does not check that an object exists, and it stops at four parts. Do not use it as a general string splitter by swapping commas for dots. And parsing a build says nothing about whether the server is patched. Compare the number with your approved baseline for that product branch. Parsed input is still input.

DROP TABLE IF EXISTS #Inventory, #Builds;

Next time a dotted value sorts strangely, convert every part before you compare.

A dotted string is not a number, it is a format with parts to check.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

SQL Function, SQL Instance, SQL Order By, SQL String
Previous Post
SQL SERVER – Difference Between ORIGINAL_LOGIN() and SUSER_SNAME()
Next Post
Upcoming Birthdays in the Next 30 Days With T-SQL

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.