parsename is not working when using string from function - sql

Parsename is working when directly using string or returning static value from the function. But when I returning select value with dot from the function. its not working. I am not able to figure out the issue.
CREATE TABLE [dbo].[TestTimeLine](
[UserID] [int] NOT NULL,
[Field1] [nchar](250) NOT NULL,
[Value] [smalldatetime] NULL,
[Timestamp] [datetime] NULL,
)
INSERT INTO [TestTimeLine] VALUES(10,'TestDate',GETDATE(),GETDATE())
CREATE FUNCTION TestFunction()
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #R NVARCHAR(MAX)
SELECT TOP 1 #R=CAST(DATENAME(month,value) AS VARCHAR) + ' ' + CAST(YEAR(value) AS VARCHAR) + '.' +CONVERT(varchar,value,101) + '. ' + LTRIM(Field1)
FROM [TestTimeLine]
WHERE UserId = 10
RETURN #R
END
select dbo.TestFunction()
select PARSENAME(dbo.TestFunction(),2)
Working
select PARSENAME('January 2015.01/05/2015. TestDate',2)
NotWorking -- Returns NULL
select PARSENAME(dbo.TestFunction(),2)

Try changing the parameter 2 to 1.
select PARSENAME(dbo.TestFunction(),1)
For more details refer to:
More Details

The problem is that return value of dbo.TestFunction() is incorrect identifier - it exceeds maximal length limitation for object identifiers in SQL Server. Your sample return value has many trailing spaces which cause this situation.
In general, this is not good idea to use PARSENAME for your purpose because you are using the function not for its initial purpose so you have all side effects which are not predictable from one SQL Server version to another. Use ordinary string functions in your case.

I got the Issue.
Issue is [nchar] datatype.
After Changing to 'Varchar' datatype. Its working fine.

Related

Make SQL SERVER evaluate clauses in a certain order

Take the following table as an instance:
CREATE TABLE TBL_Names(Name VARCHAR(32))
INSERT INTO TBL_Names
VALUES ('Ken'),('1965'),('Karen'),('2541')
sqlfiddle
Executing following query throws an exception:
SELECT [name]
FROM dbo.tblNames AS tn
WHERE [name] IN ( SELECT [name]
FROM dbo.tblNames
WHERE ISNUMERIC([name]) = 1 )
AND [name] = 2541
Msg 245, Level 16, State 1, Line 1 Conversion failed when converting
the varchar value 'Ken' to data type int.
While the following query executes without error:
SELECT [name]
FROM dbo.tblNames AS tn
WHERE ISNUMERIC([name]) = 1
AND [name] = 2541
I know that this is because of SQL Server Query Optimizer's decision. but I am wondering if there is any way to make sql server evaluate clauses in a certain order. this way, in the first query,the first clause filters out those Names that are not numeric so that the second clause will not fail at converting to a number.
Update: As you may noticed, the above query is just an instance to exemplify the problem. I know the risks of that implicit conversion and appreciate those who tried to warn me of that. However my main question is how to change Optimizer's behavior of evaluating clauses in a certain order.
There is no "direct" way of telling the engine to perform operations in order. SQL isn't an imperative language where you have complete control of how to do things, you simply tell what you need and the server decides how to do it itself.
For this particular case, as long as you have [name] = 2541, you are risking a potential conversion failure since you are comparing a VARCHAR column against an INT. Even if you use a subquery/CTE there is still room for the optimizer to evaluate this expression first and try to convert all varchar values to int (thus failing).
You can evade this with workarounds:
Correctly comparing matching data types:
[name] = '2541'
Casting [name] to INT beforehand and only whenever possible and on a different statement, do the comparison.
DECLARE #tblNamesInt TABLE (nameInt INT)
INSERT INTO #tblNamesInt (
nameInt)
SELECT
[nameInt] = CONVERT(INT, [name])
FROM
dbo.tblNames
WHERE
TRY_CAST([name] AS INT) IS NOT NULL -- TRY_CAST better than ISNUMERIC for INT
SELECT
*
FROM
#tblNamesInt AS T
WHERE
T.nameInt = 2351 -- data types match
Even an index hint won't force the optimizer to use an index (that's why it's called a hint), so we have little control on how it gets stuff done.
There are a few mechanics that we know are evaluated in order and we can use to our advantage, such as the HAVING expressions will always be computed after grouping values, and the grouping always after WHERE conditions. So we can "safely" do the following grouping:
DECLARE #Table TABLE (IntsAsVarchar VARCHAR(100))
INSERT INTO #Table (IntsAsVarchar)
VALUES
('1'),
('2'),
('20'),
('25'),
('30'),
('A') -- Not an INT!
SELECT
CASE WHEN T.IntsAsVarchar < 15 THEN 15 ELSE 30 END,
COUNT(*)
FROM
#Table AS T
WHERE
TRY_CAST(T.IntsAsVarchar AS INT) IS NOT NULL -- Will filter out non-INT values first
GROUP BY
CASE WHEN T.IntsAsVarchar < 15 THEN 15 ELSE 30 END
But you should always avoid writing code that implies implicit conversions (like T.IntsAsVarchar < 15).
Try like this
SELECT [name]
FROM #TBL_Names AS tn
WHERE [name] IN ( SELECT [name]
FROM #TBL_Names
WHERE ISNUMERIC([name]) = 1 )
AND [name] = '2541'
2)
AND [name] = convert(varchar,2541 )
Since You are storing name as varchar(32) varchar will accept integer datatype values also called precedence value
What about:
SELECT *
FROM dbo.tblNames AS tn
WHERE [name] = convert(varchar, 2541)
Why do you need ISNUMERIC([name]) = 1) since you only care about the value '2541'?
You can try this
SELECT [name]
FROM dbo.TBL_Names AS tn
WHERE [name] IN ( SELECT [name]
FROM dbo.TBL_Names
WHERE ISNUMERIC([name]) = 1 )
AND [name] = '2541'
You need to just [name] = 2541 to [name] = '2541'. You are missing ' (single quote) with name in where condition.
You can find the live demo Here.
Honestly, I wouldn't apply the implicit cast to your column [name], it'll make the query non-SARGable. Instead, convert the value of your input (or pass it as a string)
SELECT [name]
FROM dbo.TBL_Names tn
WHERE [name] = CONVERT(varchar(32),2541);
If you "must", however, wrap [name] (and suffer performance degradation) then use TRY_CONVERT:
SELECT [name]
FROM dbo.TBL_Names tn
WHERE TRY_CONVERT(int,[name]) = 2541;

