Why does this work in SSMS? - sql

I was in a database class the other night and we noticed that the following code seemed to work, but we could not logically understand why.
DECLARE #counter integer
SET #counter = 42
WHILE #counter < 52
BEGIN
set #counter = #counter+++++ + 1
PRINT 'The counter is ' + cast(#counter as char)
END
We realized that we could add any number of +'s on to our #counter variable, and SSMS does not seem to care, even though it does not match the original variable. Anybody happen to know why this works?

Appending a + to a variable name does not change the variable name, as white space is ignored (and + is not a valid character in a variable name anyway).
See here for a discussion about why multiple + signs works. Essentially, every + (or -) after the first one is considered a unary operator.

Related

SQL Server Function to Split Full Names

I have scoured SO for a resolution to my problem with getting this function to run properly, but I mostly see solutions regarding the use of the function and not as many in creating the function. I have already created the function, so that's why it reads 'ALTER FUNCTION' at the top of my code. The end goal is to parse out First Name, Middle Initial, and Last Name.
I keep getting an incorrect syntax error near the first 'END' in the CASE statement regarding the parsing of the FirstName. I apologize if this is such an easy fix but I just cannot figure out what I am missing. Any help in error recognition or a cleaner syntax would be much appreciated for a beginner like myself.
Also, the 2nd SET statement towards the bottom is just a simple function(already written before I got here) that CamelCases the output.
Sorry about the first comment. Here are some sample names that I have been using and I want to parse these from one column into 3 columns. First, middle, and last name.
Carlton J Smith
Charmane Thorn
Deel S Shah
Curtis Brennan
Allie F Allison
Alex Finde
Tina D Page
Jackie Russell
I tried adding two more SET statements but it still is giving me the same syntax error around the first CASE statement. Anything else I could provide to give more context? Thanks for the prompt responses.
ALTER FUNCTION fn_clean_Name_Split (#source VARCHAR(255))
RETURNS VARCHAR(255)
AS
BEGIN
DECLARE #target VARCHAR(255) = #source
DECLARE #index INT = CHARINDEX(' ',#target)
SET #target =
CASE
WHEN #index <> LEN(#target)
THEN LEFT(#target, #index)
END AS FirstName,
CASE
WHEN #index <> LEN(#target) - CHARINDEX(' ', REVERSE(#target)) + 1
THEN SUBSTRING(#target, #index + 1, LEN(#target) - CHARINDEX(' ', REVERSE(#target)) - #index)
END AS MI,
CASE
WHEN #index <> LEN(#target) - #index + 1
THEN RIGHT(#target, CHARINDEX(' ', REVERSE(#target))) AS LastName,
ELSE #target
SET #target = dbo.fn_standardize_CamelCase(#target)
RETURN #target
END
This is too long for a comment.
A set is used to set the value of a single variable, not multiple variables. This is easy to work around; you can just use multiple set statements.
You can set multiple variables using select. That is a nice convenience. But you cannot both set values and return values in the same statement.
You have a function, so you don't want a query that returns values anyway.

Extract substring from string if certain characters exists SQL

I have a string:
DECLARE #UserComment AS VARCHAR(1000) = 'bjones marked inspection on system UP for site COL01545 as Refused to COD won''t pay upfront :Routeid: 12 :Inspectionid: 55274'
Is there a way for me to extract everything from the string after 'Inspectionid: ' leaving me just the InspectionID to save into a variable?
Your example doesn't quite work correctly. You defined your variable as varchar(100) but there are more characters in your string than that.
This should work based on your sample data.
DECLARE #UserComment AS VARCHAR(1000) = 'bjones marked inspection on system UP for site COL01545 as Refused to COD won''t pay upfront :Routeid: 12 :Inspectionid: 55274'
select right(#UserComment, case when charindex('Inspectionid: ', #UserComment, 0) > 0 then len(#UserComment) - charindex('Inspectionid: ', #UserComment, 0) - 13 else len(#UserComment) end)
I would do this as:
select stuff(#UserComment, 1, charindex(':Inspectionid: ', #UserComment) + 14, '')
This works even if the string is not found -- although it will return the whole string. To get an empty string in this case:
select stuff(#UserComment, 1, charindex(':Inspectionid: ', #UserComment + ':Inspectionid: ') + 14, '')
Firstly, let me say that your #UserComment variable is not long enough to contain the text you're putting into it. Increase the size of that first.
The SQL below will extract the value:
DECLARE #UserComment AS VARCHAR(1000); SET #UserComment = 'bjones marked inspection on system UP for site COL01545 as Refused to COD won''t pay upfront :Routeid: 12 :Inspectionid: 55274'
DECLARE #pos int
DECLARE #InspectionId int
DECLARE #IdToFind varchar(100)
SET #IdToFind = 'Inspectionid: '
SET #pos = CHARINDEX(#IdToFind, #UserComment)
IF #pos > 0
BEGIN
SET #InspectionId = CAST(SUBSTRING(#UserComment, #pos+LEN(#IdToFind)+1, (LEN(#UserComment) - #pos) + 1) AS INT)
PRINT #InspectionId
END
You could make the above code into a SQL function if necessary.
If the Inspection ID is always 5 digits then the last argument for the Substring function (length) can be 5, i.e.
SELECT SUBSTRING(#UserComment,PATINDEX('%Inspectionid:%',#UserComment)+14,5)
If the Inspection ID varies (but is always at the end - which your question slightly implies), then the last argument can be derived by subtracting the position of 'InspectionID:' from the overall length of the string. Like this:
SELECT SUBSTRING(#UserComment,PATINDEX('%Inspectionid:%',#UserComment)+14,LEN(#usercomment)-(PATINDEX('%Inspectionid:%',#UserComment)+13))

How to format the SQL PRINT messages -- a kind of C printf()?

... Well, not exactly only for PRINT. I need to assign a string variable the value that mixes explicit substrings with integer values (and possibly with other type values). The goal is to get the string for logging.
So far, I use the code like:
DECLARE #msg nvarchar(1000)
...
SET #msg = #procname + 'result = ' + CAST(#result AS nvarchar(5))
+ '; error = ' + CAST(#error AS nvarchar(5))
where the #procname is a string like sp_my_proc:, and the #result and the #error are integer variables. The result should look like (no extra spaces around the numbers, just the minimum length):
sp_my_proc: result = 3; error = 0
The above approach works, but... Is there any better way for converting an integer variable to the string than CAST(#result AS nvarchar(5))? (Consider the magic number 5 being a minor detail to be ignored.)
How do you solve the problem of generating such strings in your code?
Thanks, Petr
In SQL-Server you can use STR() function
http://msdn.microsoft.com/ru-ru/library/ms189527.aspx
The default value for 'length' parameter is 10. Since an integer variable is never longer than 10 symbols (you'd have an overflow) this will always work without errors:
declare #test int
set #test = 333333333
select STR(#test)
Also, take a look at
String.Format like functionality in T-SQL?

Find and Replace credit card numbers

We have a large database with a lot of data in it. I found out recently our sales and shipping department have been using a part of the application to store clients credit card numbers in the open. We've put a stop to it, but now there are thousands of rows with the numbers.
We're trying to figure out how to scan certain columns for 16 digits in a row (or dash separation) and replace them with X's.
It's not a simple UPDATE statement because the card numbers are stored among large amounts of text. So far I've been unable to figure out if SQL Server is capable of regex (it would seem not).
All else fails i will do this through PHP since that is what i'm best at... but it'll be painful.
Sounds like you need to use PATINDEX with a WHERE LIKE clause.
Something like this. Create a stored proc with something similar, then call it with a bunch of different parameters (make #pattern & #patternlength the params) that you have identified, until you've replaced all of the instances.
declare #pattern varchar(100), #patternlength int
set #pattern = '[0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]'
set #patternlength = 19
update tableName
set fieldName =
LEFT(fieldName, patindex('%'+ #pattern + '%', fieldName)-1)
+ 'XXXX-XXXX-XXXX-XXXX'
+ SUBSTRING(fieldName, PATINDEX('%'+ #pattern + '%', fieldName)+#patternlength, LEN(fieldName))
from tableName
where fieldName like '%'+ #pattern + '%'
The trick is just finding the appropriate patterns, and setting the appropriate #patternlength value (not the length of #pattern as that won't work!)
I think you are better off doing this programatically, especially since you mentioned the data can be in a couple of different formats. Do keep in mind that not all credit card numbers are 16 digits long (Amex is 15, Visa is 13 or 16, etc).
The ability to check for various regexes and validate code will probably be best served at a cleanup job level, if possible.
Improvised Sean's answer.
The following will find all the occurrences of #maskPattern in #text and replace them with 'x'.
Example, If #maskPattern = XXXX-XXXX-XXXX-XXXX, it will find this pattern in #text and replace all occurrences with XXXX-XXXX-XXXX-XXXX. If it does not find any occurrence, it will leave the text as is.
This stored procedure can also be manipulated to only mask 3/4th of the beginning of the maskPattern. Cheers!
ALTER PROCEDURE [dbo].[SP_MaskCharacters] #text nvarchar(max),
#maskPattern nvarchar(500)
AS
BEGIN
DECLARE #numPattern nvarchar(max) = REPLACE(#maskPattern, 'x', '[0-9]')
DECLARE #patternLength int = LEN(#maskPattern)
WHILE (#text IS NOT NULL)
BEGIN
IF PATINDEX('%' + #numPattern + '%', #text) = 0 BREAK;
SET #text =
LEFT(#text, PATINDEX('%' + #numPattern + '%', #text)-1) --Get beginning chars of the input text until first occurance of pattern is found
+ #maskPattern --Append aasking pattern
+ SUBSTRING(#text, PATINDEX('%' + #numPattern + '%', #text) + #patternLength, LEN(#text)) -- Get & append rest of the text found after masking attern
END
SELECT #text
END
I faced this situation recently. Using Patindex and Stuff should help, but you would need to repeat for CC numbers with different number of digits separately.
-- For 16 digits CC numbers
UPDATE table
SET columnname = Stuff (columnname, Patindex(
'%[3-6][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]%'
, columnname), 16, '################')
WHERE Patindex(
'%[3-6][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]%'
, columnname) > 0
You can use patindex. It won't be pretty and there might be a more concise way to write it. But you can use sets ie [0-9]
patindex: http://msdn.microsoft.com/en-us/library/ms188395.aspx
similar question: SQL Server Regular expressions in T-SQL
For anyone finding this question who does want to use PHP, here's a function I use that takes a credit card number (all digits, with dashes, or with spaces) and replaces all but the first and last 4 digits with 'X'.
To accept credit card numbers with dashes as well, use this regex pattern instead:
$cc_regex_pattern = '/(\d{4})(-)?(\d{4})(-)?(\d{4})(-)?(\d{4})/'
and remove the preprocessing of the cc number that removes the dashes:
$compressed_cc_number = preg_replace('/(\ |-)/', '', $credit_card_number);
and so the replacement string becomes (because we've changed the index of patterns - note the $7):
$cc_regex_replacement = '$1' . $cc_middle_pattern . '$7';
or if you want, simply replace the whole cc number, like in the original question:
$cc_regex_replacement = 'XXXX$2XXXX$4XXXX$6XXXX';
Here's the original function for credit card numbers with or without spaces or dashes, which obfuscates and removes any dashes:
/**
* #param integer|string $credit_card_number
* #return mixed
*/
static function obfuscate_credit_card($credit_card_number)
{
$compressed_cc_number = preg_replace('/(\ |-)/', '', $credit_card_number);
$cc_length = strlen($compressed_cc_number);
$cc_middle_length = $cc_length >= 9 ? $cc_length - 8 : 0;
//create middle pattern
$cc_middle_pattern = '';
for ($i = 0; $i < $cc_middle_length; $i++) {
$cc_middle_pattern .= 'X';
}
//replace cc middle digits with middle pattern
$cc_regex_pattern = '/(\d{4})(\d+)(\d{4})/';
$cc_regex_replacement = '$1' . $cc_middle_pattern . '$3';
$obfuscated_cc = preg_replace($cc_regex_pattern, $cc_regex_replacement, $compressed_cc_number);
return $obfuscated_cc;
}

Dangling "else" Resolution in T-SQL

So I've been tasked with converting some T-SQL code to C code. Whoever wrote the code I'm converting indulged in little to no code etiquette. I know this because of the complete lack of commenting, lack of indentation, and lack of begin/end blocks except where absolutely syntactically necessary (and a few thrown in arbitrarily for good measure).
This raises a few problems. The code I'm converting is based on the Metaphone algorithm. I say "based on" because it has quite a few... "undocumented improvements" that make it differ from the official implementation. Because of this, I can't just go grab some Metaphone implementation, because then it wouldn't actually be a "correct" translation.
So here's the root of the issue(s):
if #str1='d'
if substring(#str,#cnt,3) in ('dge','dgy','dgi')
set #Result=#Result + 'j'
else
set #Result=#Result + 't'
Because of how Metaphone works, I'm pretty sure they meant:
if #str1='d'
if substring(#str,#cnt,3) in ('dge','dgy','dgi')
set #Result=#Result + 'j'
else
set #Result=#Result + 't'
But I'm not sure if it's actually being interpreted as:
if #str1='d'
if substring(#str,#cnt,3) in ('dge','dgy','dgi')
set #Result=#Result + 'j'
else
set #Result=#Result + 't'
This little snippet isn't too big of a deal, but just after it, there's a section with five "if" statements and only one "else" statement and with no begin/end blocks to explicitly arrange them. None of this would be a big deal if I could actually run the code to to test and see, but sadly, I don't have an environment to test it and nor do I have any output from previous usage of the code.
tl;dr: Do any of you T-SQL gurus out there know which of the above two statements it'll be interpreted as and what's the rule of them with shift/reduce conflicts in T-SQL? (Attach to first "if" statement, attach to last "if" statement, pick one at random?)
EDIT: Here's another fun one a few lines down.
if #str1='t'
if substring(#str,#cnt,3) in ('tia','tio')
set #Result=#Result + 'x'
else
if #str2='th'
set #Result=#Result + '0'
else
if substring(#str,#cnt,3) <> 'tch'
set #Result=#Result + 't'
EDIT2: Ok, if I'm reading these answers correctly, that means the above is actually
if #str1='t'
if substring(#str,#cnt,3) in ('tia','tio')
set #Result=#Result + 'x'
else
if #str2='th'
set #Result=#Result + '0'
else
if substring(#str,#cnt,3) <> 'tch'
set #Result=#Result + 't'
Here is something that should help you out
DECLARE #testvar INT;
DECLARE #testvar2 INT;
SET #testvar = 1;
SET #testvar2 = 1;
IF #testvar = 1
IF #testvar2 = 1
SELECT 'Got to 1';
ELSE
SELECT 'Got to 2';
If testvar and testvar2 are both 1, it outputs "Got to 1".
If testvar=1 and testvar2=2, it outputs "Got to 2".
If testvar=2, there is no output. So the else is getting paired off against the nearest if
Your assumption is correct.
IF () IF () X ELSE Y is equivilent to IF () BEGIN IF () X ELSE Y END
The ELSE keyword looks back to the most recent IF statement in the same scope. So to get what you think was intended, you need to add BEGIN and END statements...
IF (#str1='d')
BEGIN
IF (substring(#str,#cnt,3) in ('dge','dgy','dgi'))
SET #Result=#Result + 'j'
END
ELSE
SET #Result=#Result + 't'