How to call methods on a variable in SQL Server - sql-server

I saw the below syntax in a training video and I was wondering what this is called and which methods can we use on a variable?
DECLARE #text VARCHAR(max);
SET #text = REPLICATE(CAST('asdfasdf' as varchar(max)),8000);
SET #text.write('zzzzzzzz',0,8);
SELECT #text
the value stored in the variable is
'zzzzzzzzasdfasdf...'

The update.write syntax is not really a method. It is a TSQL syntax for updating large data types like nvarchar(max).
The official UPDATE documentation describes how to use .WRITE.
SET
{ column_name = { expression | DEFAULT | NULL }
| { udt_column_name.{ { property_name = expression
| field_name = expression }
| method_name ( argument [ ,...n ] )
}
}
| column_name { .WRITE ( expression , #Offset , #Length ) }
.WRITE (expression,#Offset,#Length) Specifies that a section of the
value of column_name is to be modified. expression replaces #Length
units starting from #Offset of column_name. Only columns of
varchar(max), nvarchar(max), or varbinary(max) can be specified with
this clause. column_name cannot be NULL and cannot be qualified with a
table name or table alias.
expression is the value that is copied to column_name. expression must
evaluate to or be able to be implicitly cast to the column_name type.
If expression is set to NULL, #Length is ignored, and the value in
column_name is truncated at the specified #Offset.
#Offset is the starting point in the value stored in column_name at
which expression is written. #Offset is a zero-based ordinal byte
position, is bigint, and cannot be a negative number. If #Offset is
NULL, the update operation appends expression at the end of the
existing column_name value and #Length is ignored. If #Offset is
greater than the byte length of the column_name value, the Database
Engine returns an error. If #Offset plus #Length exceeds the end of
the underlying value in the column, the deletion occurs up to the last
character of the value.
#Length is the length of the section in the column, starting from
#Offset, that is replaced by expression. #Length is bigint and cannot
be a negative number. If #Length is NULL, the update operation removes
all data from #Offset to the end of the column_name value.
See also Updating Large Data Types and the examples provided for updating large data types.

It's not a method, though it sure looks like it. Unlike for example XML methods, which you can use in a SELECT, this is a mutator. It's available on (n)varchar and varbinary data types with max length specified, and is used to modify the stored values efficiently.
There's not much information about them out there, I only found this blog post.

Related

DATALENGTH() works on literals but doesn't work on variables

The select datalength(cast('12345' as nvarchar)) query returns 10 (this is OK), but
declare #string as nvarchar
set #string = cast('12345' as nvarchar)
select datalength(#string)
returns 2 instead of 10. Why? How can I use DATALENGHT() for determining the datalength of a string stored in a variable?
Always specify the length n of character data. eg
declare #string as nvarchar(30)
set #string = cast('12345' as nvarchar(30))
select datalength(#string)
The behavior if you don't is described in the docs:
When n is not specified in a data definition or variable declaration
statement, the default length is 1. When n is not specified with the
CAST function, the default length is 30.
nchar and nvarchar

Return length of NVARCHAR or VARCHAR variable definition

I've declared a variable in a stored procedure:
DECLARE #CurrentChunk NVARCHAR(250)
I would like to use the length of the variable, i.e. 250, later in my sp for computational purposes, and I want to keep my code as dry as possible.
Here's my code (assume #Narrative is a param to the SP):
DECLARE #ChunkSizeCharacters INT,
#NumChunks INT,
#LoopIndex INT,
#CurrentChunk NVARCHAR(250)
SET #ChunkSizeCharacters = 250 -- HERE'S WHERE I WANT THE LENGTH OF #CurrentChunk
SET #NumChunks = CEILING((LEN(#Narrative) * 1.0)/#ChunkSizeCharacters)
SET #LoopIndex = 0;
WHILE (#LoopIndex < #NumChunks)
BEGIN
SET #CurrentChunk = SUBSTRING(#Narrative,
((#LoopIndex * #ChunkSizeCharacters) + 1), #ChunkSizeCharacters)
INSERT INTO [dbo].[Chunks] ([Chunk]) VALUES (#CurrentChunk)
SET #LoopIndex = #LoopIndex + 1
END
Is there a way to ascertain the length of an NVARCHAR or VARCHAR variable definition (please read carefully -- I'm not looking for LEN())?
It seems the MaxLength variant property returns the value you're looking for.
DECLARE #Banana varchar(255) = 'This banana'
SELECT SQL_VARIANT_PROPERTY(#Banana, 'MaxLength')
Returns 255.
If you don't mind overwriting the variable (and if you do, you can assign it to a temp NVARCHAR(MAX)):
SELECT #CurrentChunk = REPLICATE(0, 8000);
SELECT #ChunkSizeCharacters = LEN(#CurrentChunk);
This trick does not and cannot work for NVARCHAR(MAX), but that's presumably no problem, given it's enormous maximum size.
Unfortunately T-SQL has nothing in the way of metadata properties for variables. Even determining the type of an expression is a chore.
Interestingly, the value returned by that SELECT SQL_VARIANT_PROPERTY statement doesn't select into a plain, predefined variable. In the end, I used:
DECLARE #Text VARCHAR(400), #TextLen INT
SELECT #TextLen = CAST(SQL_VARIANT_PROPERTY(ISNULL(#Text, ''), 'MaxLength') AS INT)
Works like a charm for me!

how to split ntext datatype data to store in database

I have a column of type ntext called Emp_details_list, and it consists of data like
emp1###emp2###emp3...
At most it has 20 thousand characters as string and I am storing in that column and I need to split it and save in other table EmpDet and in other column (Single_Emp_det) but while splitting I can't cast ntext as nvarchar so am using a local variable and declared as nvarchar(max) and splitting but I can store only 8000 character only if I have 8001 characters it showing exception because it can't store so how can I store whole ntext data in other column using splitting concept in SQL Server
So you are probably stuck with Sql server 2000. If you can't use nvarchar(max), one possible way is to may be use substring function and copy your ntext to manageable chunks of varchar(8000) in a loop. In each iteration, save the 'part of chunk after the last #', to be used in next iteration. So you basically loop over your table, within that loop again loop over the ntext field value in chunks of 8k and do the rest. Hope it is clear enough.
As others already mentioned you can easily store 20000 characters in nvarchar(max). You are probably doing something wrong when converting these types.
Here is an example of converting from and to nvarchar(max) that clearly shows how can you store 20000 characters there.
DECLARE #v1 nvarchar(max)
DECLARE #v2 nvarchar(max)
create table #textExample
(
id int,
t1 ntext
)
declare #count int
set #v1 = ''
SET #count = 0
while #count < 20000
begin
set #v1 = #v1 + '1'
set #count = #count + 1
end
--converting nvarchar(max) to ntext
insert into #textExample
values (1, CONVERT(ntext,#v1))
select * from #textExample
-- converting ntext back to nvarchar(max)
SET #v2 = CONVERT(nvarchar(max), (select t1 from #textExample where id = 1))
select #v2, LEN(#v2)
drop table #textExample

SQL Server 2008 R2 - Scalar UDF results in infinite loop

The following code is resulting in an infinite loop or really really slow execution:
CREATE FUNCTION [dbo].[CleanUriPart]
(
-- Add the parameters for the function here
#DirtyUriPart nvarchar(200)
)
RETURNS nvarchar(200)
AS
BEGIN;
-- Declare the return variable here
DECLARE #Result nvarchar(200);
DECLARE #i int;
SET #i = 1;
WHILE 1 = 1
BEGIN;
SET #i = PATINDEX('%[^a-zA-Z0-9.~_-]%', #DirtyUriPart COLLATE Latin1_General_BIN);
IF #i > 0
SET #DirtyUriPart = STUFF(#DirtyUriPart, #i, 1, '-');
ELSE
BREAK;
END;
-- Add the T-SQL statements to compute the return value here
SELECT #Result = #DirtyUriPart;
-- Return the result of the function
RETURN #Result;
END;
The input/output should be as follows:
'abcdef' -> 'abcdef' works ok
'abc-def' -> 'abc-def' results in infinite loop
'abc*def' -> 'abc-def' results in infinite loop
etc.
Please help!
SELECT PATINDEX('%[^a-]%', N'aaa-def' COLLATE Latin1_General_BIN),
PATINDEX('%[^-a]%', N'aaa-def' COLLATE Latin1_General_BIN),
PATINDEX('%[^a-]%', 'aaa-def' COLLATE Latin1_General_BIN),
PATINDEX('%[^-a]%', 'aaa-def' COLLATE Latin1_General_BIN)
Returns
----------- ----------- ----------- -----------
1 5 5 5
So it seems that for varchar datatypes a trailing - is treated as being part of a set whereas for nvarchar it is ignored (treated as a malformed range as a is ignored too?)
The BOL entry for LIKE doesn't explicitly talk about how to use - within [] to get it to be treated as part of a set but does have the example
LIKE '[-acdf]'
to match -, a, c, d, or f so I assume that it needs to be the first item in a set (i.e. that [^a-zA-Z0-9.~_-] needs to be altered to [^-a-zA-Z0-9.~_]). That also matches the result of my testing above.
Any chance #DirtyUriPart can evaluate to NULL? ON the PATINDEX function, if either pattern or expression is NULL, PATINDEX returns NULL and a NULL in this case will cause a infinite loop
It looks like you could fix the problem by casting #DirtyUriPart as VARCHAR(200) in PATINDEX, which will cause the dash to be recognized along with the other characters in the class:
DECLARE #DirtyUriPart nvarchar(200)='abc-def';
-- Returns 0
SELECT PATINDEX('%[^a-zA-Z0-9.~_-]%', CAST(#DirtyUriPart AS VARCHAR(200)) COLLATE Latin1_General_BIN);
-- Returns 4
SELECT PATINDEX('%[^a-zA-Z0-9.~_-]%', #DirtyUriPart COLLATE Latin1_General_BIN);

Search an replace text in column for Column Type Text SQL Server

What I need is to search for a string in a specific column (datatype: text) of a table and replace it with another text.
For example
Id | Text
-----------------------------
1 this is test
2 that is testosterone
If I chose to replace test with quiz, results should be
this is quiz
that is quizosterone
What I've tried so far?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROC [dbo].[SearchAndReplace]
(
#FindString NVARCHAR(100)
,#ReplaceString NVARCHAR(100)
)
AS
BEGIN
SET NOCOUNT ON
SELECT CONTENT_ID as id, CONTENT_TEXT, textptr(CONTENT_TEXT) as ptr, datalength(CONTENT_TEXT) as lng
INTO #newtable6 FROM HTML_CONTENTS
DECLARE #COUNTER INT = 0
DECLARE #TextPointer VARBINARY(16)
DECLARE #DeleteLength INT
DECLARE #OffSet INT
SELECT #TextPointer = TEXTPTR(CONTENT_TEXT)
FROM #newtable6
SET #DeleteLength = LEN(#FindString)
SET #OffSet = 0
SET #FindString = '%' + #FindString + '%'
WHILE (SELECT COUNT(*)
FROM #newtable6
WHERE PATINDEX(#FindString, CONTENT_TEXT) <> 0) > 0
BEGIN
SELECT #OffSet = PATINDEX(#FindString, CONTENT_TEXT) - 1
FROM #newtable6
WHERE PATINDEX(#FindString, CONTENT_TEXT) <> 0
UPDATETEXT #newtable6.CONTENT_TEXT
#TextPointer
#OffSet
#DeleteLength
#ReplaceString
SET #COUNTER = #COUNTER + 1
END
select #COUNTER,* from #newtable6
drop table #newtable6
SET NOCOUNT OFF
I get the error:
Msg 7116, Level 16, State 4, Procedure SearchAndReplace, Line 31
Offset 1900 is not in the range of available LOB data.
The statement has been terminated.
Thank you
If you can't change your column types permanently, you can cast them on the fly:
ALTER PROC [dbo].[SearchAndReplace]
(#FindString VARCHAR(100),
#ReplaceString VARCHAR(100) )
AS
BEGIN
UPDATE dbo.HTML_CONTENTS
SET CONTENT_TEXT = cast (REPLACE(cast (CONTEXT_TEXT as varchar(max)), #FindString, #ReplaceString) as TEXT)
END
The datatype TEXT is deprecated and should not be used anymore - exactly because it's clunky and doesn't support all the usual string manipulation methods.
From the MSDN docs on text, ntext, image:
ntext, text, and image data types will
be removed in a future version of
MicrosoftSQL Server. Avoid using these
data types in new development work,
and plan to modify applications that
currently use them. Use nvarchar(max),
varchar(max), and varbinary(max)
instead.
My recommendation: convert that column to VARCHAR(MAX) and you should be fine after that!
ALTER TABLE dbo.HTML_CONTENTS
ALTER COLUMN CONTEXT_TEXT VARCHAR(MAX)
That should do it.
When your column is VARCHAR(MAX), then your stored procedures becomes totally simple:
ALTER PROC [dbo].[SearchAndReplace]
(#FindString VARCHAR(100),
#ReplaceString VARCHAR(100) )
AS
BEGIN
UPDATE dbo.HTML_CONTENTS
SET CONTENT_TEXT = REPLACE(CONTEXT_TEXT, #FindString, #ReplaceString)
END
Two observations on the side:
it would be helpful to have a WHERE clause in your stored proc, in order not to update the whole table (unless that's what you really need to do)
you're using TEXT in your table, yet your stored procedure parameters are of type NVARCHAR - try to stick to one set - either TEXT/VARCHAR(MAX) and regular VARCHAR(100) parameters, or then use all Unicode strings: NTEXT/NVARCHAR(MAX) and NVARCHAR(100). Constantly mixing those non-Unicode and Unicode strings is a mess and causes lots of conversions and unnecessary overhead

Resources