Sequential IP Block at T-SQL - sql-server

How to get these IP blocks via T-SQL like above? I try WITH CTE block but I didn't do. Can you suggest about how to do?
EDIT: I wrote wrong. Not WITH CTE, is WHILE
10.230.0.1
10.230.1.1
10.230.x.1
10.230.x.1
...
10.249.253.1
10.249.254.1
10.249.255.1

Try with tally tables:
;WITH t0 AS (SELECT 0 n UNION ALL SELECT 0)
,t1 AS (SELECT 0 n FROM t0 a CROSS JOIN t0 b)
,t2 AS (SELECT 0 n FROM t1 a CROSS JOIN t1 b)
,t3 AS (SELECT 0 n FROM t2 a CROSS JOIN t2 b)
,t (n) AS (SELECT -1 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM t3)
SELECT '10.' + CAST(t1.n AS NVARCHAR(3)) + '.' + CAST(t2.n AS NVARCHAR(3)) +'.1' AS IP
FROM t t1
CROSS JOIN t t2
WHERE t1.n BETWEEN 230 AND 249
ORDER BY t1.n, t2.n

There is another solution I created. Maybe it is helpful for other people.
CREATE FUNCTION fnTblATMList
(
#From INT,
#To INT
)
RETURNS #table TABLE (IP_Address NVARCHAR(50))
AS
BEGIN
Declare #third int
while (#From <= #To)
BEGIN
set #third = 1
while (#third <= 255)
BEGIN
INSERT INTO #table
select '10.' + CONVERT(nvarchar(3), #From) + '.' + CONVERT(NVARCHAR(3), #third) +'.2'
SET #third = #third + 1
END
SET #From = #From +1
END
RETURN
END
GO

Another approach
;with seg as (
select
0 num
union all
select
num + 1
from
seg
where
num < 255
)
select
cast(seg1.num as varchar)
+ '.' + cast(seg2.num as varchar)
+ '.' + cast(seg3.num as varchar)
+ '.' + cast(seg4.num as varchar)
from
seg seg1,
seg seg2,
seg seg3,
seg seg4
where
seg1.num = 10 -- Really need to filter
and seg2.num between 230 and 249 -- on at least two of these
-- and seg3.num = '0' -- or the query will run
and seg4.num = 1 -- for a very long time
order by
seg1.num,
seg2.num,
seg3.num,
seg4.num
option (maxrecursion 0)

Related

return value at a position from STRING_SPLIT in SQL Server 2016

Can I return a value at a particular position with the STRING_SPLIT function in SQL Server 2016 or higher?
I know the order from a select is not guaranteed, but is it with STRING_SPLIT?
DROP TABLE IF EXISTS #split
SELECT 'z_y_x' AS splitIt
INTO #split UNION
SELECT 'a_b_c'
SELECT * FROM #split;
WITH cte
AS (
SELECT ROW_NUMBER() OVER ( PARTITION BY s.splitIt ORDER BY s.splitIt ) AS position,
s.splitIt,
value
FROM #split s
CROSS APPLY STRING_SPLIT(s.splitIt, '_')
)
SELECT * FROM cte WHERE position = 2
Will this always return the value at the 2nd element? b for a_b_c and y for z_y_x?
I don't understand why Microsoft doesn't return a position indicator column alongside the value for this function.
There is - starting with v2016 - a solution via FROM OPENJSON():
DECLARE #str VARCHAR(100) = 'val1,val2,val3';
SELECT *
FROM OPENJSON('["' + REPLACE(#str,',','","') + '"]');
The result
key value type
0 val1 1
1 val2 1
2 val3 1
The documentation tells clearly:
When OPENJSON parses a JSON array, the function returns the indexes of the elements in the JSON text as keys.
For your case this was:
SELECT 'z_y_x' AS splitIt
INTO #split UNION
SELECT 'a_b_c'
DECLARE #delimiter CHAR(1)='_';
SELECT *
FROM #split
CROSS APPLY OPENJSON('["' + REPLACE(splitIt,#delimiter,'","') + '"]') s
WHERE s.[key]=1; --zero based
Let's hope, that future versions of STRING_SPLIT() will include this information
UPDATE Performance tests, compare with popular Jeff-Moden-splitter
Try this out:
USE master;
GO
CREATE DATABASE dbTest;
GO
USE dbTest;
GO
--Jeff Moden's splitter
CREATE FUNCTION [dbo].[DelimitedSplit8K](#pString VARCHAR(8000), #pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH E1(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
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
GO
--Avoid first call bias
SELECT * FROM dbo.DelimitedSplit8K('a,b,c',',');
GO
--Table to keep the results
CREATE TABLE Results(ID INT IDENTITY,ResultSource VARCHAR(100),durationMS INT, RowsCount INT);
GO
--Table with strings to split
CREATE TABLE dbo.DelimitedItems(ID INT IDENTITY,DelimitedNString nvarchar(4000),DelimitedString varchar(8000));
GO
--Get rows wiht randomly mixed strings of 100 items
--Try to play with the count of rows (count behind GO) and the count with TOP
INSERT INTO DelimitedItems(DelimitedNString)
SELECT STUFF((
SELECT TOP 100 ','+REPLACE(v.[name],',',';')
FROM master..spt_values v
WHERE LEN(v.[name])>0
ORDER BY NewID()
FOR XML PATH('')),1,1,'')
--Keep it twice in varchar and nvarchar
UPDATE DelimitedItems SET DelimitedString=DelimitedNString;
GO 500 --create 500 differently mixed rows
--The tests
DECLARE #d DATETIME2;
SET #d = SYSUTCDATETIME();
SELECT DI.ID, DS.Item, DS.ItemNumber
INTO #TEMP
FROM dbo.DelimitedItems DI
CROSS APPLY dbo.DelimitedSplit8K(DI.DelimitedNString,',') DS;
INSERT INTO Results(ResultSource,RowsCount,durationMS)
SELECT 'delimited8K with NVARCHAR(4000)'
,(SELECT COUNT(*) FROM #TEMP) AS RowCountInTemp
,DATEDIFF(MILLISECOND,#d,SYSUTCDATETIME()) AS Duration_NV_ms_delimitedSplit8K
SET #d = SYSUTCDATETIME();
SELECT DI.ID, DS.Item, DS.ItemNumber
INTO #TEMP2
FROM dbo.DelimitedItems DI
CROSS APPLY dbo.DelimitedSplit8K(DI.DelimitedString,',') DS;
INSERT INTO Results(ResultSource,RowsCount,durationMS)
SELECT 'delimited8K with VARCHAR(8000)'
,(SELECT COUNT(*) FROM #TEMP2) AS RowCountInTemp
,DATEDIFF(MILLISECOND,#d,SYSUTCDATETIME()) AS Duration_V_ms_delimitedSplit8K
SET #d = SYSUTCDATETIME();
SELECT DI.ID, OJ.[Value] AS Item, OJ.[Key] AS ItemNumber
INTO #TEMP3
FROM dbo.DelimitedItems DI
CROSS APPLY OPENJSON('["' + REPLACE(DI.DelimitedNString,',','","') + '"]') OJ;
INSERT INTO Results(ResultSource,RowsCount,durationMS)
SELECT 'OPENJSON with NVARCHAR(4000)'
,(SELECT COUNT(*) FROM #TEMP3) AS RowCountInTemp
,DATEDIFF(MILLISECOND,#d,SYSUTCDATETIME()) AS Duration_NV_ms_OPENJSON
SET #d = SYSUTCDATETIME();
SELECT DI.ID, OJ.[Value] AS Item, OJ.[Key] AS ItemNumber
INTO #TEMP4
FROM dbo.DelimitedItems DI
CROSS APPLY OPENJSON('["' + REPLACE(DI.DelimitedString,',','","') + '"]') OJ;
INSERT INTO Results(ResultSource,RowsCount,durationMS)
SELECT 'OPENJSON with VARCHAR(8000)'
,(SELECT COUNT(*) FROM #TEMP4) AS RowCountInTemp
,DATEDIFF(MILLISECOND,#d,SYSUTCDATETIME()) AS Duration_V_ms_OPENJSON
GO
SELECT * FROM Results;
GO
--Clean up
DROP TABLE #TEMP;
DROP TABLE #TEMP2;
DROP TABLE #TEMP3;
DROP TABLE #TEMP4;
USE master;
GO
DROP DATABASE dbTest;
Results:
200 items in 500 rows
1220 delimited8K with NVARCHAR(4000)
274 delimited8K with VARCHAR(8000)
417 OPENJSON with NVARCHAR(4000)
443 OPENJSON with VARCHAR(8000)
100 items in 500 rows
421 delimited8K with NVARCHAR(4000)
140 delimited8K with VARCHAR(8000)
213 OPENJSON with NVARCHAR(4000)
212 OPENJSON with VARCHAR(8000)
100 items in 5 rows
10 delimited8K with NVARCHAR(4000)
5 delimited8K with VARCHAR(8000)
3 OPENJSON with NVARCHAR(4000)
4 OPENJSON with VARCHAR(8000)
5 items in 500 rows
32 delimited8K with NVARCHAR(4000)
30 delimited8K with VARCHAR(8000)
28 OPENJSON with NVARCHAR(4000)
24 OPENJSON with VARCHAR(8000)
--unlimited length (only possible with OPENJSON)
--Wihtout a TOP clause while filling
--results in about 500 items in 500 rows
1329 OPENJSON with NVARCHAR(4000)
1117 OPENJSON with VARCHAR(8000)
Facit:
the popular splitter function does not like NVARCHAR
the function is limited to strings within 8k byte volumen
Only the case with many items and many rows in VARCHAR lets the splitter function be ahead.
In all other cases OPENJSON seems to be more or less faster...
OPENJSON can deal with (almost) unlimited counts
OPENJSON demands for v2016
Everybody is waiting for STRING_SPLIT with the position
UPDATE Added STRING_SPLIT to the test
In the meanwhile I re-run the test with two more test sections using STRING_SPLIT(). As position I had to return a hardcoded value as this function does not return the part's index.
In all tested cases OPENJSON was close with STRING_SPLIT and often faster:
5 items in 1000 rows
250 delimited8K with NVARCHAR(4000)
124 delimited8K with VARCHAR(8000) --this function is best with many rows in VARCHAR
203 OPENJSON with NVARCHAR(4000)
204 OPENJSON with VARCHAR(8000)
235 STRING_SPLIT with NVARCHAR(4000)
234 STRING_SPLIT with VARCHAR(8000)
200 items in 30 rows
140 delimited8K with NVARCHAR(4000)
31 delimited8K with VARCHAR(8000)
47 OPENJSON with NVARCHAR(4000)
31 OPENJSON with VARCHAR(8000)
47 STRING_SPLIT with NVARCHAR(4000)
31 STRING_SPLIT with VARCHAR(8000)
100 items in 10.000 rows
8145 delimited8K with NVARCHAR(4000)
2806 delimited8K with VARCHAR(8000) --fast with many rows!
5112 OPENJSON with NVARCHAR(4000)
4501 OPENJSON with VARCHAR(8000)
5028 STRING_SPLIT with NVARCHAR(4000)
5126 STRING_SPLIT with VARCHAR(8000)
The simple answer is, no. Microsoft so far have refused to provide Ordinal position as part of the return dataset in STRING_SPLIT. You'll need to use a different solution I'm afraid. For example Jeff Moden's DelimitedSplit8k.
(Yes, I realise this is more or less a link only answer, however, pasting Jeff's solution here would effectively be plagiarism).
If you were to use Jeff's solution, then you would be able to do something like:
SELECT *
FROM dbo.DelimitedSplit8K('a,b,c,d,e,f,g,h,i,j,k',',') DS
WHERE ItemNumber = 2;
Of course, you'd likely be passing column rather than a literal string.
I just extended #Shnugo's answer if the splitted text would contain line breaks, unicode and other non json compatible characters, to use
STRING_ESCAPE
My Test code with pipe as separator instead comma:
DECLARE #Separator VARCHAR(5) = STRING_ESCAPE('|', 'json'); -- here pipe or use any other separator (even ones escaped by json)
DECLARE #LongText VARCHAR(MAX) = 'Albert says: "baby, listen!"|ve Çağrı söylüyor: "Elma"|1st Line' + CHAR(13) + CHAR(10) + '2nd line';
SELECT * FROM OPENJSON('["' + REPLACE(STRING_ESCAPE(#LongText, 'json'), #Separator ,'","') + '"]'); -- ok
-- SELECT * FROM OPENJSON('["' + REPLACE(#LongText, #Separator ,'","') + '"]'); -- fails with: JSON text is not properly formatted. ...
Updated due to comment from Simon Zeinstra
I didn't want to deal with OPENJSON, but still wanted to get string_split() value by index.
The performance was not an issue in my case.
I used CTE (Common Table Expression)
Assume you have string str = "part1 part2 part3".
WITH split_res_list as
(
SELECT value FROM STRING_SPLIT('part1 part2 part3', ' ')
),
split_res_list_with_index as
(
SELECT [value],
ROW_NUMBER() OVER (ORDER BY [value] ASC) as [RowNumber]
FROM split_res_list
)
SELECT * FROM split_res_list_with_index WHERE RowNumber = 2
BUT: please be aware that the order of 3 parts is changed according to ORDER BY condition!
The output for the second row with "part2" value:
Using STRING_SPLIT:
STRING_SPLIT ( string , separator [ , enable_ordinal ] )
enable_ordinal
An int or bit expression that serves as a flag to enable or disable the ordinal output column. A value of 1 enables the ordinal column. If enable_ordinal is omitted, NULL, or has a value of 0, the ordinal column is disabled.
The enable_ordinal argument and ordinal output column are currently only supported in Azure SQL Database, Azure SQL Managed Instance, and Azure Synapse Analytics (serverless SQL pool only).
Query:
SELECT value FROM STRING_SPLIT('part1_part2_part3', '_', 1) WHERE ordinal = 2;
Here is my workaround. I will follow the Question waiting for a better answer:
UPDATED: Original code did not take into consideration if a word contains another.
UPDATE 2: Performance was horrible in production so i have to think another way. you have it at the end as option 2, implementation for table.
UPDATE 3: Added code for UDF in the implementation in a string.
Implementation in a string:
declare #a as nvarchar(100) = 'Lorem ipsum dolor dol ol sit amet. D Lorem DO ipsum DOL dolor sit amet. DOLORES ipsum';
WITH T AS (
SELECT T1.value
,charindex(' ' + T1.value + ' ',' ' + #a + ' ' ,0) AS INDX
,RN = ROW_NUMBER() OVER (PARTITION BY value order BY value)
FROM STRING_SPLIT(#a, ' ') AS T1
WHERE T1.value <> ''
),
R (VALUE,INDX,RN) AS (
SELECT *
FROM T
WHERE T.RN = 1
UNION ALL
SELECT T.VALUE
,charindex(' ' + T.value + ' ',' ' + #a + ' ',R.INDX + 1) AS INDX
,T.RN
FROM T
JOIN R
ON T.value = R.VALUE
AND T.RN = R.RN + 1
)
SELECT * FROM R ORDER BY INDX
result:
tableOfResults
UDF:
CREATE FUNCTION DBO.UDF_get_word(#string nvarchar(100),#wordNumber int)
returns nvarchar(100)
AS
BEGIN
DECLARE #searchedWord nvarchar(100);
WITH T AS (
SELECT T1.value
,charindex(' ' + T1.value + ' ',' ' + #string + ' ' ,0) AS INDX
,RN = ROW_NUMBER() OVER (PARTITION BY value order BY value)
FROM STRING_SPLIT(#string, ' ') AS T1
WHERE T1.value <> ''
),
R (VALUE,INDX,RN) AS (
SELECT *
FROM T
WHERE T.RN = 1
UNION ALL
SELECT T.VALUE
,charindex(' ' + T.value + ' ',' ' + #string + ' ',R.INDX + 1) AS INDX
,T.RN
FROM T
JOIN R
ON T.value = R.VALUE
AND T.RN = R.RN + 1
)
SELECT #searchedWord = (value) FROM ( SELECT *, ORD = ROW_NUMBER() OVER (ORDER BY INDX) FROM R )AS TBL WHERE ORD = #wordNumber
RETURN #searchedword
END
GO
Modification for a column in a table, OPTION 1:
WITH T AS (
SELECT T1.stringToBeSplit
,T1.column1 --column1 is an example of column where stringToBeSplit is the same for more than one record. better to be avoid but if you need to added here it is how just follow column1 over the code
,T1.column2
,T1.value
,T1.column3
/*,...any other column*/
,charindex(' ' + T1.value + ' ',' ' + T1.stringToBeSplit + ' ' ,0) AS INDX
,RN = ROW_NUMBER() OVER (PARTITION BY t1.column1, T1.stringToBeSplit, T1.value order BY T1.column1, T1.T1.stringToBeSplit, T1.value) --any column that create duplicates need to be added here as example i added column1
FROM (SELECT TOP 10 * FROM YourTable D CROSS APPLY string_split(D.stringToBeSplit,' ')) AS T1
WHERE T1.value <> ''
),
R (stringToBeSplit, column1, column2, value, column3, INDX, RN) AS (
SELECT stringToBeSplit, column1, column2, value, column3, INDX, RN
FROM T
WHERE T.RN = 1
UNION ALL
SELECT T.stringToBeSplit, T.column1, column2, T.value, T.column3
,charindex(' ' + T.value + ' ',' ' + T.stringToBeSplit + ' ',R.INDX + 1) AS INDX
,T.RN
FROM T
JOIN R
ON T.value = R.VALUE AND T.COLUMN1 = R.COLUMN1 --any column that create duplicates need to be added here as exapmle i added column1
AND T.RN = R.RN + 1
)
SELECT * FROM R ORDER BY column1, stringToBeSplit, INDX
Modification for a column in a table, OPTION 2 (max performance i could get, main action came from removing the join and finding a way of properly execute (and stop) the recursive loop of the CTE, from 1.30 for 1000 lines to 2 sec for 30K lines of strings of similar type and length):
WITH T AS (
SELECT T1.stringToBeSplit --no extracolumns this time
,T1.value
,charindex(' ' + T1.value + ' ',' ' + T1.stringToBeSplit + ' ' ,0) AS INDX
,RN = ROW_NUMBER() OVER (PARTITION BY T1.stringToBeSplit,T1.value order BY T1.stringToBeSplit,T1.value) --from clause use distinct and where if possible
FROM (SELECT DISTINCT stringToBeSplit, VALUE FROM [your table] D CROSS APPLY string_split(D.stringToBeSplit,' ') WHERE [your filter]) AS T1
WHERE T1.value <> ''
),
R (stringToBeSplit, value, INDX, RN) AS (
SELECT stringToBeSplit, value, INDX, RN
FROM T
WHERE T.RN = 1
UNION ALL
SELECT R.stringToBeSplit, R.value
,charindex(' ' + R.value + ' ',' ' + R.stringToBeSplit + ' ',R.INDX + 1) AS INDX
,R.RN + 1
FROM R
WHERE charindex(' ' + R.value + ' ',' ' + R.stringToBeSplit + ' ',R.INDX + 1) <> 0
)
SELECT * FROM R ORDER BY stringToBeSplit, INDX
For getting the word ordinal instead of SELECT * FROM R USE:
SELECT stringToBeSplit ,value , ROW_NUMBER() OVER (PARTITION BY stringToBeSplit order BY [indX]) AS ORD FROM R
if instead of having one RW per word you prefer one column:
select * FROM (SELECT [name 1],value , ROW_NUMBER() OVER (PARTITION BY [name 1] order BY [indX]) AS ORD FROM R ) as R2
pivot (MAX(VALUE) FOR ORD in ([1],[2],[3]) ) AS PIV
if you don't want to specify the number of columns QUOTNAME() like in this link, in my case i only need first 4 words rest are irrelevant for the moment. Below the code from the page in case link fail:
DECLARE
#columns NVARCHAR(MAX) = '',
#sql NVARCHAR(MAX) = '';
-- select the category names
SELECT
#columns+=QUOTENAME(category_name) + ','
FROM
production.categories
ORDER BY
category_name;
-- remove the last comma
SET #columns = LEFT(#columns, LEN(#columns) - 1);
-- construct dynamic SQL
SET #sql ='
SELECT * FROM
(
SELECT
category_name,
model_year,
product_id
FROM
production.products p
INNER JOIN production.categories c
ON c.category_id = p.category_id
) t
PIVOT(
COUNT(product_id)
FOR category_name IN ('+ #columns +')
) AS pivot_table;';
-- execute the dynamic SQL
EXECUTE sp_executesql #sql;
Last but not least i'm really looking forward to know if there is an easier way with same performance either in SQL server or in C#. i just think everything that does not use external info should stay in the Server and run as query or batch but not sure to be honest as i heard the contrary (specially from people that use panda) but no one have convince me just yet.
This works
Example:
String = "pos1-pos2-pos3"
REVERSE(PARSENAME(REPLACE(REVERSE(String), '-', '.'), 1))
With 1 Returns "pos1"
With 2 will return "pos2"...

SQL Select all words in a string from the third word

My data set is changing and now includes two additional words at the start of my customer name field, I need to clean this data up before moving it to my main customer table.
What I need to be able to do is keep only the words after the second space in a select statement.
Can anyone suggest a way to do this
i.e. "ZENDUSER ABCABC S ROCCO AL PORTO" needs to be returned as "S ROCCO AL PORTO"
You can use CHARINDEX and SUBSTRING to do this:
declare #a varchar(200)
set #a = 'ZENDUSER ABCABC S ROCCO AL PORTO'
select #a, substring(#a, charindex(' ', #a, charindex(' ', #a, 1) + 1) + 1, 200)
DECLARE #cust NVARCHAR(MAX);
SET #cust = N'ZENDUSER ABCABC S ROCCO AL PORTO';
SELECT SUBSTRING(#cust, CHARINDEX(' ', #cust, CHARINDEX(' ', #cust, 0) + 1) + 1,
LEN(#cust) - CHARINDEX(' ', #cust, CHARINDEX(' ', #cust, 0) + 1));
GO
| (No column name) |
| :--------------- |
| S ROCCO AL PORTO |
dbfiddle here
Try this:
select substring(MyColumn, CHARINDEX(MyColumn, ' ', CHARINDEX(MyColumn, ' ', 1) + 1) + 1, Len(MyColumn)) from MyTable
I know, that this is very similair to MJH answer, but additionally, I take Len(MyColumn) in substring method, so we are sure that we include all characters after second space. The other answer takes only 200 characters.
If you'd like your trimming to be more dynamic i.e starting from the 5th word etc, you can use the following code snippet. I would have encapsulate this in an inline function for additional capabilities
DECLARE #Sentence NVARCHAR(200) = 'ZENDUSER ABCABC S ROCCO AL PORTO'
DECLARE #Del NVARCHAR(2)= ' '
DECLARE #WordStart INT = 5
;WITH Nums (n) as
(
SELECT TOP (LEN(#Sentence)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d(n)
)
, Main as
(
SELECT N, x.val , Ind , CASE WHEN n = 1 THEN 1 ELSE SUM(Ind) OVER (ORDER BY n) + 1 END Pos
FROM Nums
CROSS APPLY
(
VALUES (SUBSTRING(#Sentence,n,1),
CASE WHEN SUBSTRING(#Sentence,n,1) = #Del THEN 1 ELSE 0 END)
) x(val, Ind)
)
, Combine (StrOut) as
(
SELECT LTRIM(RTRIM(STUFF(
CAST((SELECT ''+ val
FROM Main
WHERE Pos >= #WordStart
FOR XML PATH (''),TYPE) AS NVARCHAR(MAX)),1,0,'')
)))
SELECT StrOut
FROM Combine
UPDATE: creating a function
CREATE FUNCTION dbo.SentenceSplitter
(
#Sentence NVARCHAR(2000),
#WordStart INT,
#Del NVARCHAR(2) = ' '
)
RETURNS TABLE AS RETURN
WITH Nums (n) as
(
SELECT TOP (LEN(#Sentence)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d(n)
)
, Main as
(
SELECT N, x.val , Ind , CASE WHEN n = 1 THEN 1 ELSE SUM(Ind) OVER (ORDER BY n) + 1 END Pos
FROM Nums
CROSS APPLY
(
VALUES (SUBSTRING(#Sentence,n,1),
CASE WHEN SUBSTRING(#Sentence,n,1) = #Del THEN 1 ELSE 0 END)
) x(val, Ind)
)
, Combine (StrOut) as
(
SELECT LTRIM(RTRIM(STUFF(
CAST((SELECT ''+ val
FROM Main
WHERE Pos >= #WordStart
FOR XML PATH (''),TYPE) AS NVARCHAR(MAX)),1,0,'')
)))
SELECT StrOut
FROM Combine
Use case:
SELECT StrOut
FROM dbo.SentenceSplitter ('ZENDUSER ABCABC S ROCCO AL PORTO', 5, ' ')
will result:
StrOut
AL PORTO

Random Phone number generator

I'm trying to update a table, specifically the phone numbers.
I have made two procedures (also converted both to functions):
CreateRandomMobileNumber
ALTER procedure [dbo].[GetRandomMobileNumber](
#min AS BIGINT,
#max AS BIGINT,
#mobile_num AS VARCHAR(20)
) AS BEGIN
SELECT #min = 1, #max = 99999999
SELECT #mobile_num = CAST(CAST(((#max + 1) - #min) * Rand() + #min as bigint) as varchar(15))
SELECT #mobile_num = '04' + ' ' + SUBSTRING(#mobile_num, 1, 4) + ' ' + RIGHT(#mobile_num,4)
SELECT #mobile_num
END
CreateRandomPhoneNumber
ALTER procedure [dbo].[GetRandomPhoneNumber](
#min AS BIGINT,
#max AS BIGINT,
#phone_num AS VARCHAR(20)
)
AS
BEGIN
SELECT #min = 1, #max = 99999999
Select #phone_num = CAST(CAST(((#max + 1) - #min) * Rand() + #min as bigint) as varchar(15))
SELECT #phone_num =
CASE
WHEN LEFT(#phone_num, 1) BETWEEN 1 AND 6
THEN '02' + ' ' + SUBSTRING(#phone_num, 1, 4) + ' ' + RIGHT(#phone_num,4)
WHEN LEFT(#phone_num, 1) BETWEEN 7 AND 8
THEN '03' + ' ' + SUBSTRING(#phone_num, 1, 4) + ' ' + RIGHT(#phone_num,4)
ELSE '07' + ' ' + SUBSTRING(#phone_num, 1, 4) + ' ' + RIGHT(#phone_num,4)
END
SELECT #phone_num
END
What I want to do is be able to call these on an update, this is the insert statement:
; WITH CTE AS
(
SELECT *, rn = ROW_NUMBER() OVER ( ORDER BY newid() )
FROM anon_PersonChangeData
)
UPDATE c1
SET c1.personNewGender = c2.personCurrentGender
, c1.personNewFirstName = c2.personCurrentFirstName
, c1.personNewSurname = c3.personCurrentSurname
, c1.personNewHomeEmail = CASE
WHEN c1.personStatus IN ('both', 'candidate') AND c1.personCurrentFirstName LIKE '%p%'
THEN c2.personCurrentFirstName + '.' + c3.personCurrentSurname + '#yoohoo.com'
WHEN c1.personStatus IN ('both', 'candidate') AND c1.personCurrentFirstName LIKE '%e%'
THEN c2.personCurrentFirstName + '.' + c3.personCurrentSurname + '#hatmail.com'
ELSE c2.personCurrentFirstName + '.' + c3.personCurrentSurname + '#gmoil.com'
END
, c1.personNewWorkEmail = NULL
, c1.personNewHomePhone = dbo.GetRandomPhoneNumber
, c1.personNewWorkPhone = NULL
, c1.personNewMobilePhone = dbo.GetRandomMobileNumber
FROM CTE c1
INNER JOIN CTE c2 ON c1.rn = c2.rn
INNER JOIN CTE c3 ON c2.rn = c3.rn
I have tried a few different things and my function versions keep giving me the following error:
Invalid use of a side-effecting operator 'rand' within a function.
Any help would be much appreciated.
Here's an easy way, using an "inline tally table" to set the how many numbers you want to generate and ABS(CHECKSUM(NEWID())) % N to generate random numbers. In this case, I'm setting the bound between 1000000000 and 9999999999 (because we want 10 digits...
WITH
cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)),
cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b), -- 100 rows
cte_n3 (n) AS (SELECT 1 FROM cte_n2 a CROSS JOIN cte_n2 b), -- 10,000 rows
cte_Tally (n) AS (
SELECT TOP 20 -- set the number of row you wanr here...
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
cte_n3 a CROSS JOIN cte_n3 b -- 100,000,000 rows
)
SELECT
PhoneFormatted = '(' + STUFF(STUFF(pn.PhoneNumber, 7, 0, '-'), 4, 0, ') ')
FROM
cte_Tally
CROSS APPLY ( VALUES (CAST(ABS(CHECKSUM(NEWID())) % 9999999999 + 1000000001 AS VARCHAR(14))) ) pn (PhoneNumber);
And the results...
PhoneFormatted
-------------------
(165) 103-7444
(288) 558-8646
(259) 635-8073
(277) 171-6645
(104) 265-1071
(119) 838-5696
(111) 378-0002
(202) 490-0627
(208) 400-6605
(101) 681-2601
(239) 842-7079
(257) 067-0305
(227) 761-1426
(171) 330-1550
(312) 728-6722
(108) 526-2654
(202) 573-9529
(205) 334-9711
(112) 345-8447
(202) 587-9164
The best part... It's fast... Here is the STATISTICS IO,TIME from putting 1M numbers into a temp table.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 7 ms.
SQL Server Execution Times:
CPU time = 1392 ms, elapsed time = 1224 ms.
(1000000 rows affected)
If you want a one liner, something like this might work:
SELECT TOP 500 PhoneNumber = FORMAT(ROUND(((9999999999 - 1111111111)*RAND(CAST(DATEDIFF(s,'1970-01-01 12:00:00',GETDATE()) As BIGINT)+(ROW_NUMBER() OVER(ORDER BY (SELECT(NULL)))) ) + 1111111111),0),'(###) ###-####')
FROM anytable;

How can I use T-SQL to find all positions that are not in a list of characters?

I need to find all the positions in a string that are not A, G, C, or T (this is genomic data). I have figured out a way to do this using a loop (see below) but honestly I am not sure if there is a 'smarter' way of doing this.
I am using SQL Server.
DECLARE #myTest varchar(max) = 'GGCGATXAATXCCC-GCCT'
DECLARE #pos int =1
DECLARE #table1 TABLE (position int, DiffValue varchar(1))
WHILE (#pos <= LEN(#myTest))
BEGIN
INSERT INTO #table1
SELECT
#pos,
CASE
WHEN SUBSTRING(#myTest, #pos, 1) NOT IN ('A','G','C','T')
THEN SUBSTRING(#myTest, #pos, 1)
END
WHERE
SUBSTRING(#myTest, #pos, 1) NOT IN ('A','G','C','T')
SELECT #pos= #pos + 1
END
SELECT * FROM #table1
Results in
position DiffValue
7 X
11 X
15 -
Just grab a copy of NGrams8K and your all set.
DECLARE #myTest varchar(max) = 'GGCGATXAATXCCC-GCCT'
SELECT Position, Token
FROM dbo.ngrams8K(#myTest, 1)
WHERE token NOT LIKE '[AGCT]';
Results:
Position Token
-------------------- --------
7 X
11 X
15 -
Perhaps with an ad-hoc tally table
DECLARE #myTest varchar(max) = 'GGCGATXAATXCCC-GCCT'
Select N
,S = substring(#myTest,N,1)
From (Select Top (Len(#myTest)) N=Row_Number() Over (Order By (Select NULL)) From master..spt_values n1,master..spt_values n2 ) A
Where substring(#myTest,N,1) not in ('A','G','C','T')
Returns
N S
7 X
11 X
15 -
Are you looking something like this as below:
DECLARE #myTest varchar(max) = 'GGCGATXAATXCCC-GCCTADASFDASDFXASDASFASDFXASDFASDXASDAS'
;with nums as (
select top(len(#myTest)) RowN = Row_number() over(order by (Select NULL))
from master..spt_values s1, master..spt_values s2
)
select RowN as Position, DiffValue = SUBSTRING(#myTest,RowN,1) from nums
where SUBSTRING(#myTest,RowN,1) not in ('A','G','C','T')

Find missing integers in a list of Values

Currently, I have 12 rows with column Named 'Value'. The sample like this (just sample data, real data will be more):
Value
1
2
3
4
6
7
8
9
10
11
12
14
What I want is select them to get result like this:
Result Result_Miss
1-4, 6-12, 14 5, 13
I want to avoid using a cursor to work row-by-row.
Dynamic, set-based approach using CTEs to hunt down the missing values, and write out the ranges available based on those missing values.
--(I can't seem to get SqlFiddle to work with CTE's or I'd post one up here)--
Reworked to be more dynamic for number of records:
This works provided you always have '1' in your set of value
CREATE TABLE #OneTen
(
Value INT NOT NULL
);
INSERT INTO #OneTen
VALUES (1), (2), (3), (4), (6), (8), (9), (10), (11), (12), (14);
WITH ExpectedActual AS
(
SELECT ot.Value AS Actual, ROW_NUMBER() OVER (ORDER BY Value) AS Expected
FROM #OneTen AS ot
)
, DegreesOff AS
(
SELECT ea.Expected, ea.Actual, (ea.Actual - ea.Expected) AS Change
FROM ExpectedActual AS ea
)
, Missing AS
(
SELECT CASE
WHEN MIN(do.Expected) = 1 THEN 0
ELSE MIN(do.Expected) + do.Change - 1
END AS Missing
, ROW_NUMBER() OVER (ORDER BY MIN(do.Expected)) AS RowNumber
FROM DegreesOff AS do
GROUP BY do.Change
UNION ALL
SELECT MAX(do.Actual + 1), MAX(do.Change + 2) --Adding Last Value 1 higher than Actual so the code below that takes mNext.Missing - 1 brings it down to the proper value:
--Change + 2 to account for 0 plus being 1 higher
FROM DegreesOff AS do
)
SELECT STUFF((
SELECT ', ' + CASE
WHEN m.Missing + 1 = mNext.Missing - 1 THEN CAST(m.Missing + 1 AS NVARCHAR(4))
ELSE CAST(m.Missing + 1 AS NVARCHAR(4)) + '-' + CAST(mNext.Missing - 1 AS NVARCHAR(4))
END
FROM Missing AS m
LEFT JOIN Missing AS mNext ON m.RowNumber = mNext.RowNumber - 1
FOR XML PATH('')), 1, 2, '') AS Result
, STUFF((
SELECT ', ' + CAST(MIN(do.Expected + do.Change - 1) AS NVARCHAR(4))
FROM DegreesOff AS do
WHERE do.Change > 0
GROUP BY do.Change
FOR XML PATH('')), 1, 2, '') AS Result_Miss
Try the following script:
DDL
CREATE TABLE Numbers
(
Value INT NOT NULL
);
INSERT INTO Numbers
VALUES (1), (2), (3), (4), (6), (7), (8), (9), (10), (12),(13);
Script
DECLARE #MinValue INT
DECLARE #MaxValue INT
DECLARE #Temp TABLE(MissingValues INT)
DECLARE #MissingValues VARCHAR(50)
SELECT #MinValue = MIN(Value),
#MaxValue = MAX(Value)
FROM Numbers
;WITH CTE AS
(
SELECT #MinValue Value
UNION ALL
SELECT Value + 1
FROM CTE
WHERE Value + 1 <= #MaxValue
)
INSERT INTO #Temp
SELECT CTE.Value
FROM CTE
LEFT JOIN Numbers N
ON CTE.Value = N.Value
WHERE N.Value IS NULL
OPTION (MAXRECURSION 1000)
SELECT #MissingValues =
STUFF(( SELECT ',' + CAST(MissingValues AS VARCHAR)
FROM #Temp
FOR XML PATH('')),1,1,'')
INSERT INTO #Temp
SELECT #MinValue - 1
UNION ALL
SELECT #MaxValue + 1
;WITH CTE AS
(
SELECT MissingValues,
ROW_NUMBER() OVER(ORDER BY MissingValues ASC) RN
FROM #Temp
)
,Ranges AS
(
SELECT CAST(T1.MissingValues + 1 AS VARCHAR) + '-' +
CAST(T2.MissingValues - 1 AS VARCHAR) Ranges
FROM CTE AS T1
INNER JOIN CTE AS T2
ON T1.RN = T2.RN - 1
)
SELECT STUFF(( SELECT ',' + R.Ranges
FROM Ranges R
FOR XML PATH('')),1,1,'') Result,
#MissingValues AS Result_Miss

Resources