11

I have a table with an NTEXT column called comments. I have a second string, let's call it anothercomment (a varchar) that needs placing inside a given comments string after the word UPDATEHERE.

Casting to nvarchar(max) truncates the comments string, so I cannot use the likes of CHARINDEX() (Msg 8152, Level 16, State 10, Line 2 String or binary data would be truncated.). I have used datalength() to check that there are a few thousand columns that are >8000 characters.

An example of what I want to achieve (albeit with much longer strings):

comments - This is a test UPDATEHERE This is the end of the test

anothercomment - . This is inserted.

Resulting string - This is a test UPDATEHERE. This is inserted. This is the end of the test

I realize that this is trivial with a normal varchar()/nvarchar(), but ntext is a complete and utter nightmare to work with. I realize it's a deprecated data type, but I did not write the application in question.

8

Converting to nvarchar(max) should work unless you are doing something wrong with your CHARINDEX()

Try this code snippet, it should output what you want.

-- Create the table
CREATE TABLE [dbo].[PhilsTable](
    [comment] [ntext] NULL,
    [anothercomment] [nvarchar](50) NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY];

GO

-- insert very long string
INSERT INTO [dbo].[PhilsTable] (comment, anothercomment) VALUES (N'This is a test UPDATEHERE This is the end of the test' + REPLICATE (CAST(N'x' AS nvarchar(max)), 1000000), 'this goes in here');

-- verify data
SELECT DATALENGTH(comment), *  FROM [dbo].[PhilsTable];

-- perform replace
SELECT CAST(REPLACE(CAST(comment AS NVARCHAR(MAX)),'UPDATEHERE','UPDATEHERE' + anothercomment) AS NTEXT) FROM [dbo].[PhilsTable];

DROP TABLE [dbo].[PhilsTable];

Thanks go out to Andriy M for helping out with the REPLICATE statement.

10

Converting to nvarchar(max) and back to ntext does make life simpler from a code point of view, but it does mean converting and rewriting the whole (perhaps very large) value, with all the CPU and logging overhead that implies.

An alternative is to use UPDATETEXT. This is deprecated, just like ntext, but it can reduce the logging overhead significantly. On the downside, it means using text pointers, and it only operates on one row at a time.

The following example code uses a cursor to work around that limitation, and uses PATINDEX instead of CHARINDEX since the former is one of the few functions that work directly with ntext:

Sample data

CREATE TABLE dbo.PhilsTable
(
    comment ntext NULL,
    anothercomment nvarchar(50) NULL
);

INSERT dbo.PhilsTable
    (comment, anothercomment)
VALUES 
(
    CONVERT(ntext, 
        N'This is a test UPDATEHERE This is the end of the test ' + 
            REPLICATE (CONVERT(nvarchar(max), N'x'), 1000000)), 
    CONVERT(nvarchar(50), N'. This is inserted.')
),
(
    CONVERT(ntext, 
        N'This is a test UPDATEHERE This is the end of the test ' + 
            REPLICATE (CONVERT(nvarchar(max), N'x'), 1000000)), 
    CONVERT(nvarchar(50), N'. This is inserted.')
),
(
    CONVERT(ntext, 
        N'This is a test UPDATEHERE This is the end of the test ' + 
            REPLICATE (CONVERT(nvarchar(max), N'x'), 1000000)), 
    CONVERT(nvarchar(50), N'. This is inserted.')
);

Cursor declaration

DECLARE c 
    CURSOR GLOBAL 
    FORWARD_ONLY 
    DYNAMIC 
    SCROLL_LOCKS 
    TYPE_WARNING
FOR
SELECT
    TxtPtr = TEXTPTR(PT.comment),
    Src = PT.anothercomment,
    Offset = PATINDEX(N'%UPDATEHERE%', PT.comment) + LEN(N'UPDATEHERE') - 1
FROM dbo.PhilsTable AS PT
WHERE
    PT.comment LIKE N'%UPDATEHERE%'; -- LIKE works with ntext

OPEN c;

Processing loop

DECLARE 
    @Ptr binary(16),
    @Src nvarchar(50),
    @Offset integer;

SET STATISTICS XML OFF; -- No cursor fetch plans

BEGIN TRANSACTION;

    WHILE 1 = 1
    BEGIN
        FETCH c INTO @Ptr, @Src, @Offset;

        IF @@FETCH_STATUS = -2 CONTINUE; -- row missing
        IF @@FETCH_STATUS = -1 BREAK; -- no more rows

        IF 1 = TEXTVALID('dbo.PhilsTable.comment', @Ptr)
        BEGIN
            -- Modify ntext value
            UPDATETEXT dbo.PhilsTable.comment @Ptr @Offset 0 @Src;
        END;
    END;

COMMIT TRANSACTION;

CLOSE c; DEALLOCATE c;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.