This is a very specific problem and solution, so it’s helpfulness is questionable. It very likely makes bad data even worse. However, at times the things that are right and just in this world are not what needs to come out the other side of the machine at the end of the day.
While working in a SQL Server sproc that generates data for a client, I needed to:
Ensure this variable length nvarchar value has at least 4 trailing zeros. The given values had their 0s chopped off at times. And sometimes it’s not all of them.
(Dear God, why?)
Because the product codes are variable length, I can’t rely on the standard “pad this string until it is length 10” idea.
Here’s how I’m working through it right now.
Breaking it down
First things first… Where are we?
Using a combination of
PATINDEX() with a negating wildcard
[^0] I am able to ask “How many characters from the end is the first instance
GOOD: Start: '1012340000' REVERSE(): '0000432101' ||||| 12345 --> Index of first non '0': 5 BAD: Start: '10123400' REVERSE(): '00432101' ||| 123 --> Index of first non '0': 3!
DECLARE @mpc NVARCHAR(20) = '10123400' SELECT @mpc [mpc], PATINDEX('%[^0]%', REVERSE(@mpc)) as chars_from_end /******* ************** mpc chars_from_end 10123400 3 */
Add some scribbles
Now that we know how many characters from the end to the first non-zero, we can
calculate how many zeros we need to append. Finally we can add these using the
REPLICATE() function. I expect this index to be 5, so anything less needs some
BAD: Start: '10123400' REVERSE(): '00432101' [index]: 3 [expected]: 5 ZerosToAdd: [expected] - [index] = 2 | CONCAT( @mpc, REPLICATE('0', 2 ) ) End: '1012340000'
DECLARE @mpc NVARCHAR(20) = '10123400' SELECT @mpc as [mpc], PATINDEX('%[^0]%', REVERSE(@mpc)) as [chars_from_end], 5 - PATINDEX('%[^0]%', REVERSE(@mpc)) as [zeros_to_add], CONCAT( @mpc, REPLICATE('0', 5 - PATINDEX('%[^0]%', REVERSE(@mpc))) ) as [result] /******** ************** ************ ********** mpc chars_from_end zeros_to_add result 10123400 3 2 1012340000 */
NULLS are gracefully handled for us in the event that
there are a billionty 0s already on the end.
This can be summarized into a function for cleaner usage later:
-- Ensure a string ends with at least X number of a char. -- @examples: -- SELECT fn_pad_trailing_chars('12345', 4, '0') -- => '123450000' -- -- SELECT fn_pad_trailing_chars('12345000', 4, '0') -- => '123450000' -- -- SELECT fn_pad_trailing_chars('123450000', 4, '0') -- => '123450000' CREATE FUNCTION dbo.fn_pad_trailing_chars(@mpc NVARCHAR(100), @ensure_this_many INT, @char NVARCHAR(1)) RETURNS NVARCHAR(100) BEGIN RETURN CONCAT( @mpc, REPLICATE( @char, ( 1 + @ensure_this_many -- Expected - -- - PATINDEX('%[^' + @CHAR + ']%', REVERSE(@mpc)) -- Actual ) ) ) END GO DECLARE @mpc NVARCHAR(20) = '10123400' SELECT @mpc [mpc], fn_pad_trailing_chars(@mpc, 4, '0') [val] /******* ********** mpc val 10123400 1012340000 */
For Pete’s Sake, (Probably) Don’t Do This.
This IS foolish and dangerous to just append
0s to the codes all willy nilly.
What if the code is supposed to be ‘1234500000’ but is written as ‘123450’? This
is taking bad data, and potentially making it worse. For now, I just needed to
pad some zeros with a shotgun to help a guy out downstream while reviewing some
data. A better solution is to drop all trailing 0s for comparison, and take
records that only have a single match. But that’s not what today calls for.
Please don’t add random amounts of
0 to your data. Also, if you’re a human
doing data entry, don’t drop them willy nilly before saving it to your db, and
we don’t have to have this chat in the first place.