Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 9 years ago.
Improve this question
In this Question the accepted solution using bitwise XOR in PHP to find first different Charindex of two string. How can implement this solution in SQL query or is there a better solution for this problem?
Update: Here's Sql XOR solution:
only for string with len letter than 8
DECLARE #A VARCHAR(50)
DECLARE #B VARCHAR(50)
DECLARE #Pos INT
set #A='ABrC12#4'
set #B='ABrC1234'
SET #Pos=( select
case
when len(#A)<>LEN(#B) then -1
when #A=#B then 0
ELSE 1+PATINDEX('%[1-9]%',
CONVERT(varchar(max),cast(cast(CAST(#A AS varbinary) as BIGINT) ^
cast(CAST(#B AS varbinary) as BIGINT)
as varbinary ),2))/2
end)
Print #Pos
You can use a little nice result with this:
create FUNCTION [dbo].[fnFirstPosDif]
(
#Word as Nvarchar(70),
#Word2 as Nvarchar(70)
)
RETURNS INT
AS
BEGIN
declare #Strings2 TABLE
(
FirstPosDif INT
)
declare #FirstPosDif as int
;with C as
(
select #Word as Word,#Word2 as Word2 ,0 as Iteration
union all
select cast(substring(#Word,Iteration+1,1)as Nvarchar(70)) as Word,cast(substring(#Word2,Iteration+1,1)as Nvarchar(70)) as Word2,Iteration + 1 from C
WHERE Iteration < len(#Word) and cast(substring(#Word,Iteration+1,1)as Nvarchar(70))=cast(substring(#Word2,Iteration+1,1)as Nvarchar(70))
)
insert into #Strings2(FirstPosDif) select MAX(Iteration) as FirstPosDif from C
set #FirstPosDif=(select top 1 FirstPosDif from #Strings2)
return #FirstPosDif
END
I do not believe that there is an equivalent to any of the methods shown in the question you referenced for SQL Server.
The only way appears to be to manually walk through the string (as per the PHP suggestion), but in SQL it is even more laborious due to the fact that you cannot address CHAR types as if they were arrays:
DECLARE #A NVARCHAR(50)
DECLARE #B NVARCHAR(50)
DECLARE #I INT
DECLARE #Pos INT
SET #A = 'Hello World!'
SET #B = 'Hella World!'
SET #I = 0
SET #Pos = 0
-- Loop through each character
WHILE (#I < LEN(#A) AND #I < LEN(#B) AND #Pos = 0)
BEGIN
-- See if the characters at this position differ
IF (SUBSTRING(#A, #I, 1) != SUBSTRING(#B, #I, 1))
SET #Pos = #I
SET #I = #I + 1
END
IF (#Pos > 0)
PRINT 'Difference at position ' + CAST(#Pos AS VARCHAR) + ' A:' + SUBSTRING(#A, #Pos, 1) + ' B:' + SUBSTRING(#B, #Pos, 1)
ELSE
PRINT 'Strings are the same'
Running this as per the above would yield the following output:
Difference at position 5 A:o B:a
Note that you could put this into a UDF for simplicity if you use it often, and additionally I have not included checks for NULL values.
Related
I'm somewhat new to SQL Server and am trying to use a while loop inside of an INTO statement, but for some reason SQL Server is not letting me DECLARE my variable inside of the INTO statement... seems to be okay w/ the while loop though, but I need to be able to DECLARE and define my variable to use in the while loop...any ideas?
--Calculations portion of query
SELECT
FNL.*
INTO
#Final_Calc
FROM
(
DECLARE #i INT
SET #i = 0
WHILE (#StartDte + #i <= #EndDte)
BEGIN
SET #i = #i + 1
SELECT
YEAR(ACD.WorkDte) AS WorkDte,
'LOB - ALL ACM' AS RptType,
'Month' AS RptLvl
FROM
#FinalResults ACD
WHERE
ACD.WorkDte BETWEEN #StartDte AND #StartDte + #i
GROUP BY
Year(ACD.WorkDte), ACD.LOB
END
GO) fnl
So, it is not liking DECLARE #i INT portion...
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Concatenate many rows into a single text string?
I have 2 tables in SQL Server 2008 database - tblQuestion and tblSummary. I'm selecting QuestionIDs from tblQuestion as follows.
SELECT QuestionId from tblQuestion WHERE Status='Completed';
The result is as follows (from multiple records),
QuestionId
----------
1
2
5
7
8
9
[6 rows]
Now, I need to insert above selected IDs to "CompletedSections" column (which is VARCHAR type) in tblSummary. It should be inserted as a CSV like format - 1,2,5,7,8,9
For example, if I select those from tblSummary will be as follows.
SELECT CompletedSections FROM tblSummary WHERE <Some Condition>
Result should be,
CompletedSections
-----------------
1,2,5,7,8,9
[1 row]
How this can be done at the database level using t-SQL (not using any programming language like C#)? I'm hoping to implement this with t-SQL using a scheduled SQL SP/ Function/ Trigger.
Thanks,
Chatur
Here is a solution I have used in the past. It is a little hacky, but it should be faster than using a cursor to create the CSV. Here is some example code to get you started.
DECLARE #tblQuestion TABLE
(
QuestionId nvarchar(10)
)
DECLARE #tblSummary TABLE
(
CompletedSections nvarchar(100)
)
INSERT INTO #tblQuestion
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9)
INSERT INTO #tblSummary
SELECT SUBSTRING(
(SELECT ',' + QuestionId
from #tblQuestion
FOR XML PATH('')),2,200000) AS CSV
SELECT * FROM #tblSummary
This will do the job;
DECLARE #S VARCHAR(5000)
SELECT #S=ISNULL(#S+ ',', '') + CONVERT(VARCHAR(10),QuestionId)
FROM tblQuestion
SELECT #S
DECLARE #STRING VARCHAR(MAX) = ''
DECLARE #VALUE VARCHAR(MAX)
DECLARE A_CURSOR CURSOR FOR
SELECT QuestionId from tblQuestion WHERE Status='Completed'
OPEN A_CURSOR
FETCH A_CURSOR INTO #VALUE
WHILE ##FETCH_STATUS = 0
BEGIN
if #STRING <> ''
set #STRING = #STRING + ', '
set #STRING = #STRING + CONVERT(VARCHAR(MAX),#VALUE)
FETCH A_CURSOR INTO #VALUE
END
CLOSE A_CURSOR
DEALLOCATE A_CURSOR
INSERT INTO CompletedSection (tblSummary) SELECT #STRING
I am trying to decide whether to set min_word_length to be 2 or 3 on my new MySQL instance, so i figure if I count the number of 2 letter words in the column to be indexed it will give an indication of the right answer.
So, question is, is it possible using a SQL query to count the number two letter words within a column?
Not tested, but could you try
SELECT SUM(
CASE WHEN yourColumn REGEXP '[[:<:]][a-zA-Z]{2}[[:>:]]'
THEN 1
ELSE 0 END
) AS matches
FROM ...
Although I think, that it might only count if a 2-character-word is in a row, not how many times in one row.
UPDATE: Tested and it works like I thought, so please just ignore this answer.
The LEN() function returns the length of the value in a text field.
SELECT LEN(column_name) FROM table_name
The COUNT(*) function returns the number of records in a table:
SELECT COUNT(column_name) FROM table_name
So your query should be something like this :
SELECT COUNT(column_name) FROM table_name
WHERE LEN(column_name)=2
UPDATE:
Ok sorry I guess I misunderstood you. In the case you want it I'm afraid there is no build in functions to do what you want it to do. So you have to make a function on your own. Something like this should work:
CREATE FUNCTION [dbo].[WordCount] ( #InputString VARCHAR(4000) )
RETURNS INT
AS
BEGIN
DECLARE #Index INT
DECLARE #Char CHAR(1)
DECLARE #PrevChar CHAR(1)
DECLARE #WordCount INT
DECLARE #CharCount INT
SET #Index = 1
SET #WordCount = 0
WHILE #Index <= LEN(#InputString)
BEGIN
SET #Char = SUBSTRING(#InputString, #Index, 1)
SET #CharCount = #CharCount + 1
SET #PrevChar = CASE WHEN #Index = 1 THEN ' '
ELSE SUBSTRING(#InputString, #Index - 1, 1)
END
IF #PrevChar = ' '
SET #CharCount = 1
END
IF #Char = ' ' AND #CharCount < 3 AND #CharCount > 1
SET #WordCount = #WordCount + 1
SET #Index = #Index + 1
END
RETURN #WordCount
END
Now I have not tested it so you have to test it on your own but it should work. It will return all 2 letter words in the selected string. To get the 3 letters words just change
IF #Char = ' ' AND #CharCount < 4 AND #CharCount > 2
I hope this helps.
Is there a smarter way to remove all special characters rather than having a series of about 15 nested replace statements?
The following works, but only handles three characters (ampersand, blank and period).
select CustomerID, CustomerName,
Replace(Replace(Replace(CustomerName,'&',''),' ',''),'.','') as CustomerNameStripped
from Customer
One flexible-ish way;
CREATE FUNCTION [dbo].[fnRemovePatternFromString](#BUFFER VARCHAR(MAX), #PATTERN VARCHAR(128)) RETURNS VARCHAR(MAX) AS
BEGIN
DECLARE #POS INT = PATINDEX(#PATTERN, #BUFFER)
WHILE #POS > 0 BEGIN
SET #BUFFER = STUFF(#BUFFER, #POS, 1, '')
SET #POS = PATINDEX(#PATTERN, #BUFFER)
END
RETURN #BUFFER
END
select dbo.fnRemovePatternFromString('cake & beer $3.99!?c', '%[$&.!?]%')
(No column name)
cake beer 399c
Create a function:
CREATE FUNCTION dbo.StripNonAlphaNumerics
(
#s VARCHAR(255)
)
RETURNS VARCHAR(255)
AS
BEGIN
DECLARE #p INT = 1, #n VARCHAR(255) = '';
WHILE #p <= LEN(#s)
BEGIN
IF SUBSTRING(#s, #p, 1) LIKE '[A-Za-z0-9]'
BEGIN
SET #n += SUBSTRING(#s, #p, 1);
END
SET #p += 1;
END
RETURN(#n);
END
GO
Then:
SELECT Result = dbo.StripNonAlphaNumerics
('My Customer''s dog & #1 friend are dope, yo!');
Results:
Result
------
MyCustomersdog1friendaredopeyo
To make it more flexible, you could pass in the pattern you want to allow:
CREATE FUNCTION dbo.StripNonAlphaNumerics
(
#s VARCHAR(255),
#pattern VARCHAR(255)
)
RETURNS VARCHAR(255)
AS
BEGIN
DECLARE #p INT = 1, #n VARCHAR(255) = '';
WHILE #p <= LEN(#s)
BEGIN
IF SUBSTRING(#s, #p, 1) LIKE #pattern
BEGIN
SET #n += SUBSTRING(#s, #p, 1);
END
SET #p += 1;
END
RETURN(#n);
END
GO
Then:
SELECT r = dbo.StripNonAlphaNumerics
('Bob''s dog & #1 friend are dope, yo!', '[A-Za-z0-9]');
Results:
r
------
Bobsdog1friendaredopeyo
I faced this problem several years ago, so I wrote a SQL function to do the trick. Here is the original article (was used to scrape text out of HTML). I have since updated the function, as follows:
IF (object_id('dbo.fn_CleanString') IS NOT NULL)
BEGIN
PRINT 'Dropping: dbo.fn_CleanString'
DROP function dbo.fn_CleanString
END
GO
PRINT 'Creating: dbo.fn_CleanString'
GO
CREATE FUNCTION dbo.fn_CleanString
(
#string varchar(8000)
)
returns varchar(8000)
AS
BEGIN
---------------------------------------------------------------------------------------------------
-- Title: CleanString
-- Date Created: March 26, 2011
-- Author: William McEvoy
--
-- Description: This function removes special ascii characters from a string.
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
declare #char char(1),
#len int,
#count int,
#newstring varchar(8000),
#replacement char(1)
select #count = 1,
#len = 0,
#newstring = '',
#replacement = ' '
---------------------------------------------------------------------------------------------------
-- M A I N P R O C E S S I N G
---------------------------------------------------------------------------------------------------
-- Remove Backspace characters
select #string = replace(#string,char(8),#replacement)
-- Remove Tabs
select #string = replace(#string,char(9),#replacement)
-- Remove line feed
select #string = replace(#string,char(10),#replacement)
-- Remove carriage return
select #string = replace(#string,char(13),#replacement)
-- Condense multiple spaces into a single space
-- This works by changing all double spaces to be OX where O = a space, and X = a special character
-- then all occurrences of XO are changed to O,
-- then all occurrences of X are changed to nothing, leaving just the O which is actually a single space
select #string = replace(replace(replace(ltrim(rtrim(#string)),' ', ' ' + char(7)),char(7)+' ',''),char(7),'')
-- Parse each character, remove non alpha-numeric
select #len = len(#string)
WHILE (#count <= #len)
BEGIN
-- Examine the character
select #char = substring(#string,#count,1)
IF (#char like '[a-z]') or (#char like '[A-Z]') or (#char like '[0-9]')
select #newstring = #newstring + #char
ELSE
select #newstring = #newstring + #replacement
select #count = #count + 1
END
return #newstring
END
GO
IF (object_id('dbo.fn_CleanString') IS NOT NULL)
PRINT 'Function created.'
ELSE
PRINT 'Function NOT created.'
GO
I know this is an old thread, but still, might be handy for others.
Here's a quick and dirty (Which I've done inversely - stripping out non-numerics) - using a recursive CTE.
What makes this one nice for me is that it's an inline function - so gets around the nasty RBAR effect of the usual scalar and table-valued functions.
Adjust your filter as needs be to include or exclude whatever char types.
Create Function fncV1_iStripAlphasFromData (
#iString Varchar(max)
)
Returns
Table With Schemabinding
As
Return(
with RawData as
(
Select #iString as iString
)
,
Anchor as
(
Select Case(IsNumeric (substring(iString, 1, 1))) when 1 then substring(iString, 1, 1) else '' End as oString, 2 as CharPos from RawData
UNION ALL
Select a.oString + Case(IsNumeric (substring(#iString, a.CharPos, 1))) when 1 then substring(#iString, a.CharPos, 1) else '' End, a.CharPos + 1
from RawData r
Inner Join Anchor a on a.CharPos <= len(rtrim(ltrim(#iString)))
)
Select top 1 oString from Anchor order by CharPos Desc
)
Go
select * from dbo.fncV1_iStripAlphasFromData ('00000')
select * from dbo.fncV1_iStripAlphasFromData ('00A00')
select * from dbo.fncV1_iStripAlphasFromData ('12345ABC6789!&*0')
If you can use SQL CLR you can use .NET regular expressions for this.
There is a third party (free) package that includes this and more - SQL Sharp .
I'm working on a project involving C# and a SQL Server 2008 database.
In one of the tables, I have a field (nvarchar(15)) which will contain an IP address.
I'd like to add a check constraint which will validate that the input value is actually an IP address.
I wanted to use a regex to do that, but it seems that this feature is not supported by default. I saw things about writing a customm dll with UDF inside (MSDN tutorial), but I don't really understand how it works (i.e. where should I place the dll ?)
Is there a "simple" way to add such a constraint ?
Any solution is welcome.
Thanks in advance !
There are several way of doing this - the most performant one would probably be a CLR function in the database.
This is because SQL has fairly poor text manipulation tooling and no native RegEx in SQL Server.
As other have said, this is better handled by an application before insertion to the DB.
It shouldn't be handled in the database, it should be handled first and foremost in the application.
There's no harm in then adding a check to the database, but leaving it up to the DB to filter input is very sketchy.
The easiest way I can think of is to create a function like fnCheckIP and use this function in the constraint.
There's no need to use UDF.
create function fnCheckIP(#ip varchar(15)) returns bit
AS
begin
if (#ip is null)
return null
declare #num1 int
declare #num varchar(15)
declare #pos int
while (#ip is not null)
begin
set #pos = IsNull(NullIf(charindex('.', #ip), 0), Len(#ip) + 1)
set #num = substring(#ip, 1, #pos - 1)
if (isnumeric(#num) = 0) or (not cast(#num as int) between 0 and 255)
return cast(0 as bit)
if (len(#ip) - #pos <= 0)
set #ip = null
else
set #ip = NullIf(substring(#ip, #pos + 1, len(#ip) - #pos), '')
end
return cast (1 as bit)
end
go
select dbo.fnCheckIP('127.0.0.1')
select dbo.fnCheckIP('127.0.0.300')
This solution is similar to Paulo's but using either approach will require getting rid of the comma character because isnumeric allows commas which will throw a cast to int error.
CREATE FUNCTION fn_ValidateIP
(
#ip varchar(255)
)
RETURNS int
AS
BEGIN
DECLARE #Result int = 0
IF
#ip not like '%,%' and
len(#ip) <= 15 and
isnumeric(PARSENAME(#ip,4)) = 1 and
isnumeric(PARSENAME(#ip,3)) = 1 and
isnumeric(PARSENAME(#ip,2)) = 1 and
isnumeric(PARSENAME(#ip,1)) = 1 and
cast(PARSENAME(#ip,4) as int) between 1 and 255 and
cast(PARSENAME(#ip,3) as int) between 0 and 255 and
cast(PARSENAME(#ip,2) as int) between 0 and 255 and
cast(PARSENAME(#ip,1) as int) between 0 and 255
set #Result = 1
ELSE
set #Result = 0
RETURN #Result
END
select dbo.fn_ValidateIP('127.0.0.1')
This may not be entirely practical, but one way would be to store the converted string ###-###-###-### into a binary(4) data type. Let the interface fuss around with hyphens and deal with converting the four numbers to binary and back (and this could probably even be done by a caluclated column.) A bit extreme, yes, but with binary(4) you will always be able to turn it into an IP address.
At last about 10 yrs after Oracle, sqlserver got native compilation (with limitations)
ALTER function fn_ValidateIPv4
(
#ip varchar(255)
)
RETURNS int
--WITH EXECUTE AS OWNER, SCHEMABINDING, NATIVE_COMPILATION
AS
BEGIN
--ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english')
/* only sql2016 native Compilation **/
DECLARE #len_ip as int;
SET #len_ip = len(#ip);
DECLARE #firstBlock varchar(4) = '';
DECLARE #secondBlock varchar(4) = '';
DECLARE #thirdBlock varchar(4) = '';
DECLARE #fourthBlock varchar(4) = '';
DECLARE #countDot as smallint = 0;
DECLARE #l_i as smallint = 0;
DECLARE #l_curChar varchar(1) = 'X';
DECLARE #Result int = 0
IF (#len_ip <= 15)
BEGIN
WHILE (#l_i < #len_ip)
BEGIN
set #l_i += 1;
set #l_curChar = substring(#ip,#l_i,1);
if #l_curChar = '.'
SET #countDot += 1
ELSE
BEGIN
IF #l_curChar IN ( '0','1','2','3','4','5','6','7','8','9' )
BEGIN
IF #countDot = 0
SET #firstBlock = #firstBlock + #l_curChar;
IF #countDot = 1
SET #secondBlock = #secondBlock + #l_curChar;
IF #countDot = 2
SET #thirdBlock = #thirdBlock + #l_curChar;
IF #countDot = 3
SET #fourthBlock = #fourthBlock + #l_curChar;
IF #countDot > 3
set #firstBlock = 'AAA'; -- force error
END
ELSE set #firstBlock = 'AAA'; -- force error
END;
END;
IF ( #countDot = 3 and
cast(#fourthBlock as int) between 1 and 255 and
cast(#thirdBlock as int) between 0 and 255 and
cast(#secondBlock as int) between 0 and 255 and
cast(#firstBlock as int) between 0 and 255
)
set #Result = 1;
END;
/*
select dbo.fn_ValidateIPv4( '127.0.0.258' );
*/
RETURN #Result
END;
I had to remove not de-supported built functions isnumeric etc...