As part of the blog series TSQL - Solve it YOUR Way, today we will tackle a numeric parsing question. While past topics include solutions that vary from 15-150 lines of TSQL code, all of today's solutions will be very short. A question was recently posted in the MSDN forums asking how to retrieve the digits after the decimal for a numeric data type. Off the top of our heads, many people can probably come up with a few solutions. After sending the question out to the blog series contributors, we came up with ten solutions that highlight creative parsing solutions and some less known tips and tricks within T-SQL.
Topic: Given a numeric value, return the digits after the decimal (ex: 3.142 returns 142)
Solutions - provided by Tom Cooper, Olaf Helper, Kent Waldrop, Jingyang Li, & Steven Wang
- -- Test data
- Declare @Test Table(Foo decimal(9,3));
- Insert @Test(Foo) Values (1.479), (2.000), (5.123);
- -- Solution 1: ParseName
- Select Foo, ParseName(Foo, 1) From @Test;
- -- Solution 2: Modulo / Absolute Value
- SELECT SUBSTRING(CONVERT(varchar(30), ABS(Foo) % 1), 3, 30) AS Result FROM @Test
- -- Solution 3: Slightly modified version using modulo. Note: This solution returns '0' for input 2.000, whereas others return '000'
- select Foo, cast(sign(Foo) * 1000 * (Foo % 1) as integer
- ) as decimal_Part
- from @test
- -- Solution 4: Reverse / Floor solution
- SELECT REVERSE(CAST(FLOOR(REVERSE(ABS(Foo))) AS bigint)) from @Test
- -- Solution 5: Use the original number minus an integer converted from the original number to an integer and then convert the decimal number to a varchar (50) to use the STUFF function to remove the '0.' part from the decimal number.
- SELECT Stuff(Cast(Foo - Cast(Foo AS INT) AS VARCHAR(50)), 1, 2, '') from @Test
- -- Solution 6: Remove the "0." part from the above calculation with a substring function
- SELECT Substring(Cast(Foo - Cast(Foo AS INT) AS VARCHAR(50)), 3, Len(Foo)) from @Test
- -- Solution 7: Use right function to remove the leading "0." from the string
- SELECT RIGHT(Cast(Foo - Cast(Foo AS INT) AS VARCHAR(50)), Len(Cast(Foo - Cast(Foo AS INT) AS VARCHAR(50))) - 2) from @Test
- -- Solution 8: Find the . position using charindex and stuff everything before decimal
- SELECT Stuff(Foo, 1, Charindex('.', Foo), '')from @Test
- -- Solution 9
- SELECT Stuff(Foo, 1, Charindex(0X2E, Cast(Ltrim(Foo) AS VARBINARY(50)), 1), '') from @Test
- -- Solution 10: CAST the string as an XML type and retrieve the value from the XML node
- SELECT Cast (N'<root><row>' + Replace(Foo, '.', '</row><row>') + '</row></root>' AS XML).value('(/root/row)', 'nvarchar(50)') from @Test
It is very common to encounter situations where parsing is required in some capacity. The solutions presented above cover common parsing techniques such as SUBSTRING and PARSENAME, but also leverage other constructs such as STUFF, REVERSE, Modulo, and others in creative ways. I hope that you are able to learn a lot by trying out the problem yourself and then reading through the additional solutions.
Special thanks to Tom, Olaf, Jingyang, Steven, and Kent for their valuable forums contribution and for contributing to this series!
Hope that helps,
Sam Lester (MSFT)