SQL: Validate a quantity given a comma delimited set of ranges - sql

I need to 'validate' a quantity. Given any number x, return true or false if the number is contained in a comma delimited set of ranges and numbers. Example: valid numbers "1,5-10,25-50,100,500", some valid numbers would be 1,5,6,7,8,9,10, but not 11,12,51, etc.

If you convert the string into a temporary table of min and max values, you can easily select the valid values.
Example in T-SQL (MS SQL Server):
declare #valid varchar(50) = '1,5-10,25-50,100,500'
declare #i int, #range varchar(10)
declare #t table(min int, max int)
while len(#valid) > 0 begin
set #i = charindex(',', #valid)
if #i = 0 begin
set #range = #valid
set #valid = ''
end else begin
set #range = left(#valid, #i - 1)
set #valid = right(#valid, len(#valid) - #i)
end
set #i = charindex('-', #range)
if #i = 0 begin
insert into #t (min, max) values (cast(#range as int), cast(#range as int))
end else begin
insert into #t (min, max) values (cast(left(#range, #i - 1) as int), cast(right(#range, len(#range) - #i) as int))
end
end
select
n
from
(values(1),(5),(6),(7),(8),(9),(10),(11),(12),(51)) as x (n)
inner join #t on n between min and max

Check this (SQL Server 2008)
CREATE FUNCTION [dbo].[RunningNumbers](#anzahl INT=1000000, #StartAt INT=0)
RETURNS TABLE
AS
RETURN
WITH E1(N) AS(SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)), --10 ^ 1
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 10 ^ 8 = 10,000,000 rows
CteTally AS
(
SELECT TOP(ISNULL(#anzahl,1000000)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) -1 + ISNULL(#StartAt,0) As Nmbr
FROM E8
)
SELECT * FROM CteTally;
GO
CREATE FUNCTION dbo.CheckRange
(
#ValidTarget INT
,#rangeList VARCHAR(MAX)
)
RETURNS BIT
AS
BEGIN
DECLARE #rangeParts XML=CAST('<root><r>'+REPLACE(#rangeList,',','</r><r>') + '</r></root>' AS XML);
DECLARE #Count INT;
SELECT #Count= COUNT(*)
FROM
(
SELECT Valid.X
FROM #rangeParts.nodes('/root/r') AS rp(p)
CROSS APPLY
(
SELECT CASE WHEN CHARINDEX('-',p.value('.','varchar(max)'))>0
THEN CAST('<root><r>'+REPLACE(p.value('.','varchar(max)'),'-','</r><r>') + '</r></root>' AS XML)
ELSE '<root><r>' + p.value('.','varchar(max)') + '</r><r>' + p.value('.','varchar(max)') + '</r></root>' END AS pr
) AS partsResolved
CROSS APPLY
(
SELECT CASE WHEN #ValidTarget BETWEEN partsResolved.pr.value('(/root/r)[1]','int') AND partsResolved.pr.value('(/root/r)[2]','int') THEN 1 ELSE 0 END AS X
) AS Valid
) AS tbl
WHERE tbl.X=1
RETURN #Count;
END
GO
SELECT Nmbr,dbo.CheckRange(Nmbr,'1,5-10,25-50,100,500')
FROM dbo.RunningNumbers(70,-3)
DROP FUNCTION dbo.RunningNumbers;
GO
DROP FUNCTION dbo.CheckRange;

Related

How to get only Capital letters from given value

I have a table it contains ID, Description and code columns. I need to fill code column using description column. Sample Description is "Investigations and Remedial Measures" so my code should be "IRM".
Note: Is there any words like "and/for/to/in" avoid it
This code may help you..
declare #input as varchar(1000) -- Choose the appropriate size
declare #output as varchar(1000) -- Choose the appropriate size
select #input = 'Investigations and Remedial Measures', #output = ''
declare #i int
select #i = 0
while #i < len(#input)
begin
select #i = #i + 1
select #output = #output + case when unicode(substring(#input, #i, 1))between 65
and 90 then substring(#input, #i, 1) else '' end
end
SELECT #output
Personally I would do this with an inline table-valued function
On SQL Server 2017 or better, or Azure SQL Database:
CREATE OR ALTER FUNCTION dbo.ExtractUpperCase(#s nvarchar(4000))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH s(s) AS (SELECT 1 UNION ALL SELECT s+1 FROM s WHERE s < LEN(#s))
SELECT TOP (3) value = STRING_AGG(SUBSTRING(#s,s,1),'')
WITHIN GROUP (ORDER BY s.s)
FROM s WHERE ASCII(SUBSTRING(#s,s,1)) BETWEEN 65 AND 90
);
GO
On SQL Server 2016 or older:
CREATE FUNCTION dbo.ExtractUpperCase(#s nvarchar(4000))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH s(s) AS (SELECT 1 UNION ALL SELECT s+1 FROM s WHERE s < LEN(#s))
SELECT value = (SELECT TOP (3) v = SUBSTRING(#s,s,1) FROM s
WHERE ASCII(SUBSTRING(#s,s,1)) BETWEEN 65 AND 90
ORDER BY s.s FOR XML PATH(''),
TYPE).value(N'./text()[1]',N'nvarchar(4000)')
);
GO
In either case:
CREATE TABLE #x(id int, name nvarchar(4000));
INSERT #x(id, name) VALUES
(1, N'Belo Horizonte Orange'),
(2, N'São Paulo Lala'),
(3, N'Ferraz de Vasconcelos Toranto');
SELECT id, f.value FROM #x AS x
CROSS APPLY dbo.ExtractUpperCase(x.name) AS f
ORDER BY id OPTION (MAXRECURSION 4000);
Results:
id name
---- ----
1 BHO
2 SPL
3 SVT
The OPTION (MAXRECURSION 4000) is only necessary if your strings can be longer than 100 characters.

What is the easiest way to print all prime numbers until n, in SQL?

What kind of algorithm should I use, for printing all prime numbers until let's say 1000?
SQL Server is preferred.
Thanks
You could use this to list all primes that smaller than 1000 in SQL
;WITH temp AS
(
SELECT 2 AS Value
UNION ALL
SELECT t.Value+1 AS VAlue
FROM temp t
WHERE t.Value < 1000
)
SELECT *
FROM temp t
WHERE NOT EXISTS
( SELECT 1 FROM temp t2
WHERE t.Value % t2.Value = 0
AND t.Value != t2. Value
)
OPTION (MAXRECURSION 0)
Demo link: Rextester
This code I've written for SQL Server is very fast, it will find all the primes less than 10 million in about 12 seconds:
DECLARE #Min int = 2, #Max int = 100000
--
IF OBJECT_ID('tempdb..#N','U') IS NOT NULL DROP TABLE #N
--
CREATE TABLE #N(N int NOT NULL, SqrtN int NOT NULL);
--
WITH L0 AS (SELECT 'Anything' N FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) AS T(N)), -- 16 values
L1 AS (SELECT A.N FROM L0 A, L0 B, L0 C, L0 D, L0 E, L0 F, L0 G, L0 H), -- 15^8 values (2562890625 more than enough for max value of int (2^31-1)
L2 AS (SELECT TOP(#Max/6) CONVERT(int,6*ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) RowNum FROM L1)
INSERT INTO #N(N, SqrtN)
SELECT T.N, SQRT(N)
FROM L2
CROSS APPLY(VALUES(L2.RowNum-1),(L2.RowNum+1)) T(N)
WHERE T.N BETWEEN #Min AND #Max
AND 0 NOT IN (N%5,N%7,N%11,N%13,N%17,N%19,N%23,N%29,N%31,N%37,N%41,N%43,N%47,N%53,N%59) -- Not interested in anything dividable by these low primes
--
ALTER TABLE #N ADD PRIMARY KEY CLUSTERED(N) WITH FILLFACTOR = 100
--
IF OBJECT_ID('tempdb..#Primes','U') IS NOT NULL DROP TABLE #Primes
--
SELECT Z.N Prime
FROM (SELECT N FROM (VALUES(2),(3),(5),(7),(11),(13),(17),(19),(23),(29),(31),(37),(41),(43),(47),(53),(59)) T(N)
WHERE T.N BETWEEN #Min AND #Max
UNION ALL
SELECT X.N
FROM #N AS X
WHERE NOT EXISTS(SELECT *
FROM #N AS C
WHERE C.N <= X.SqrtN
AND 0 = X.N%C.N)) Z
ORDER BY 1
The answer is simple:
Assuming you already have following table populated with data:
CREATE TABLE dbo.PrimeNumber(Num INT NOT NULL PRIMARY KEY);
you'll need only a simple SELECT:
SELECT * FROM dbo.PrimeNumber
This is my answer
I did it in SQL Server
DECLARE #number INT = 4
DECLARE #isprime INT = 0
DECLARE #counter INT
DECLARE #result VARCHAR(MAX) = '2&3&'
WHILE (#number <= 1000)
BEGIN
SET #counter = 2
WHILE (#counter <= CAST(SQRT(#number) as INT))
BEGIN
IF (#number % #counter = 0)
BEGIN
SET #isprime = 0
Break
END
ELSE
BEGIN
SET #isprime = 1
SET #counter += 1
END
END
IF #isprime = 1
SET #result += CAST(#number as VARCHAR(6)) + '&'
SET #number += 1
END
PRINT(LEFT(#result, len(#result)-1))
This is the output
2&3&5&7&11&13&17&19&23&29&31&37&41&43&47&53&59&61&67&71&73&79&83&89&97&101&103&107&109&113&127&131&137&139&149&151&157&163&167&173&179&181&191&193&197&199&211&223&227&229&233&239&241&251&257&263&269&271&277&281&283&293&307&311&313&317&331&337&347&349&353&359&367&373&379&383&389&397&401&409&419&421&431&433&439&443&449&457&461&463&467&479&487&491&499&503&509&521&523&541&547&557&563&569&571&577&587&593&599&601&607&613&617&619&631&641&643&647&653&659&661&673&677&683&691&701&709&719&727&733&739&743&751&757&761&769&773&787&797&809&811&821&823&827&829&839&853&857&859&863&877&881&883&887&907&911&919&929&937&941&947&953&967&971&977&983&991&997
with prime
as
(
select 1 as 'start'
union all
select start+1 'start'
from prime where start<100
)
select e as prime_value from
(select a.start%b.start as w, a.start as e from prime A , Prime B
where --a.start% b.start<>0 and
b.start<a.start
--and a.start between 1 and 100
)A
where w=0
group by A.e
having count(w) <=1
BY Nagaraj M-BE
DECLARE #range int = 1000, #x INT = 2, #y INT = 2
While (#y <= #range)
BEGIN
while (#x <= #y)
begin
IF ((#y%#x) =0)
BEGIN
IF (#x = #y)
PRINT #y
break
END
IF ((#y%#x)<>0)
set #x = #x+1
end
set #x = 2
set #y = #y+1
end

How to select only armstrong numbers from the list?

I want is to select Armstrong numbers from the list below list I have searched of solution of this question bu unable to find in SQL-Server:
Numbers
121
113
423
153
541
371
I am sure most of you know what's the Armstrong number and how to calculate though I am describing is for the simplicity : sum of the cubes of its digits is equal to the number itself i.e.
1*1*1 + 5*5*5 + 3*3*3 = 153
3*3*3 + 7*7*7 + 1*1*1 = 371
Please help me on this as I am also trying but seeking for quick solution. It will be very helpful to me. Thanks in advance.
Obviously static processing during each query is not correct approach but we can create function like this and
create function dbo.IsArmstrongNumber(#n int)
returns int as
begin
declare #retValue int = 0
declare #sum int = 0
declare #num int = #n
while #num > 0
begin
set #sum += (#num%10) * (#num%10) * (#num%10)
set #num = #num/10
end
IF #sum = #n
set #retValue = 1
return #retValue
end
Pre-processing and selecting in IN clause is better
select * from #Numbers where dbo.IsArmstrongNumber(n) = 1
select 153 x into #temp;
insert #temp values(371);
insert #temp values(541);
with cte as (select x, substring(cast(x as nvarchar(40)) ,1,1) as u, 1 as N FROM #temp
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),3)) = x
drop table #temp;
here is the mark 2 - you can change the #ORDER to explore power of 4,5 etc
declare #order int = 3;
declare #limit int = 50000;
with nos as (select 1 no
union all
select no + 1 from nos where no < #limit),
cte as (select no as x, substring(cast(no as nvarchar(40)) ,1,1) as u, 1 as N FROM nos
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),#order)) = x
option (maxrecursion 0);
This is a quick mod to my sum of digits UDF
Declare #Table table (Numbers int)
Insert into #Table values
(121),
(113),
(423),
(153),
(541),
(371)
Select * from #Table where [dbo].[udf-Stat-Is-Armstrong](Numbers)=1
Returns
Numbers
153
371
The UDF
CREATE Function [dbo].[udf-Stat-Is-Armstrong](#Val bigint)
Returns Bit
As
Begin
Declare #RetVal as bigint
Declare #LenInp as bigint = len(cast(#Val as varchar(25)))
;with i AS (
Select #Val / 10 n, #Val % 10 d
Union ALL
Select n / 10, n % 10
From i
Where n > 0
)
Select #RetVal = IIF(SUM(power(d,#LenInp))=#Val,1,0) FROM i;
Return #RetVal
End
You can use the following to find Armstrong numbers using Sql functions:
WITH Numbers AS(
SELECT 0 AS number UNION ALL SELECT number + 1 FROM Numbers WHERE number < 10000)
SELECT number AS ArmstrongNumber FROM Numbers
WHERE
number = POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),1,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),2,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),3,1),0),3)
OPTION(MAXRECURSION 0)

Complicated SQL while loop

I am trying to create a while loop in SQL and it seems kind of complex. Here's what I need it to achieve:
Iterate through a single VARCHAR string (ex. '123')
If the nth character is in an even position in the string (ex. 2nd, 4th .... letter in the string), it must be added(SUM) to a base variable (Let's assume #z)
If the nth character is in an odd position in the string (ex. 1st, 3rd .... letter in the string), it must be multiplied by 2. If this newly generated value (Let's assume #y) is less than 10, it must be added(SUM) to the base variable (Still the same assumed #z). If #y is greater than 10, we need to subtract 9 from #y before adding(SUM) it to #z
After iterating through the entire string, this should return a numeric value generated by the above process.
Here is what I've done so far, but I'm stuck now (Needless to say, this code does not work yet, but I think I'm heading in the right direction):
DECLARE #x varchar(20) = '12345'
DECLARE #p int = len(#x)
WHILE #p > 0
SELECT #x =
stuff(#x, #p, 1,
case when CONVERT(INT,substring(#x, #p, 1)) % 2 = 0 then CONVERT(INT, #x) + CONVERT(INT,substring(#x, #p, 1))
end), #p -= 1
RETURN #x;
PS. The input will always be 100% numeric values, but it is formatted as VARCHAR when I recieve it.
UPDATE
The expected result for the sample string is 15
You can do this without using a loop. Here is a solution using Tally Table:
DECLARE #x VARCHAR(20) = '12345'
DECLARE #z INT = 0 -- base value
;WITH E1(N) AS( -- 10 ^ 1 = 10 rows
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
CteTally(N) AS(
SELECT TOP(LEN(#x)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E4
),
CteChars(N, num) AS(
SELECT
t.N, CAST(SUBSTRING(#x, t.N, 1) AS INT)
FROM CteTally t
WHERE t.N <= LEN(#x)
)
SELECT
SUM(
CASE
WHEN N % 2 = 0 THEN num
WHEN num * 2 < 10 THEN num * 2
ELSE (num * 2) - 9
END
) + #z
FROM CteChars
The CTEs up to CteTally generates a list of number from 1 to LEN(#x). CteChars breaks #x character by character into separate rows. Then the final SELECT does a SUM based on the conditions.
OUTPUT : 15
Check below if it helps you
DECLARE #x varchar(20) = '12345'
DECLARE #p int = 1
DECLARE #result bigint=0;
DECLARE #tempval int =0;
WHILE #p <= len(#x)
BEGIN
SET #tempval = CONVERT(INT,substring(#x, #p, 1));
if(#p%2 = 1)
BEGIN
SET #tempval = #tempval * 2;
IF(#tempval >= 10) SET #tempval = #tempval - 9;
END
SET #result = #result + #tempval;
SET #p = #p + 1;
END;
PRINT #result;--This is the result
RETURN #x;
DECLARE #x INT = 12345
DECLARE #p int = len(#x)
DECLARE #z INT =0
PRINT #p%2
SET #x=#x/10
PRINT #x
WHILE #p > 0
BEGIN
IF(#p%2 = 0)
BEGIN
SET #z=#z+#x%10
SET #p=#p-1
SET #x=#x/10
END
ELSE
BEGIN
SET #z=#z+(2*(#x%10))
SET #p=#p-1
SET #x=#x/10
IF(#x>=10)
BEGIN
SET #x=(#x/10+#x%10)
END
END
END
SELECT #z
The while loop does not seem necessary here.
This can be achieved with a CTE that will split the string and a case statement:
DECLARE #x varchar(20) = '12345';
with split(id, v) as (
select 0, cast(0 as tinyint)
union all
select id+1, cast(SUBSTRING(#x, id+1, 1) as tinyint)
From split
Where id+1 <= len(#x)
)
Select Result = SUM(
Case When id % 2 = 0 then v
When v < 5 then v*2
Else (v*2)-9
End
)
From split
output = 15

Replace with a regular expression

Please see the SQL statement below:
select * from person1
inner join person2 on person1.reference=person2.reference
where replace(person1.surname,' ','')<>replace(person2.surname,' ','')
I want to join on reference and then list all persons with a different surname in person1 and person2. However, I do not want whitespaces and certain other characters to be used in the matching, but I don't want lots of nested Replace statements like this:
replace(replace(replace(person1.surname,' ',''),char(39),''),'-','')<>replace(replace(replace(person2.surname,' ',''),char(39),''), '-','')
I am trying to design an SQL statements that replaces all characters that are not in the following list as a zero length string:
A-Z
a-z
Hyphen
I believe I could get around this using regular expressions.
Like I said, this will be about as fast as tree growth, but have fun...
CREATE FUNCTION dbo.StripBadCharacters
(
#input NVARCHAR(255)
)
RETURNS NVARCHAR(255)
WITH SCHEMABINDING
AS
BEGIN
DECLARE #s NVARCHAR(255), #i INT;
SELECT #s = N'', #i = 0;
WHILE #i <= LEN(#input)
BEGIN
IF SUBSTRING(#input, #i, 1) LIKE N'[A-Za-z-]'
BEGIN
SET #s = #s + SUBSTRING(#input, #i, 1);
END
SET #i = #i + 1;
END
RETURN (#s);
END
GO
Sample usage:
DECLARE #x TABLE(name1 NVARCHAR(255), name2 NVARCHAR(255));
INSERT #x VALUES('bob o''brien', 'bob obrien'); -- this will return
INSERT #x VALUES('bob obrien', 'bob o '' brien'); -- this will return
INSERT #x VALUES('bob o''brien', 'bob o''brian'); -- this will not
SELECT name1, name2 FROM #x
WHERE dbo.StripBadCharacters(name1) = dbo.StripBadCharacters(name2);
An inline table-valued function. It's relatively snappy.
CREATE FUNCTION dbo.StringCompareAlpha(
#str1 nvarchar(255),
#str2 nvarchar(255)
)
RETURNS TABLE AS
RETURN
(
WITH
t0 AS (SELECT 0 i UNION ALL SELECT 0),
t1 AS (SELECT 0 i FROM t0 a, t0 b),
t2 AS (SELECT 0 i FROM t1 a, t1 b),
t3 AS (SELECT 0 i FROM t2 a, t2 b),
n AS (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) i FROM t3),
s1 AS (SELECT ROW_NUMBER() OVER(ORDER BY i) i, SUBSTRING(#str1,i,1) c FROM n WHERE SUBSTRING(#str1,i,1) LIKE '[A-Za-z-]' AND i <= LEN(#str1)),
s2 AS (SELECT ROW_NUMBER() OVER(ORDER BY i) i, SUBSTRING(#str2,i,1) c FROM n WHERE SUBSTRING(#str2,i,1) LIKE '[A-Za-z-]' AND i <= LEN(#str2))
SELECT 1 i WHERE NOT EXISTS(
(SELECT * FROM s1 EXCEPT SELECT * FROM s2)
UNION ALL
(SELECT * FROM s2 EXCEPT SELECT * FROM s1)
)
)
GO
DECLARE #x TABLE(name1 NVARCHAR(255), name2 NVARCHAR(255));
INSERT #x VALUES('bob o''brien', 'bob obrien'); -- this will return
INSERT #x VALUES('bob obrien', 'bob o '' brien'); -- this will return
INSERT #x VALUES('bob o''brien', 'bob o''brian'); -- this will not
SELECT * FROM #x CROSS APPLY dbo.StringCompareAlpha(name1,name2)