Thanks for all of your input.
Below is a revised version of the stored procedure. It uses spu_ instead of sp_, only does replacements on records that require them, and only replaces the text found rather than an entire text block. It's nice to get such valuable feedback, so please
advise where you see issues or improvements.
This assumes the schema is designed where each table that could have a TEXT data type would have some unique numeric identifier in a column with the naming convention <table_name>_id.
Is there a better way to handle converting a BINARY(16)? I couldn't use VARCHAR, INT or BIGINT since they either didn't convert correctly, or the byte size differed enough to truncate the binary value. The only data type that I could find to do this with
decency was the UNIQUEIDENTIFIER. Is there a better, or more generic datatype?
Thanks again for your comments,
Bri
--
/* ============================================================ */
/* SP: sp_textreplace */
/* ============================================================ */
/* If procedure already exists...delete it */
IF EXISTS (SELECT 1 FROM sysobjects
WHERE name = 'spu_textreplace'
AND type = 'P')
BEGIN
DROP PROC spu_textreplace
END
GO
/**********************************************************/
CREATE PROCEDURE spu_textreplace
/*** Proc: spu_textreplace ********************************/
/* Description: */
/* A procedure that does replacement of text in a field */
/* defined by the user and of a TEXT data type. */
/* */
/* The SQL REPLACE fn doesn't work on TEXT data types, so */
/* we have to break the TEXT field into multiple VARCHARs */
/* and do REPLACE on those, then place them back into the */
/* field value through a text pointer. */
/* */
/* It is strongly advised that this script only executes */
/* during a time when nobody will be accessing the data. */
/* */
/* Input: table_name, text_field_name, record_id, */
/* find_text, replace_text */
/* */
/* Developer Task Date */
/* ------------------------------- ---- -------- */
/* Bri Gipson 06-22-2004 */
/* Created */
/* */
/* Bri Gipson 06-25-2004 */
/* Genericised to handle any table/field instead of */
/* just the table and field of a hard-coded object. */
/* (previously meta_data_glob.glob) */
/* */
/* Bri Gipson 06-30-2004 */
/* Encorporated Aaron Bertrand's optimizations to only */
/* commit an UPDATETEXT when necessary and to only do */
/* the update for the specified replacement, not an */
/* entire chunk of text. */
/*
http://www.aspfaq.com/show.asp?id=2445 */
/**********************************************************/
/* Input Values */
@table_name VARCHAR(256),
@field_name VARCHAR(256),
@record_id NUMERIC,
@find VARCHAR(256),
@replace VARCHAR(256)
/* Output Values */
-- None
AS
BEGIN
SET NOCOUNT ON
PRINT 'Replacing the text "' + @find + '" with "' + @replace + '" for ' + @table_name + '.' + @field_name
/* local variables */
DECLARE @ptr BINARY(16)
DECLARE @pos INT
DECLARE @current_id NUMERIC
DECLARE @populate VARCHAR(8000)
DECLARE @update VARCHAR(8000)
DECLARE @find_len INT
DECLARE @update_txt_pidx VARCHAR(8000)
SELECT @find_len = DATALENGTH( @find )
CREATE TABLE #glob ( glob_id NUMERIC, glob_ptr BINARY(16), txt_pidx INT )
IF ( @record_id = 0 )
SET @populate = 'INSERT INTO #glob ( glob_id, glob_ptr, txt_pidx ) SELECT ' + @table_name + '_id, tptr = TEXTPTR( ' + @field_name + ' ), PATINDEX(''%' + @find + '%'', ' + @field_name + ' ) FROM ' + @table_name
ELSE
SET @populate = 'INSERT INTO #glob ( glob_id, glob_ptr, txt_pidx ) SELECT ' + @table_name + '_id, tptr = TEXTPTR( ' + @field_name + ' ), PATINDEX(''%' + @find + '%'', ' + @field_name + ' ) FROM ' + @table_name + ' WHERE ' + @table_name + '_id = ' +
CONVERT( VARCHAR(10), @record_id )
EXEC ( @populate )
DECLARE glob_cursor INSENSITIVE CURSOR FOR
SELECT glob_id, glob_ptr
FROM #glob
WHERE txt_pidx > 0
OPEN glob_cursor
FETCH NEXT FROM glob_cursor INTO @current_id, @ptr
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT
@pos = txt_pidx
FROM
#glob
WHERE
#glob.glob_id = @current_id
WHILE @pos > 0
BEGIN
PRINT 'Found "' + @find + '" in row id ' + CAST( @current_id AS VARCHAR(10) ) + ' at position ' + CAST( @pos AS VARCHAR(10) )
-- Do our replacement:
SELECT @pos = @pos - 1
SET @update =
'DECLARE @ptr BINARY(16) ' +
'SET @ptr = CONVERT( BINARY(16), CONVERT( UNIQUEIDENTIFIER, ''' + CONVERT( VARCHAR(255), CONVERT( UNIQUEIDENTIFIER, @ptr ) ) + ''' ) ) ' +
'UPDATETEXT ' + @table_name + '.' + @field_name + ' ' +
'@ptr ' +
CONVERT( VARCHAR(255), @pos ) + ' ' +
CONVERT( VARCHAR(256), @find_len ) + ' ' +
'''' + @replace + ''''
-- PRINT @update + ''''
EXECUTE ( @update )
SET @update_txt_pidx =
'UPDATE ' +
'#glob ' +
'SET ' +
'txt_pidx = PATINDEX(''%' + @find + '%'', ' + @table_name + '.' + @field_name + ' ) ' +
'FROM ' +
@table_name + ' ' +
'WHERE ' +
@table_name + '_id = ' + CAST( @current_id AS VARCHAR(10) ) + ' AND ' +
'glob_id = ' + CAST( @current_id AS VARCHAR(10) )
-- PRINT @update_txt_pidx + ''''
EXECUTE ( @update_txt_pidx )
SELECT
@pos = txt_pidx
FROM
#glob
WHERE
#glob.glob_id = @current_id
END
FETCH NEXT FROM glob_cursor INTO @current_id, @ptr
END
CLOSE glob_cursor
DEALLOCATE glob_cursor
SET NOCOUNT OFF
END
/* Use these commands to test FROM the command line: */
/* EXECUTE spu_textreplace 'meta_data_glob', 'glob', 0, 'house', 'mouse' */
GO