January 9, 2009 at 2:58 pm
I am looking for a way to insert a value into a string while preserving the order of the string. For example, given the string '1348' I need to insert the value 7 such that the resulting string is '13478'. Is there a more efficient/elegant way of doing this other than testing the length of the string and byte values and then using if clauses to handle each substring scenario?
Many thanks in advance for your help!
S Ely
January 9, 2009 at 3:07 pm
Heh...bwa-heh... BWAAA-HAAA!!! Hey, Sergei... STUFF it will ya? :P:D:)
Oh dear... couldn't resist the pun. Seriously, though... that's the answer... see here...
DECLARE @SomeExistingString VARCHAR(100),
@SomeStringToInsert VARCHAR(100)
SELECT @SomeExistingString = '1348',
@SomeStringToInsert = '7'
SELECT [font="Arial Black"]STUFF[/font](@SomeExistingString,4,0,@SomeStringToInsert)
--Jeff Moden
Change is inevitable... Change for the better is not.
January 9, 2009 at 3:24 pm
Thanks Jeff. I neglected to mention that the insert position is variable, though. The field is char(9) with the values restricted to integers 1-9 so the insertion could be in byte positions 1-7. So I guess the real question is what is the most efficient way to determine the position it should go in?
Sergei
January 9, 2009 at 4:05 pm
Ah... that's a slightly different story. Am I to assume that you want the digit to be inserted in the correct ascending numerical order... in other words, the 7 went were it did because it's numerically between the 4 and the 8? That can be done pretty easily but lemme know if that's the actual requirement, please.
Also, I'm always interested in the "why"... why is it that you have to do such a thing? What are the business rules behind this? Thanks.
--Jeff Moden
Change is inevitable... Change for the better is not.
January 9, 2009 at 4:44 pm
Your assumption about the numerical order is correct. The existence of a number in the field refers to a specific checkbox (of which there are 10)in the front end app. These checkboxes signify logical yes/no and represent diferent marketing mailing purposes. Not being the designer of the app, I'm not sure why they chose to store the data this way vs. separate bit fields.
January 9, 2009 at 5:06 pm
Thanks for taking the time to explain. Even though it's for a necessarily RBAR action for a GUI and there's probably no need to handle the proverbial "millions of rows" for this one, I'll try to make it scalable to some extent and still try to make it easy to use... there's a couple of things I've gotta try. I'll be back soon.
--Jeff Moden
Change is inevitable... Change for the better is not.
January 9, 2009 at 7:40 pm
Ok... that was fun... here's a function to do what you want. The function will make it easy and consistent on folks to use and I tried to build some speed into it using some short circuiting, some short cuts using a derived table like you would a CTE (yep... this code works in SQL Server 2000, too!), and my dearest coding friend, the Tally table. Before you can build the function, you'll need a Tally table and here's where you can get one...
The "Numbers" or "Tally" Table: What it is and how it replaces a loop[/font][/url]
The article explains why the Tally table is so powerful and how it makes otherwise difficult code a real breeze to think about and write.
Here's the function to do your ordered insertion into a string... I did make it so it will only insert 1 character at a time... if ya wanna know how it does what it does, just read the embedded comments
CREATE FUNCTION dbo.OrderedCharacterInsert
/**********************************************************************************************************************
Purpose:
This function accepts an existing order string and inserts a charcter in the proper order.
Note... spaces are not allowed in either string.
Example and usage:
SELECT dbo.OrderedCharacterInsert('1348','0') UNION ALL --Returns 01348
SELECT dbo.OrderedCharacterInsert('1348','1') UNION ALL --Returns 1348 because the "1" already existed
SELECT dbo.OrderedCharacterInsert('1348','7') UNION ALL --Returns 13478
SELECT dbo.OrderedCharacterInsert('1348','9') --Returns 13489
Revision History:
Rev 00 - 09 Jan 2009 - Jeff Moden - Initial creation and test
Written for: http://www.sqlservercentral.com/Forums/Topic633926-338-1.aspx?
**********************************************************************************************************************/
(
@ExistingString VARCHAR(256),
@CharToInsert CHAR(1)
)
RETURNS VARCHAR(256)
AS
BEGIN
RETURN (
SELECT CASE -- If character isn't already present, then insert it
WHEN CHARINDEX(@CharToInsert,posit.ExistingString) = 0
THEN LTRIM(RTRIM(STUFF(posit.ExistingString,posit.InsertHere,0,@CharToInsert)))
ELSE @ExistingString
END
FROM (--==== Determines character to insert at and modified string to insert on.
-- The TOP 1 short circuits the "pseudo cursor" created by the
-- Tally table for extra speed. The -t.N does it all backwards so
-- we don't have to ORDER BY DESC... again, for extra speed.
SELECT TOP 1 (LenExistingString -t.N +1) AS InsertHere,ExistingString
FROM dbo.Tally t,
(--==== Adds spaces to string to insert on and gets its new length.
-- Works like a CTE so we don't have to add the spaces elsewhere in code.
SELECT ' '+@ExistingString+' ' AS ExistingString,
LEN(@ExistingString)+2 AS LenExistingString
) es
WHERE t.N <= LenExistingString
AND SUBSTRING(es.ExistingString,(LenExistingString -t.N),1) < @CharToInsert
ORDER BY t.N --Not actually used by execution plan, but makes nay sayers breath easier.
--And, yes, it still works in 2k5 ;-)
) posit
) --End of return
END
... and I'm thinking that you can probably figure out how to use it from there. Thanks for the interesting problem. 😀
--Jeff Moden
Change is inevitable... Change for the better is not.
January 9, 2009 at 11:11 pm
Wow. I'll take a look at this over the weekend and let you know how it goes. Thanks again!
Sergei Ely
January 12, 2009 at 2:59 am
Here's another way.
SELECT CASE WHEN CHARINDEX(@CharToInsert,@ExistingString) > 0
THEN @ExistingString
ELSE RTRIM(STUFF(@ExistingString+' ',PATINDEX('%['+@CharToInsert+'-9]%',@ExistingString+'9'),0,@CharToInsert))
END
____________________________________________________
Deja View - The strange feeling that somewhere, sometime you've optimised this query before
How to get the best help on a forum
http://www.sqlservercentral.com/articles/Best+Practices/61537January 12, 2009 at 12:23 pm
John,
The code worked perfectly. 😀 Thanks again.
Sergei
January 12, 2009 at 12:31 pm
Mark,
Thanks for the input. The solution requires placement of the integer within the ordered string at variable positions. For example, the existing string could be a single integer ie '4' with the desired result being '47', or '12468' => '124678', or ' ' => '7'. Sorry if I didn't make that clear in my post.
S Ely
January 12, 2009 at 12:48 pm
Sergei Ely (1/12/2009)
John,The code worked perfectly. 😀 Thanks again.
Sergei
Heh... actually, it's "Jeff" but I appreciate the feedback anyway. Thanks, Sergei. 🙂
--Jeff Moden
Change is inevitable... Change for the better is not.
January 13, 2009 at 2:11 am
Sergei Ely (1/12/2009)
Mark,Thanks for the input. The solution requires placement of the integer within the ordered string at variable positions. For example, the existing string could be a single integer ie '4' with the desired result being '47', or '12468' => '124678', or ' ' => '7'. Sorry if I didn't make that clear in my post.
S Ely
which is what my solution does....
CREATE FUNCTION dbo.AnotherOrderedCharacterInsert
(
@ExistingString VARCHAR(256),
@CharToInsert CHAR(1)
)
RETURNS VARCHAR(256)
AS
BEGIN
RETURN (
SELECT CASE WHEN CHARINDEX(@CharToInsert,@ExistingString) > 0
THEN @ExistingString
ELSE RTRIM(STUFF(@ExistingString+' ',PATINDEX('%['+@CharToInsert+'-9]%',@ExistingString+'9'),0,@CharToInsert))
END
)
END
GO
SELECT dbo.AnotherOrderedCharacterInsert('4','7') UNION ALL --Returns 47
SELECT dbo.AnotherOrderedCharacterInsert('12468','7') UNION ALL --Returns 124678
SELECT dbo.AnotherOrderedCharacterInsert('','7') UNION ALL --Returns 7
SELECT dbo.AnotherOrderedCharacterInsert('1348','0') UNION ALL --Returns 01348
SELECT dbo.AnotherOrderedCharacterInsert('1348','1') UNION ALL --Returns 1348 because the "1" already existed
SELECT dbo.AnotherOrderedCharacterInsert('1348','7') UNION ALL --Returns 13478
SELECT dbo.AnotherOrderedCharacterInsert('1348','9') --Returns 13489
____________________________________________________
Deja View - The strange feeling that somewhere, sometime you've optimised this query before
How to get the best help on a forum
http://www.sqlservercentral.com/articles/Best+Practices/61537January 13, 2009 at 6:45 am
Mark, if you do a final replace of ")" with "& # 0 4 1 ;" (without the spaces), then your code wil show correctly without all those bloody smiley faces. You can turn them off as part of your profile, but I prefer to have them on because I'm frequently taken the wrong way without them. Just don't want the buggers in my code and thought I'd share a suppression method with you.
--Jeff Moden
Change is inevitable... Change for the better is not.
January 13, 2009 at 7:07 am
Mark,
VERY clever code... I obviously never thought of using PatIndex and a variable for the first character of a range... And, it's about twice as fast as the Tally table method.
The very cool part is that it's twice as fast as that if you take it out of the UDF and use the code inline.
--Jeff Moden
Change is inevitable... Change for the better is not.
Viewing 15 posts - 1 through 15 (of 21 total)
You must be logged in to reply to this topic. Login to reply