tsql set variable from select - sql

I have the following query:
declare #vlength varchar(50)
set #vLength =
(select
CASE
WHEN
SubString(SPSYS08.length, 0, CharIndex('"', SPSYS08.length)) > 0
AND SPSYS08.um = 'PC'
THEN SubString(SPSYS08.length, 0, CharIndex('"', SPSYS08.length)) / 12
else 1
end
From
SPSYS08 Inner Join
SPSYS07 On SPSYS08.loc_code = SPSYS07.loc_code And SPSYS08.so_no =
SPSYS07.so_no)
Select
SPSYS08.loc_code,
SPSYS08.so_no,
SPSYS08.line_no,
SPSYS07.invoice_date
From
SPSYS08 Inner Join
SPSYS07 On SPSYS08.loc_code = SPSYS07.loc_code And SPSYS08.so_no =
SPSYS07.so_no
Where
SPSYS07.invoice_date = '20131206'
We are running on SQL server 2008. What I am trying to do is assign the length of a part to the vLength variable. Length is a char as it contains the inches (") symbol. I either get conversion failed when converting the varchar value to int or a select statement that assigns a value to a variable must not be combined with data-retrieval options.
How can I get around this?

Use a CTE for this instead:
;WITH Lengths AS
(select
Code,
'L' =
CASE
WHEN
SubString(SPSYS08.length, 0, CharIndex('"', SPSYS08.length)) > 0
AND SPSYS08.um = 'PC'
THEN SubString(SPSYS08.length, 0, CharIndex('"', SPSYS08.length)) / 12
else 1
end
From
SPSYS08 Inner Join
SPSYS07 On SPSYS08.loc_code = SPSYS07.loc_code And SPSYS08.so_no =
SPSYS07.so_no)
Then you can
...
JOIN Lengths L
ON L.Code = SomeOtherTable.Code
...
and get the length for each record.

First of all this part should change
THEN SubString(SPSYS08.length, 0, CharIndex('"', SPSYS08.length)) / 12
the substring returns a string which you are trying to devide by 12 which will fail
you should probably use:
THEN CAST(SubString(SPSYS08.length, 0, CharIndex('"', SPSYS08.length)) AS INTEGER) / 12
but still you should make sure your substring returns a calue that is conveertable to integer
and second, it is possible that your select statement returns multiple rows which will cause your SET to fail, so you need to SELECT TOP 1 or something in order to fix that part.

Related

mssql cdc update_mask filter changes made only in column TS

I want to find the rows in my mssql cdc table where only the column "TS" has been changed.
So I found some logic to check if a specific column was changed (this works), but I need to check if only the column TS was changed:
SET #colorder = sys.fn_cdc_get_column_ordinal('dbo_mytable', 'TS')
SELECT case when substring([__$update_mask],len([__$update_mask]) - ((#colorder-1)/8),1) & power(2,(#colorder-1)%8) > 0 then 1 else 0 end
FROM cdc.fn_cdc_get_all_changes_dbo_MYTABLE(#from_lsn, #to_lsn, 'all') PD
I've been meaning to write functions like this for a while, thanks for giving me a reason to actually do it.
Please do some unit testing of your own, I have only done a few very basic checks
-- inline tabular function because it's more versatile
-- cross applies used purely for ease of reading,
-- could just make nested calls but hard to read. Up to you.
-- pass null to flip, otherwise pass explicit value you want the bit to be set to
create function dbo.setbit(#b varbinary(128), #bitpos tinyint, #value bit = null)
returns table as
return
(
select result = cast(result.val as varbinary(128))
from (select len(#b) - ((#bitpos - 1) / 8)) bytepos(val)
cross apply (select substring(#b, bytepos.val, 1)) byte(val)
cross apply (select power(2, (#bitpos - 1) % 8)) mask(val)
cross apply (
select cast
(
case #value
when 1 then byte.val | mask.val
when 0 then byte.val & ~mask.val
else byte.val ^ mask.val
end
as binary(1)
)
) newbyte(val)
cross apply (select stuff(#b, bytepos.val, 1, newbyte.val)) result(val)
);
-- scalar wrapper for tabular function
create function dbo.setbitscalar(#b varbinary(128), #bitpos tinyint, #value bit = null)
returns varbinary(128) as
begin
return (select result from dbo.setbit(#b, #bitpos, #value));
end
-- how it works
declare #b varbinary(128) = 0x0101 -- 2 bytes!
select
dbo.setbitscalar(#b, 1, 1), -- set bit 1 to 1
dbo.setbitscalar(#b, 1, 0), -- set bit 1 to 0
dbo.setbitscalar(#b, 1, default) -- flip bit 1
-- how to use it in your case:
-- set the #colorder bit in the mask to zero,
-- then see if the resulting mask is zero
-- if it is, then only TS changed
SET #colorder = sys.fn_cdc_get_column_ordinal('dbo_mytable', 'TS')
select only_TS_changed = iif(t.result = 0x, 1, 0)
from cdc.fn_cdc_get_all_changes_dbo_MYTABLE(#from_lsn, #to_lsn, 'all') PD
cross apply dbo.setbit(PD.[__$update_mask], #colorder, 0) t

Need to verify value in binary digit in T-SQL

Here is my problem. For example I have a binary digit 0010010 and I only need to verify that third value, from left to right, is 1. How do I do that in T-SQL ?
-- Addded estimated hours field
SET #EstHours = (SELECT SUM(ESTHOURS)
FROM LABORMP
WHERE #PMID = LABORMP.PMID AND ( LEFT(CYCLETYPE,3) = 1 OR LABORMP.CYCLETYPE is null)
)
Use substring() to get the 3d digit and convert to bit, which will return True or False:
declare #binary varchar(8) = '00100010'
select CONVERT(bit, substring(#binary, 3, 1))
returns
True --1
and
declare #binary varchar(8) = '00000010'
select CONVERT(bit, substring(#binary, 3, 1))
returns
False --0
Edit:
For your query you just need:
...SUBSTRING(CYCLETYPE, 3, 1) = '1' ...
if CYCLETYPE is a string.
Your query's logic can be writen like this:
SET #EstHours = (
SELECT SUM(ESTHOURS)
FROM LABORMP
WHERE
#PMID = PMID
AND
SUBSTRING(COALESCE(CYCLETYPE, '111'), 3, 1) = '1'
)
If the value is varbinary, you can use the bitwise and operator (&):
DECLARE #binaryValue varbinary(7) = 0010010;
SELECT 1
WHERE #binaryValue & 0010000 = 0010000
Try this:
SELECT 1
FROM yourtable
WHERE SUBSTRING(yourcolumn, 3, 1) = 1
You will get an empty result if the third digit is not 1

SQL script for removing extra characters

I've got MSSQL 2012 database with some data issues in the certain column A which contains text.
There are many occurences of aditional unnecesarry character after the </B> tag, for instance:
'<B>Something</B>g' where should stand '<B>Something</B>'
'<B>SomethingElse</B>e' where should stand '<B>SomethingElse</B>'
Previous values are part of a greater text, for instance and can occur more than once -> Column example:
'Some text is here <B>Something</B>g and also here <B>SomethingElse</B>e more text'
Those 'extra' characters are always the same as the last character between the <B></B> tags.
I would like to create SQL scripts which will:
Remove extra character after </B> tag
Only if extra character is the same as the last character between the
<B></B> tags (as a aditional check). EDIT: This is not absolutely necessary
I assuming there is a way of calling replace function, like in this pseudo in which X represents any character.
replace(X</B>X, X</B>);
But I am not very good in SQL and also I don't know how to implement 2. check.
Thank you for your help.
If your column has no other characters then just those strings, you could use this update statement on column a:
update mytable
set a = left(a, len(a)-1)
where left(right(a, 6), 5) = right(a, 1) + '</B>'
Here are some test cases in a fiddle.
To replace such occurrences in longer strings, where there might be multiple of them, then you can use this recursive query:
WITH recursive AS (
SELECT replace(a, '</B>', 'µ') as a
FROM mytable
UNION ALL
SELECT stuff(a, charindex('µ', a),
CASE WHEN substring(a, charindex('µ', a)-1, 1)
= substring(a, charindex('µ', a)+1, 1)
THEN 2
ELSE 1
END, '</B>')
FROM recursive
WHERE charindex('µ', a) > 0
)
SELECT *
FROM recursive
WHERE charindex('µ', a) = 0
The character µ that appears in several places should be a character that you do not expect to ever have in your data. Replace it by another character if necessary.
Here is a fiddle.
The above query turned into an update statement looks like below. It assumes that your table has a primary key id:
WITH recursive AS (
SELECT id,
replace(a, '</B>', 'µ') as a,
0 as modified
FROM mytable
UNION ALL
SELECT id,
stuff(a, charindex('µ', a),
CASE WHEN substring(a, charindex('µ', a)-1, 1)
= substring(a, charindex('µ', a)+1, 1)
THEN 2 ELSE 1 END, '</B>'),
1
FROM recursive
WHERE charindex('µ', a) > 0
)
UPDATE mytable
SET a = recursive.a
FROM recursive
INNER JOIN mytable
ON mytable.id = recursive.id
WHERE charindex('µ', recursive.a) = 0
AND recursive.modified = 1;
Here is the fiddle for that as well.
You can create a scalar function:
CREATE FUNCTION [dbo].[RemoveChars]
(
-- Add the parameters for the function here
#InputStr NVARCHAR(50)
)
RETURNS NVARCHAR(50)
AS
BEGIN
DECLARE #SearchStr NVARCHAR(4) = '</B>'
DECLARE #LastChar CHAR(1)
DECLARE #LastCharInStr CHAR(1)
DECLARE #Result NVARCHAR(50)
SET #LastChar = SUBSTRING(#InputStr,
CHARINDEX(#SearchStr, #InputStr) + LEN(#SearchStr), 1)
SET #LastCharInStr = SUBSTRING(#InputStr,
CHARINDEX(#SearchStr, #InputStr) - 1, 1)
IF (#LastCharInStr = #LastChar)
SET #Result = SUBSTRING(#InputStr, 0,
CHARINDEX(#SearchStr, #InputStr) + LEN(#SearchStr))
ELSE
SET #Result = #InputStr
RETURN #Result
END
And then call it:
UPDATE MyTable
Set A = dbo.RemoveChars(A)
Personally I would create a second function to only apply the updates to the values that have a difference between the last char in the string and the char after the but that's for you to decide.

How to produce random hex color in sql?

It's pretty hard topic for me because SQL is not my best skill ;)
I must insert random hex colors into database row. How can I do it? Is it possible to create function that will draw numbers?
SET [Color] = '#' + CONVERT(VARCHAR(max), CRYPT_GEN_RANDOM(3), 2)
Here is the logic explained below wrapped in a function for MySQL. It's very easy to use.
mysql> select random_color();
+----------------+
| random_color() |
+----------------+
| #8F50B4 |
+----------------+
1 row in set (0.00 sec)
It can be called over and over again and each time it will have a different color.
CREATE FUNCTION `random_color`() RETURNS char(7) CHARSET latin1 DETERMINISTIC
BEGIN
DECLARE str CHAR(7);
SET str = concat('#',SUBSTRING((lpad(hex(round(rand() * 10000000)),6,0)),-6));
RETURN str;
END;
This will give you six digit hex number codes in MySQL
SELECT concat('#',SUBSTRING((lpad(hex(round(rand() * 10000000)),6,0)),-6))
Here's a great one that will give you incremental colors
SELECT *,
concat('#',SUBSTRING((lpad(hex(#curRow := #curRow + 10),6,0)),-6)) AS color
FROM table
INNER JOIN (SELECT #curRow := 5426175) color_start_point
This works for me in MySQL:
mysql> SELECT CONCAT('#',LPAD(CONV(ROUND(RAND()*16777215),10,16),6,0)) AS color;
+---------+
| color |
+---------+
| #0E74A9 |
+---------+
1 row in set (0.00 sec)
Short explanation:
16777215 is the maximum 24 bit unsigned integer, that is: 224-1
Multiplied by RAND() and ROUND()'ed gives a random unsigned integer in the RGB color range: (0, 224-1)
Then, convert from base 10 to base 16 to get hexadecimal
LPAD() to zero-fill by left
And finally, CONCAT() to get the '#' character
with cte1 as (
select round(round(rand(),1)*15,0) as hex1,
round(round(rand(),1)*15,0) as hex2,
round(round(rand(),1)*15,0) as hex3,
round(round(rand(),1)*15,0) as hex4,
round(round(rand(),1)*15,0) as hex5,
round(round(rand(),1)*15,0) as hex6
),
cte2 as (
select case when hex1 = 10 then 'A'
when hex1 = 11 then 'B'
when hex1 = 12 then 'C'
when hex1 = 13 then 'D'
when hex1 = 14 then 'E'
when hex1 = 15 then 'F'
else str(hex1) end as hex1h,
case when hex2 = 10 then 'A'
when hex2 = 11 then 'B'
when hex2 = 12 then 'C'
when hex2 = 13 then 'D'
when hex2 = 14 then 'E'
when hex2 = 15 then 'F'
else str(hex2) end as hex2h,
case when hex3 = 10 then 'A'
when hex3 = 11 then 'B'
when hex3 = 12 then 'C'
when hex3 = 13 then 'D'
when hex3 = 14 then 'E'
when hex3 = 15 then 'F'
else str(hex3) end as hex3h,
case when hex4 = 10 then 'A'
when hex4 = 11 then 'B'
when hex4 = 12 then 'C'
when hex4 = 13 then 'D'
when hex4 = 14 then 'E'
when hex4 = 15 then 'F'
else str(hex4) end as hex4h,
case when hex5 = 10 then 'A'
when hex5 = 11 then 'B'
when hex5 = 12 then 'C'
when hex5 = 13 then 'D'
when hex5 = 14 then 'E'
when hex5 = 15 then 'F'
else str(hex5) end as hex5h,
case when hex6 = 10 then 'A'
when hex6 = 11 then 'B'
when hex6 = 12 then 'C'
when hex6 = 13 then 'D'
when hex6 = 14 then 'E'
when hex6 = 15 then 'F'
else str(hex6) end as hex6h from cte1)
select '#'+ltrim(hex1h)+ltrim(hex2h)+ltrim(hex3h)+ltrim(hex4h)+ltrim(hex5h)+ltrim(hex6h) from cte2
Here is a SQL Server Function which works in all versions from 2012 onwards that generates random HTML colour codes in hex format, i.e. #AABB00. It requires a View as well, which is also provided below.
The Function gives you the option to avoid light or dark colours, which can be useful if you are trying to generate background and foreground colours that won't clash too badly (i.e. dark text on a dark background).
CREATE FUNCTION dbo.Get_Random_Colour (#intStyle tinyint = 0)
RETURNS varchar(7)
/*
* Purpose: Returns the HTML colour code for a random colour
* Inputs: #intStyle - 0: does not filter the colour range
* 1: avoid dark colours
* 2: avoid light colours
*/
AS
BEGIN
DECLARE #c1 char(2), #c2 char(2), #c3 char(2)
DECLARE #i1 tinyint, #i2 tinyint, #i3 tinyint
DECLARE #strResult As varchar(255)
DECLARE #intLow tinyint = 0
DECLARE #intHigh tinyint = 255
IF #intStyle = 1
SET #intLow = 80
IF #intStyle = 2
SET #intHigh = 140
--Generate random numbers
SELECT #i1 = CAST(ROUND((#intHigh-#intLow) * RandNumber + #intLow,0) as int) from dbo.vRandNumber
SELECT #i2 = CAST(ROUND((#intHigh-#intLow) * RandNumber + #intLow,0) as int) from dbo.vRandNumber
SELECT #i3 = CAST(ROUND((#intHigh-#intLow) * RandNumber + #intLow,0) as int) from dbo.vRandNumber
--Convert them to hex format
SELECT #c1 = FORMAT(#i1, 'X')
SELECT #c2 = FORMAT(#i2, 'X')
SELECT #c3 = FORMAT(#i3, 'X')
--Pad them to two characters
SELECT #strResult = '#'
+ REPLICATE('0', 2-LEN(#c1)) + #c1
+ REPLICATE('0', 2-LEN(#c2)) + #c2
+ REPLICATE('0', 2-LEN(#c3)) + #c3
RETURN #strResult
END
GO
And here is the View that the Function is dependent on:
CREATE VIEW [dbo].[vRandNumber]
AS
SELECT RAND() as RandNumber
I'm not sure which DBMS are you using, but this solution is applied on SQL Server, and could be applied on a different DBMS if you know the corresponding syntax and functions.
(I will try to be as brief as I can in this)
Hex colors are basically a hexadecimal VARBINARY values which is supported by almost all DBMS. You only need the right conversion to bind it to your needs.
For example, if we cast a string foo as VARBINARY it'll return the value of 0x666F6F and if we cast back to VARCHAR it'll return foo
SELECT CAST('foo' AS VARBINARY)
-- Returns : 0x666F6F
SELECT CAST(0x666F6F AS VARCHAR)
-- Returns : foo
So, it's the same value with corresponded datatype even if the output is different. This is also applied in colors (hex to rgb, rgb to hex, hex to binary and so on).
Now, you know that Hex colors are using VARBINARY datatype, which will be our target in this solution.
The first thing is to generate that VARBINARY by using RGB values (which is another datatype of INT values). RGB values are three different values representing Red, Green, and Blue. The minimum number of each of them is 0 and the max is 255. So, if we do RGB(0,0,0) this will return #000000 (black) and RGB(255,255,255) returns #FFFFFF (white).
Oh, I forgot, you don't need any special equation in this conversion since DBMS is already handling this.
So, your first target is to do a function which takes RGB values (with min 0 and max 255) and then convert it to Hexadecimal VARBINARY, which will gives you the Hex color.
To Convert from RGB to Hex, you will need to know each 2 hexadecimal represent an RGB Value. For instance #BA55D3 is corresponded to rgb(186,85,211)
The actual hexadecimal for it is
R = 0xBA
G = 0x55
B = 0xD3
WHERE
0xBA = 186
0x55 = 18
0xD3 = 211
. So, from each Hex color, you'll need to divide it into 3 groups of two values to represent RGB values.
If you cast 0xBA as INT it'll give you 186 which is the value of the red part.
SELECT
CAST(0xBA AS INT)
-- Returns : 186
the same thing applies to the rest.
Now, we need our function to do the work for us, for this, I have created a function that simply takes three int values and convert them into VARBINARY which helps me to convert it back to varchar to just format the output into a hex color value.
CREATE FUNCTION RGBToHex
(
#R INT,
#G INT,
#B INT
)
RETURNS VARCHAR(7)
AS
BEGIN
DECLARE
#VarR VARBINARY,
#VarG VARBINARY,
#VarB VARBINARY,
#Result VARCHAR(7)
SELECT
#VarR = CONVERT(VARBINARY, #R) * 1 ,
#VarG = CONVERT(VARBINARY, #G) * 1 ,
#VarB = CONVERT(VARBINARY, #B) * 1
SET #Result = '#' + SUBSTRING(Convert(VARCHAR(MAX),#VarR, 1), 3, 2) + SUBSTRING(Convert(VARCHAR(MAX),#VarG, 1), 3, 2) + SUBSTRING(Convert(VARCHAR(MAX),#VarB, 1), 3, 2)
RETURN #Result
END
Example :
SELECT
RGBToHex(186,85,211)
-- Returns : #BA55D3
Now, the function is ready to be used, all we need is a way to randomize three INTs inside this function, to get a random color each time, taking the minimum and maximum (0-255) values for RGB in mind.
IN SQL Server I used a recursive query for the sake of simplicity, you could do your own query with your own method or function.
;WITH CTE AS (
SELECT
ABS(CHECKSUM(NewId())) % 256 AS R ,
ABS(CHECKSUM(NewId())) % 256 AS G ,
ABS(CHECKSUM(NewId())) % 256 AS B
)
SELECT
dbo.RGBToHex(R,G,B)
FROM CTE
In the query above I have set the random number between 0-255 for each column, then I just put these values inside the function to generate a new color.
If you need a reversed function that takes color's hexadecimal and return RGB value, you could use this function :
CREATE FUNCTION HexToRGB
(
#Hex VARCHAR(7)
)
RETURNS VARCHAR(100)
AS
BEGIN
DECLARE
#VarH VARCHAR(6),
#R INT,
#G INT,
#B INT,
#Result VARCHAR(100)
SET #VarH = (CASE WHEN LEFT(#Hex, 1) = '#' THEN SUBSTRING(#Hex, 2,6) ELSE #Hex END)
SELECT
#R = CONVERT(INT, CONVERT(VARBINARY, '0x' + SUBSTRING(#VarH, 1,2), 1) ),
#G = CONVERT(INT, CONVERT(VARBINARY, '0x' + SUBSTRING(#VarH, 3,2), 1) ),
#B = CONVERT(INT, CONVERT(VARBINARY, '0x' + SUBSTRING(#VarH, 5,2), 1) )
SET #Result = 'rgb(' + CAST(#R AS VARCHAR(3) ) + ',' + CAST(#G AS VARCHAR(3) ) + ',' + CAST(#B AS VARCHAR(3) ) + ')'
RETURN #Result
END
If you are using MySQL
select concat("#", conv(round(rand() * 255), 10, 16), conv(round(rand() * 255), 10, 16), conv(round(rand() * 255), 10, 16));
Here's a solution for PostgreSQL.
SELECT concat('#',lpad(to_hex(round(random() * 10000000)::int4),6,'0'))
I used it to set the random color to Color field of the table using that code:
UPDATE "MyTable" SET "Color" = concat('#',lpad(to_hex(round(random() * 10000000)::int8),6,'0'));
Guess you need to use rand - https://dev.mysql.com/doc/refman/5.0/en/mathematical-functions.html#function_rand - i.e.
INSERT INTO ... VALUES ( ... , ROUND(RAND(255 * 255 * 255)), ...)
For all SQL queries(MySql, SQL Server, etc) this method will work
In sql we can generate random colors using the substring function. Keep in mind that in sql charecter index starts from 1 (not 0 like javascript)
select concat('#',substring('0123456789ABCDEF',round(rand() * 15)+1,1),substring('0123456789ABCDEF',round(rand() * 15)+1,1),substring('0123456789ABCDEF',round(rand() * 15)+1,1),substring('0123456789ABCDEF',round(rand() * 15)+1,1),substring('0123456789ABCDEF',round(rand() * 15)+1,1),substring('0123456789ABCDEF',round(rand() * 15)+1,1));
Where round(rand() * 15) gives a random number between 0 and 15 including 0 and 15. Thats why we are adding 1 to it to get 1 as minimum value for the substring index
To get only Light Colors :-
select concat('#',substring('0123456789ABCDEF',round(rand() * 8)+8,1),substring('0123456789ABCDEF',round(rand() * 15)+1,1),substring('0123456789ABCDEF',round(rand() * 8)+8,1),substring('0123456789ABCDEF',round(rand() * 15)+1,1),substring('0123456789ABCDEF',round(rand() * 8)+8,1),substring('0123456789ABCDEF',round(rand() * 15)+1,1));
To get only Dark Colors :-
select concat('#',substring('0123456789ABCDEF',round(rand() * 7)+1,1),substring('0123456789ABCDEF',round(rand() * 15)+1,1),substring('0123456789ABCDEF',round(rand() * 7)+1,1),substring('0123456789ABCDEF',round(rand() * 15)+1,1),substring('0123456789ABCDEF',round(rand() * 7)+1,1),substring('0123456789ABCDEF',round(rand() * 15)+1,1));
Update:-
In all the above cases we can use a temporary variable to have the repeating range value to minimize the query like
set #r='0123456789ABCDEF';
select concat('#',substring(#r,round(rand() * 15)+1,1),substring(#r,round(rand() * 15)+1,1),substring(#r,round(rand() * 15)+1,1),substring(#r,round(rand() * 15)+1,1),substring(#r,round(rand() * 15)+1,1),substring(#r,round(rand() * 15)+1,1));

Apply a Mask to Format a String in SQL Server Query/View

Is there a neat way to apply a mask to a string in a SQL Server query?
I have two tables, one with Phone number stored as varchar with no literals 0155567890 and a phone type, which has a mask for that phone number type: (##) #### ####
What is the best way to return a string (for a merge Document) so that the query returns the fully formatted phone number:
(01) 5556 7890
As noted in the comment, my original answer below will result in terrible performance if used in a large number of rows. i-one's answer is preferred if performance is a consideration.
I needed this also, and thanks to Sjuul's pseudocode, I was able to create a function to do this.
CREATE FUNCTION [dbo].[fx_FormatUsingMask]
(
-- Add the parameters for the function here
#input nvarchar(1000),
#mask nvarchar(1000)
)
RETURNS nvarchar(1000)
AS
BEGIN
-- Declare the return variable here
DECLARE #result nvarchar(1000) = ''
DECLARE #inputPos int = 1
DECLARE #maskPos int = 1
DECLARE #maskSign char(1) = ''
WHILE #maskPos <= Len(#mask)
BEGIN
set #maskSign = substring(#mask, #maskPos, 1)
IF #maskSign = '#'
BEGIN
set #result = #result + substring(#input, #inputPos, 1)
set #inputPos += 1
set #maskPos += 1
END
ELSE
BEGIN
set #result = #result + #maskSign
set #maskPos += 1
END
END
-- Return the result of the function
RETURN #result
END
Just in case someone ever needs a table-valued function.
Approach 1 (see #2 for a faster version)
create function ftMaskPhone
(
#phone varchar(30),
#mask varchar(50)
)
returns table as
return
with ci(n, c, nn) as (
select
1,
case
when substring(#mask, 1, 1) = '#' then substring(#phone, 1, 1)
else substring(#mask, 1, 1)
end,
case when substring(#mask, 1, 1) = '#' then 1 else 0 end
union all
select
n + 1,
case
when substring(#mask, n + 1, 1) = '#' then substring(#phone, nn + 1, 1)
else substring(#mask, n + 1, 1)
end,
case when substring(#mask, n + 1, 1) = '#' then nn + 1 else nn end
from ci where n < len(#mask))
select (select c + '' from ci for xml path(''), type).value('text()[1]', 'varchar(50)') PhoneMasked
GO
Then apply it as
declare #mask varchar(50)
set #mask = '(##) #### ####'
select pm.PhoneMasked
from Phones p
outer apply ftMaskPhone(p.PhoneNum, #mask) pm
Approach 2
I'm going to leave the above version for historical purposes. However, this one has better performance.
CREATE FUNCTION dbo.ftMaskPhone
(
#phone varchar(30),
#mask varchar(50)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH v1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
v2(N) AS (SELECT 1 FROM v1 a, v1 b),
v3(N) AS (SELECT TOP (ISNULL(LEN(#mask), 0)) ROW_NUMBER() OVER (ORDER BY ##SPID) FROM v2),
v4(N, C) AS (
SELECT N, ISNULL(SUBSTRING(#phone, CASE WHEN c.m = 1 THEN ROW_NUMBER() OVER (PARTITION BY c.m ORDER BY N) END, 1), SUBSTRING(#mask, v3.N, 1))
FROM v3
CROSS APPLY (SELECT CASE WHEN SUBSTRING(#mask, v3.N, 1) = '#' THEN 1 END m) c
)
SELECT MaskedValue = (
SELECT c + ''
FROM v4
ORDER BY N
FOR XML PATH(''), TYPE
).value('text()[1]', 'varchar(50)')
);
GO
Schema binding, in combination with this being a single-statement table-valued-function, makes this version eligible for inlining by the query optimizer. Implement the function using a CROSS APPLY as in the example above, or for single values, like this:
SELECT *
FROM dbo.ftMaskPhone('0012345678910', '### (###) ###-####')
Results look like:
MaskedValue
001 (234) 567-8910
This is just what came up in my head. I don't know whether it's the best solution but I think it should be workable.
Make a function with the name applyMask (orso)
Pseudocode:
WHILE currentPosition < Length(PhoneNr) AND safetyCounter < Length(Mask)
IF currentSign = "#"
result += Mid(PhoneNr, currentPosition, 1)
currentPosition++
ELSE
result += currentSign
safetyCounter++
END
END
Return result
As noted by #Sean, SQL Server 2012 and up supports the FORMAT function, which almost gives you what you need, with the following caveats:
It takes a number to format, rather than a VARCHAR. This could be worked around by using a CAST.
The mask as provided ((##) #### ####), coupled with a CAST would remove the leading zero, leaving you with (1) 5556 7890. You could update the mask to (0#) #### ####. Going on a limb that you're representing an Australian phone number, it seems that the leading 0 is always there anyways:
Within Australia, to access the "Number" of a landline telephone in an "Area" other than that in which the caller is located (including a caller using a "Mobile" 'phone), firstly it is necessary to dial the Australian "Trunk Access Code" of 0 plus the "Area" code, followed by the "Local" Number. Thus, the "Full National Number" (FNN) has ten digits: 0x xxxx xxxx.
But ultimately, I would argue that SQL Server is not the best place to handle representation/formatting of your data (as with dates, so with phone numbers). I would recommend doing this client-side using something like Google's libphonenumber. When a phone number is entered into the database, you could store the phone number itself and the country to which it belongs, which you could then use when displaying the phone number (or doing something like calling it or checking for validity).
There is the built in FORMAT function, which almost works. Unfortunately it takes an int as the first parameter, so it strips off the leading zero:
select format(0155567890 ,'(##) #### ####')
(1) 5556 7890
If you need to "mask", rather hide the real value with another, and then "unmask" a string you can try this function, or extend it for that matter. :)
https://stackoverflow.com/a/22023329/2175524
I wanted to hide some information, so i used RIGHT function. It shows only first 4 chars from right side.
CONCAT('xxx-xx-', RIGHT('03466045896', 4))
Above code will show "xxx-xx-5896"