TSQL - Solve it YOUR Way - Parsing Tips and Tricks - Returning Digits after the Decimal Point
Introduction:
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
Code Snippet
- -- 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)[2]', 'nvarchar(50)') from @Test
Conclusion:
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)
Comments
Anonymous
October 23, 2012
Truly amazing and amusing. A great example how T-sql can achieve same thing in so many different ways.Anonymous
October 24, 2012
I just added one more that Jingyang had sent where he casts to xml and retrieves the value from the xml node.Anonymous
October 24, 2012
The comment has been removedAnonymous
October 24, 2012
The comment has been removedAnonymous
October 25, 2012
Interesting solutions - diversity in action. Four remarks:
- Solutions 5, 6 and 7 don't work with negative numbers...
- Hardcoded delimiters can be a source for bugs when the decimal symbol changes.
- Hardcoded numbers related to the number of decimals (aka scale) can lead to bugs as well
- Some of solutions stop working when the scale increases. For example the use of integer and bigint in 3rd, respectively 4th solution, make the solution stop working after 10, respectively 17 decimals. (Maybe the problem definition needs to be more clear.)
Anonymous
October 27, 2012
Amazing Solutions... Thanks Samuel for collating the solutionsAnonymous
March 04, 2014
How about: declare @n decimal(10,3) set @n=12.345 select @n as num ,@n-cast(abs(@n) as decimal) as method_1