LIKE operator, N and % SQL Server doesn't work on nvarchar column

Is there any way to make following query Work?
declare #t nvarchar(20)
set #t='حس'
SELECT [perno] ,[pName]
FROM [dbo].[People]
Where [pName] like N''+#t +'%'
I cann't use like this:
Where [pName] like N'حس%'
Or using an stored procedure :
ALTER PROCEDURE [dbo].[aTest]
(#t nvarchar(20))
AS
BEGIN
SELECT [perno] ,[pName]
FROM [dbo].[People]
WHERE ([People].[pName] LIKE N'' +#t + '%')
END
You don't need to use N prefix in the WHERE clause since your variable is already nvarchar, and you are passing a variable not a literal string.
Here is an example:
CREATE TABLE People
(
ID INT,
Name NVARCHAR(45)
);
INSERT INTO People VALUES
(1, N'حسام'),
(2, N'حسان'),
(3, N'حليم');
DECLARE #Name NVARCHAR(45) = N'حس';--You need to use N prefix when you pass the string literal
SELECT *
FROM People
WHERE Name LIKE #Name + '%'; --You can use it here when you pass string literal, but since you are passing a variable, you don't need N here
Live demo
You may have seen Transact-SQL code that passes strings around using an N prefix. This denotes that the subsequent string is in Unicode (the N actually stands for National language character set). Which means that you are passing an NCHAR, NVARCHAR or NTEXT value, as opposed to CHAR, VARCHAR or TEXT.
From docs
Prefix Unicode character string constants with the letter N. Without the N prefix, the string is converted to the default code page of the database. This default code page may not recognize certain characters.
To answer your question in the comment with a simple answer, you are using the wrong datatype, so ALTER the stored procedure and change the datatype of your parameter from VARCHAR to NVARCHAR.
UPDATE:
Since you are using an SP, you can create your SP (according to your comment) as
CREATE PROCEDURE MyProc
(
#Var NVARCHAR(45)
)
AS
BEGIN
SELECT *
FROM People
WHERE Name LIKE ISNULL(#Var, Name) + '%';
--Using ISNULL() will return all rows if you pass NULL to the stored procedure
END
and call it as
EXEC MyProc N'حس'; --If you don't use N prefix then you are pass a varchar string
If you see, you need to use the N prefix when you pass literal string to your SP not inside the SP or the WHERE clause neither.
Demo for the SP
in these lines
declare #t nvarchar(20)
set #t='حس'
the 'حس' is a varchar constant that you then assign to an nvarchar variable. But you already lost data with the original conversion to that varchar constant and you cannot get that back.
The solution is to use an nvarchar constant:
set #t=N'حس'
It might be much simpler:
Try this
declare #t nvarchar(20)
set #t='حس';
SELECT #t; --the result is "??"
You are declaring the variable as NVARCHAR correctly. But the literal does not know its target. Without the N it is taken as a VARCHAR with the default collation.
The following line
Where [pName] like N''+#t +'%'
will search for a pName LIKE '??%'.
The solution should be
set #t=N'حس'; --<-- N-prefix

Escape SQL function string parameter within query

I have a SQL view that calls a scalar function with a string parameter. The problem is that the string occasionally has special characters which causes the function to fail.
The view query looks like this:
SELECT TOP (100) PERCENT
Id, Name, StartDate, EndDate
,dbo.[fnGetRelatedInfo] (Name) as Information
FROM dbo.Session
The function looks like this:
ALTER FUNCTION [dbo].[fnGetRelatedInfo]( #Name varchar(50) )
RETURNS varchar(200)
AS
BEGIN
DECLARE #Result varchar(200)
SELECT #Result = ''
SELECT #Result = #Result + Info + CHAR(13)+CHAR(10)
FROM [SessionInfo]
WHERE SessionName = #Name
RETURN #Result
END
How do I escape the name value so it will work when passed to the function?
I am guessing that the problem is non-unicode characters in dbo.Session.Name. Since the parameter to the function is VARCHAR, it will only hold unicode characters, so the non-unicode characters are lost when being passed to the function. The solution for this would be to change the parameter to be NVARCHAR(50).
However, if you care about performance, and more importantly consistent, reliable results stop using this function immediately. Alter your view to simply be:
SELECT s.ID,
s.Name,
s.StartDate,
s.EndDate,
( SELECT si.Info + CHAR(13)+CHAR(10)
FROM SessionInfo AS si
WHERE si.SessionName = s.Name
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') AS Information
FROM dbo.Session AS s;
Using variable concatenation can lead to unexpected results which are dependent on the internal pathways of the execution plan. So I would rule this out as a solution immediately. Not only this, the RBAR nature of a scalar UDF means that this will not scale well at all.
Various ways of doing this grouped concatenation have been benchmarked here, where CLR is actually the winner, but this is not always an option.

How to use literal backslash in SQL query statement?

EDIT2: Sorry everyone, problem solved. I WAS trying to concatenate a number with my string. I forgot that I simplified my statement when I posted here, thinking my problem had to do with the backslash rather than type compatibility. You guys are right and I was being an idiot. Thanks!
I'm using Access 2013 where my query pulls its data from a SQL 10.0 server (using pass-through).
I am trying to use the backslash in my SQL query like below (***edit: tbltask.jobnum is a string in my database):
SELECT [tblEstimator].[Name] + '\\20' + [tbltask].[JobNum] + ' JOBS\\' AS JobMidFilePath
But when I run the query, I get the error:
Conversion failed when converting the varchar value '\\20' to data type smallint. (#245)
I have no idea what this means or why it's trying to convert anything to smallint.
To replicate your issue, we can write a query something like this:
declare #name varchar(50) = 'Test',
#JobNum smallint = 12
select #name + '\\20' + #JobNum + ' JOBS\\';
This will return the same error:
Conversion failed when converting the varchar value 'Test\20' to data
type smallint.
But if you convert the smallint or the number field to a string like this, the error should go away:
declare #name varchar(50) = 'Test',
#JobNum smallint = 12
select #name + '\\20' + cast(#JobNum as varchar(10)) + ' JOBS\\'
Your query should look something like this:
SELECT [tblEstimator].[Name] + '\\20' + cast([tbltask].[JobNum] as varchar(10))
+ ' JOBS\\' AS JobMidFilePath
I am not sure the equivalent of MS-ACESS on this, but that should resolve your issue.
/*You can copy/paste directly into SQL, comments will be commented out ini SQL syntax
I would create a temp table to achieve this. */
USE whateverDB
GO
CREATE TABLE #toSelectFrom (
Name VARCHAR (100)
,JobNum TINYINT
/*look at the schema, it'll tell you what the data type is in SQL tables, in the column folder [see image] (http://i.ytimg.com/vi/MKPtdjq3MzA/maxresdefault.jpg) - copy verbatim to your temp table columns */
,slashPathNumber VARCHAR (25)
,slashPathJobs VARCHAR (50)
)
INSERT INTO #toSelectFrom (Name, slashPathNumber, JobNum, slashPathJobs)
SELECT [tblEstimator].[Name], '\\\\20', [tbltask].[JobNum], ' JOBS\\' AS JobMidFilePath /*** FROM yourTableToSelectFrom -- you NEED THIS FOR VALID SQL! ***/
--Then just:
SELECT * FROM #toSelectFrom;
-- OR Alternatively:
SELECT Name + slashPathNumber + JobNum + slashPathJobs AS JobMidFilePath FROM #toSelectFrom;
/*
If you use it a lot, just write the above select statement(s) into a view to select from more often. ;)
*/
First off, you can try to cast/convert your int as your output should be string. If the single quote doesn't work, you can try using double qoute (")
SELECT [tblEstimator].[Name] + "\\20" + CAST([tbltask].[JobNum] as varchar(100)) + " JOBS\\" AS JobMidFilePath

Efficiently replacing many characters from a string

I would like to know the most efficient way of removing any occurrence of characters like , ; / " from a varchar column.
I have a function like this but it is incredibly slow. The table has about 20 million records.
CREATE FUNCTION [dbo].[Udf_getcleanedstring] (#s VARCHAR(255))
returns VARCHAR(255)
AS
BEGIN
DECLARE #o VARCHAR(255)
SET #o = Replace(#s, '/', '')
SET #o = Replace(#o, '-', '')
SET #o = Replace(#o, ';', '')
SET #o = Replace(#o, '"', '')
RETURN #o
END
Whichever method you use it is probably worth adding a
WHERE YourCol LIKE '%[/-;"]%'
Except if you suspect that a very large proportion of rows will in fact contain at least one of the characters that need to be stripped.
As you are using this in an UPDATE statement then simply adding the WITH SCHEMABINDING attribute can massively improve things and allow the UPDATE to proceed row by row rather than needing to cache the entire operation in a spool first for Halloween Protection
Nested REPLACE calls in TSQL are slow anyway though as they involve multiple passes through the strings.
You could knock up a CLR function as below (if you haven't worked with these before then they are very easy to deploy from an SSDT project as long as CLR execution is permitted on the server). The UPDATE plan for this too does not contain a spool.
The Regular Expression uses (?:) to denote a non capturing group with the various characters of interest separated by the alternation character | as /|-|;|\" (the " needs to be escaped in the string literal so is preceded by a slash).
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text.RegularExpressions;
public partial class UserDefinedFunctions
{
private static readonly Regex regexStrip =
new Regex("(?:/|-|;|\")", RegexOptions.Compiled);
[SqlFunction]
public static SqlString StripChars(SqlString Input)
{
return Input.IsNull ? null : regexStrip.Replace((string)Input, "");
}
}
I want to show the huge performance differences between the using with 2 types of USER DIFINED FUNCTIONS:
User TABLE function
User SCALAR function
See the test example :
use AdventureWorks2012
go
-- create table for the test
create table dbo.FindString (ColA int identity(1,1) not null primary key,ColB varchar(max) );
declare #text varchar(max) = 'A web server can handle a Hypertext Transfer Protocol request either by reading
a file from its file ; system based on the URL <> path or by handling the request using logic that is specific
to the type of resource. In the case that special logic is invoked the query string will be available to that logic
for use in its processing, along with the path component of the URL.';
-- init process in loop 1,000,000
insert into dbo.FindString(ColB)
select #text
go 1000000
-- use one of the scalar function from the answers which post in this thread
alter function [dbo].[udf_getCleanedString]
(
#s varchar(max)
)
returns varchar(max)
as
begin
return replace(replace(replace(replace(#s,'/',''),'-',''),';',''),'"','')
end
go
--
-- create from the function above new function an a table function ;
create function [dbo].[utf_getCleanedString]
(
#s varchar(255)
)
returns table
as return
(
select replace(replace(replace(replace(#s,'/',''),'-',''),';',''),'"','') as String
)
go
--
-- clearing the buffer cach
DBCC DROPCLEANBUFFERS ;
go
-- update process using USER TABLE FUNCTIO
update Dest with(rowlock) set
dest.ColB = D.String
from dbo.FindString dest
cross apply utf_getCleanedString(dest.ColB) as D
go
DBCC DROPCLEANBUFFERS ;
go
-- update process using USER SCALAR FUNCTION
update Dest with(rowlock) set
dest.ColB = dbo.udf_getCleanedString(dest.ColB)
from dbo.FindString dest
go
AND these are the execution plan :
As you can see the UTF is much better the USF ,they 2 doing the same thing replacing string, but one return scalar and the other return as a table
Another important parameter for you to see (SET STATISTICS IO ON ;)
How about nesting them together in a single call:
create function [dbo].[udf_getCleanedString]
(
#s varchar(255)
)
returns varchar(255)
as
begin
return replace(replace(replace(replace(#s,'/',''),'-',''),';',''),'"','')
end
Or you may want to do an UPDATE on the table itself for the first time. Scalar functions are pretty slow.
Here is a similar question asked previously, I like this approach mentioned here.
How to Replace Multiple Characters in SQL?
declare #badStrings table (item varchar(50))
INSERT INTO #badStrings(item)
SELECT '>' UNION ALL
SELECT '<' UNION ALL
SELECT '(' UNION ALL
SELECT ')' UNION ALL
SELECT '!' UNION ALL
SELECT '?' UNION ALL
SELECT '#'
declare #testString varchar(100), #newString varchar(100)
set #teststring = 'Juliet ro><0zs my s0x()rz!!?!one!#!#!#!'
set #newString = #testString
SELECT #newString = Replace(#newString, item, '') FROM #badStrings
select #newString -- returns 'Juliet ro0zs my s0xrzone'