I have a table which contains data like this:
MinFormat(int) MaxFormat(int) Precision(nvarchar)
-2 3 1/2
The values in precision can be 1/2, 1/4, 1/8, 1/16, 1/32, 1/64 only.
Now I want result from query as -
-2
-3/2
-1
-1/2
0
1/2
1
3/2
2
5/2
3
Any query to get the result as follows?
Idea is to create result based onMinimum boundary (MinFomrat col value which is integer) to Maximum boundary (MaxFormat Col value which is integer) accordingly to the precision value.
Hence, in above example, value should start from -2 and generate the next values based on the precision value (1/2) till it comes to 3
Note this will only work for Precision 1/1, 1/2, 1/4, 1/8, 1/16, 1/32 and 1/64
DECLARE #t table(MinFormat int, MaxFormat int, Precision varchar(4))
INSERT #t values(-2, 3, '1/2')
DECLARE #numerator INT, #denominator DECIMAL(9,7)
DECLARE #MinFormat INT, #MaxFormat INT
-- put a where clause on this to get the needed row
SELECT #numerator = 1,
#denominator = STUFF(Precision, 1, charindex('/', Precision), ''),
#MinFormat = MinFormat,
#MaxFormat = MaxFormat
FROM #t
;WITH N(N)AS
(SELECT 1 FROM(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))M(N)),
tally(N)AS(SELECT ROW_NUMBER()OVER(ORDER BY N.N)FROM N,N a,N b,N c,N d,N e,N f)
SELECT top(cast((#MaxFormat- #MinFormat) / (#numerator/#denominator) as int) + 1)
CASE WHEN val % 1 = 0 THEN cast(cast(val as int) as varchar(10))
WHEN val*2 % 1 = 0 THEN cast(cast(val*2 as int) as varchar(10)) + '/2'
WHEN val*4 % 1 = 0 THEN cast(cast(val*4 as int) as varchar(10)) + '/4'
WHEN val*8 % 1 = 0 THEN cast(cast(val*8 as int) as varchar(10)) + '/8'
WHEN val*16 % 1 = 0 THEN cast(cast(val*16 as int) as varchar(10)) + '/16'
WHEN val*32 % 1 = 0 THEN cast(cast(val*32 as int) as varchar(10)) + '/32'
WHEN val*64 % 1 = 0 THEN cast(cast(val*64 as int) as varchar(10)) + '/64'
END
FROM tally
CROSS APPLY
(SELECT #MinFormat +(N-1) *(#numerator/#denominator) val) x
Sorry, now I'm late, but this was my approach:
I'd wrap this in a TVF actually and call it like
SELECT * FROM dbo.FractalStepper(-2,1,'1/4');
or join it with your actual table like
SELECT *
FROM SomeTable
CROSS APPLY dbo.MyFractalSteller(MinFormat,MaxFormat,[Precision]) AS Steps
But anyway, this was the code:
DECLARE #tbl TABLE (ID INT, MinFormat INT,MaxFormat INT,Precision NVARCHAR(100));
--Inserting two examples
INSERT INTO #tbl VALUES(1,-2,3,'1/2')
,(2,-4,-1,'1/4');
--Test with example 1, just set it to 2 if you want to try the other example
DECLARE #ID INT=1;
--If you want to get your steps numbered, just de-comment the tree occurencies of "Step"
WITH RecursiveCTE as
(
SELECT CAST(tbl.MinFormat AS FLOAT) AS RunningValue
,CAST(tbl.MaxFormat AS FLOAT) AS MaxF
,1/CAST(SUBSTRING(LTRIM(RTRIM(tbl.Precision)),3,10) AS FLOAT) AS Prec
--,1 AS Step
FROM #tbl AS tbl
WHERE tbl.ID=#ID
UNION ALL
SELECT RunningValue + Prec
,MaxF
,Prec
--,Step + 1
FROM RecursiveCTE
WHERE RunningValue + Prec <= MaxF
)
SELECT RunningValue --,Step
,CASE WHEN CAST(RunningValue AS INT)<>RunningValue
THEN CAST(RunningValue / Prec AS VARCHAR(10)) + '/' + CAST(CAST(1/Prec AS INT) AS VARCHAR(MAX))
ELSE CAST(RunningValue AS VARCHAR(10))
END AS RunningValueFractal
FROM RecursiveCTE;
The result
Value ValueFractal
-2 -2
-1,5 -3/2
-1 -1
-0,5 -1/2
0 0
0,5 1/2
1 1
1,5 3/2
2 2
2,5 5/2
3 3
Mine is a bit different others. I perform the addition on fraction and at the end, simplified the fraction.
-- This solution uses CTE
-- it breaks the #min, #max number into fraction
-- perform the addition in terms of fraction
-- at result, it attemp to convert the fraction to simpliest form
declare #min int,
#max int,
#step varchar(10),
#step_n int, -- precision step numerator portion
#step_d int -- precision step denominator portion
select #min = -2,
#max = 3,
#step = '1/16'
select #step_n = left(#step, charindex('/', #step) - 1),
#step_d = stuff(#step, 1, charindex('/', #step), '')
; with rcte as
(
-- Anchor member
select n = #min, -- numerator
d = 1, -- denominator
v = convert(decimal(10,5), #min)
union all
-- Recursive member
select n = case when ( (r.n * #step_d) + (r.d * #step_n) ) % #step_d = 0
and (r.d * #step_d) % #step_d = 0
then ( (r.n * #step_d) + (r.d * #step_n) ) / #step_d
else (r.n * #step_d) + (r.d * #step_n)
end,
d = case when ( (r.n * #step_d) + (r.d * #step_n) ) % #step_d = 0
and (r.d * #step_d) % #step_d = 0
then (r.d * #step_d) / #step_d
else (r.d * #step_d)
end,
v = convert(decimal(10,5), ((r.n * #step_d) + (r.d * #step_n)) / (r.d * #step_d * 1.0))
from rcte r
where r.v < #max
)
select *,
fraction = case when n = 0
then '0'
when coalesce(d2, d) = 1
then convert(varchar(10), coalesce(n2, n))
else convert(varchar(10), coalesce(n2, n)) + '/' + convert(varchar(10), coalesce(d2, d))
end
from rcte r
cross apply -- use to simplify the fraction result
(
select n2 = case when n % 32 = 0 and d % 32 = 0 then n / 32
when n % 16 = 0 and d % 16 = 0 then n / 16
when n % 8 = 0 and d % 8 = 0 then n / 8
when n % 4 = 0 and d % 4 = 0 then n / 4
when n % 2 = 0 and d % 2 = 0 then n / 2
end,
d2 = case when n % 32 = 0 and d % 32 = 0 then d / 32
when n % 16 = 0 and d % 16 = 0 then d / 16
when n % 8 = 0 and d % 8 = 0 then d / 8
when n % 4 = 0 and d % 4 = 0 then d / 4
when n % 2 = 0 and d % 2 = 0 then d / 2
end
) s
order by v
option (MAXRECURSION 0)
Related
I have a very long text string being imported into a table. I would like to split the string up; I have a routine to pull the data into a table, but it creates all the data in a single field in a table.
Example Text:
05/10/2018 21:14,#FXAAF00123456,,Cup 1 X Plane,0.00000,OK,Cup 1 Y Plane,0.00000,OK,Cup 1 Z Plane,40.64252,OK,Cup 2 X Plane,77.89434,OK,..etc
(The test string is much longer than this, in the region of 1500-1700 characters, but with the same structure in the rest of the string).
This data is a series of test measurements, with the name of the value, the value, and the OK/NOK indicator.
I want the results to be stored in a table (variable) with three fields, so the data above becomes:
Field1|Field2|Field3
05/10/2018 21:14|#FXAAF00123456|null|
Cup 1 X Plane|0.00000|OK|
Cup 1 Y Plane|0.00000|OK|
Cup 1 Z Plane|40.64252|OK|
Cup 2 X Plane|77.89434|OK|
...etc
I am using this function to split the string into a table variable:
CREATE FUNCTION [dbo].[fnSplitString]
(
#InputString NVARCHAR(MAX),
#Delim VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT [Value] FROM
(
SELECT
[Value] = LTRIM(RTRIM(SUBSTRING(#InputString, [Number],
CHARINDEX(#Delim, #InputString + #Delim, [Number]) - [Number])))
FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_objects) AS x
WHERE Number <= LEN(#InputString)
AND SUBSTRING(#Delim + #InputString, [Number], LEN(#Delim)) = #Delim
) AS y
);
How can this be modified to give the output required above?
You can try this tiny inline splitting approach.
DECLARE #s VARCHAR(MAX)='05/10/2018 21:14,#FXAAF00123456,,Cup 1 X Plane,0.00000,OK,Cup 1 Y Plane,0.00000,OK,Cup 1 Z Plane,40.64252,OK,Cup 2 X Plane,77.89434,OK';
;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CAST(CHARINDEX(',', #s, j+1) AS INT) FROM a WHERE j > i)
,b AS (SELECT n, SUBSTRING(#s, i+1, IIF(j>0, j, LEN(#s)+1)-i-1) s FROM a WHERE i >= 0)
,c AS (SELECT n,(n-1) % 3 AS Position,(n-1)/3 AS RowIndex,s FROM b)
SELECT MAX(CASE WHEN Position=0 THEN s END) AS part1
,MAX(CASE WHEN Position=1 THEN s END) AS part2
,MAX(CASE WHEN Position=2 THEN s END) AS part3
FROM c
GROUP BY RowIndex
OPTION (MAXRECURSION 0);
The result
part1 part2 part3
05/10/2018 21:14 #FXAAF00123456
Cup 1 X Plane 0.00000 OK
Cup 1 Y Plane 0.00000 OK
Cup 1 Z Plane 40.64252 OK
Cup 2 X Plane 77.89434 OK
Hint
You might change your splitter function to the recursive approach above. On the one side you are limited to a string-length of the count in sys.all_objects which might be smaller than your input. On the other side your approach has to test each and any position, while the recursive approach hops from spot to spot. Should be faster...
This could easily be opened for a multi-character-delimiter if needed...
UPDATE another approach without recursion
...which makes it clumsy to be used in a splitter function (due to OPTION MAXRECURSION(0), which must be placed at the end of the query and cannot live within the function). Try it out:
;WITH
a(Casted) AS (SELECT CAST('<x>' + REPLACE((SELECT #s AS [*] FOR XML PATH('')),',','</x><x>') + '</x>' AS XML))
,b(s,RowIndex,Position) AS
(
SELECT x.value(N'text()[1]','nvarchar(max)')
,(ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) -1) /3
,(ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) -1) %3
FROM a
CROSS APPLY Casted.nodes(N'/x') X(x)
)
SELECT RowIndex
,MAX(CASE WHEN Position=0 THEN s END) AS part1
,MAX(CASE WHEN Position=1 THEN s END) AS part2
,MAX(CASE WHEN Position=2 THEN s END) AS part3
FROM b
GROUP BY RowIndex;
Hint:
Using (SELECT #s AS [*] FOR XML PATH('')) will make this approach save with forbidden characters...
this required a small modification to your fnSplitString function. Add a RowNo to identify the original sequence of the delimited item
CREATE FUNCTION [dbo].[fnSplitString]
(
#InputString NVARCHAR(MAX),
#Delim VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT [Value] FROM
(
SELECT RowNo = ROW_NUMBER() OVER (ORDER BY Number),
[Value] = LTRIM(RTRIM(SUBSTRING(#InputString, [Number],
CHARINDEX(#Delim, #InputString + #Delim, [Number]) - [Number])))
FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_objects) AS x
WHERE Number <= LEN(#InputString)
AND SUBSTRING(#Delim + #InputString, [Number], LEN(#Delim)) = #Delim
) AS y
);
And with that, you can group every 3 rows as one. Also the RowNo can be used to identify the column
The query
; with tbl as
(
select col = '05/10/2018 21:14,#FXAAF00123456,,Cup 1 X Plane,0.00000,OK,Cup 1 Y Plane,0.00000,OK,Cup 1 Z Plane,40.64252,OK,Cup 2 X Plane,77.89434,OK'
)
select Field1 = MAX(CASE WHEN (RowNo - 1) % 3 = 0 THEN Value END),
Field2 = MAX(CASE WHEN (RowNo - 1) % 3 = 1 THEN Value END),
Field3 = MAX(CASE WHEN (RowNo - 1) % 3 = 2 THEN Value END)
from tbl t
cross apply dbo.fnSplitString (t.col, ',')
group by (RowNo - 1) / 3
Can you try following script after you create the SQL split function given in the reference document.
That split function returns the order of splitted string fragments so that information is used for row data
declare #str nvarchar(max) = '05/10/2018 21:14,#FXAAF00123456,,Cup 1 X Plane,0.00000,OK,Cup 1 Y Plane,0.00000,OK,Cup 1 Z Plane,40.64252,OK,Cup 2 X Plane,77.89434,OK'
select
floor(id / 3)+1 rn,
case when id % 3 = 1 then val end Field1,
case when id % 3 = 2 then val end Field2,
case when id % 3 = 0 then val end Field3
from dbo.Split(#str,',')
select
rn,
max(Field1) Field1,
max(Field2) Field2,
max(Field3) Field3
from (
select
floor((id-1) / 3)+1 rn,
case when id % 3 = 1 then val end Field1,
case when id % 3 = 2 then val end Field2,
case when id % 3 = 0 then val end Field3
from dbo.Split(#str,',')
) t
group by rn
I have a database field of Brazilian CPF numbers and want to check for their validity. These are 11 digit strings which are 9 digits and 2 checksum digits.
I currently implemented the checksum in MS Excel (see below) but I'd like to figure out a way to do it in SQL.
Checksum works as follows: (Hold on tight, this is nuts.)
The CPF number is written in the form ABCDEFGHI / JK or directly as
ABCDEFGHIJK, where the digits can not all be the same as each other.
The J is called 1st digit check of the CPF number.
The K is called the 2nd check digit of the CPF number.
First digit (J):
Multiply each digit of the first 9 by a constant:
10*A + 9*B + 8*C + 7*D + 6*E + 5*F + 4*G + 3*H + 2*I
Divide this sum by 11 and if the remainder is 0 or 1, J will be 0. If the remainder is >=2, J will be 11 - remainder.
Second digit (K): (Same calculation but including digit J)
Multiply each digit of the first 10 by a constant:
11A + 10B + 9C + 8D + 7E + 6F + 5G + 4H + 3I + 2J
Divide this sum by 11 and if the remainder is 0 or 1, K will be 0. If the remainder is >=2, K will be 11 - remainder.
--Implementation in MS Excel--
Assuming the CPF is in A2.
Optimizations here are welcome but not really the point of this question.
Digit J: =IF(MOD(SUM(MID($A2,1,1)*10,MID($A2,2,1)*9,MID($A2,3,1)*8,MID($A2,4,1)*7,MID($A2,5,1)*6,MID($A2,6,1)*5,MID($A2,7,1)*4,MID($A2,8,1)*3,MID($A2,9,1)*2),11)<=1,NUMBERVALUE(LEFT(RIGHT($A2,2),1))=0,NUMBERVALUE(LEFT(RIGHT($A2,2),1))=(11-MOD(SUM(MID($A2,1,1)*10,MID($A2,2,1)*9,MID($A2,3,1)*8,MID($A2,4,1)*7,MID($A2,5,1)*6,MID($A2,6,1)*5,MID($A2,7,1)*4,MID($A2,8,1)*3,MID($A2,9,1)*2),11)))
Digit K:
=IF(MOD(SUM(MID($A2,1,1)*11,MID($A2,2,1)*10,MID($A2,3,1)*9,MID($A2,4,1)*8,MID($A2,5,1)*7,MID($A2,6,1)*6,MID($A2,7,1)*5,MID($A2,8,1)*4,MID($A2,9,1)*3,MID($A2,10,1)*2),11)<=1,NUMBERVALUE(LEFT(RIGHT($A2,1),1))=0,NUMBERVALUE(LEFT(RIGHT($A2,1),1))=(11-MOD(SUM(MID($A2,1,1)*11,MID($A2,2,1)*10,MID($A2,3,1)*9,MID($A2,4,1)*8,MID($A2,5,1)*7,MID($A2,6,1)*6,MID($A2,7,1)*5,MID($A2,8,1)*4,MID($A2,9,1)*3,MID($A2,10,1)*2),11)))
My test table:
-- Create a table called CPF
CREATE TABLE CPF(Id integer PRIMARY KEY, No integer);
-- Create few records in this table
INSERT INTO CPF VALUES(1, 12345678901);
My nested query:
SELECT No,
(CASE WHEN (J != J2) THEN 'J wrong!' ELSE 'J ok!' END) as Jchk,
(CASE WHEN (K != K2) THEN 'K wrong!' ELSE 'K ok!' END) as Kchk
FROM
(SELECT No, J, K,
(CASE WHEN MJ < 2 THEN 0 ELSE 11 - MJ END) as J2,
(CASE WHEN MK < 2 THEN 0 ELSE 11 - MK END) as K2
FROM
(SELECT No, J, K,
MOD(10*A + 9*B + 8*C + 7*D + 6*E + 5*F + 4*G + 3*H + 2*I, 11) as MJ,
MOD(11*A + 10*B + 9*C + 8*D + 7*E + 6*F + 5*G + 4*H + 3*I + 2*J, 11) as MK
FROM
(SELECT
No,
substr(to_char(No), 1, 1) as A,
substr(to_char(No), 2, 1) as B,
substr(to_char(No), 3, 1) as C,
substr(to_char(No), 4, 1) as D,
substr(to_char(No), 5, 1) as E,
substr(to_char(No), 6, 1) as F,
substr(to_char(No), 7, 1) as G,
substr(to_char(No), 8, 1) as H,
substr(to_char(No), 9, 1) as I,
substr(to_char(No), 10, 1) as J,
substr(to_char(No), 11, 1) as K
FROM CPF)))
;
Assuming you have a table with an id primary key column and a cpf column that is NUMBER(9,0) data type then something like:
WITH digits ( id, a, b, c, d, e, f, g, h, i ) AS (
SELECT id,
MOD( TRUNC( cpf / 1e8 ), 10 ),
MOD( TRUNC( cpf / 1e7 ), 10 ),
MOD( TRUNC( cpf / 1e6 ), 10 ),
MOD( TRUNC( cpf / 1e5 ), 10 ),
MOD( TRUNC( cpf / 1e4 ), 10 ),
MOD( TRUNC( cpf / 1e3 ), 10 ),
MOD( TRUNC( cpf / 1e2 ), 10 ),
MOD( TRUNC( cpf / 1e1 ), 10 ),
MOD( TRUNC( cpf / 1e0 ), 10 )
FROM your_table
),
values1 ( id, j, k ) AS (
SELECT id,
MOD( 10*A + 9*B + 8*C + 7*D + 6*E + 5*F + 4*G + 3*H + 2*I, 11 ),
11*A + 10*B + 9*C + 8*D + 7*E + 6*F + 5*G + 4*H + 3*I
FROM digits
),
values2 ( id, j, k ) AS (
SELECT id,
CASE WHEN j <= 1 THEN 0 ELSE 11 - j END,
MOD( k + 2 * CASE WHEN j <= 1 THEN 0 ELSE 11 - j END, 11 )
FROM values1
)
SELECT id,
j,
CASE WHEN k <= 1 THEN 0 ELSE 11 - k END AS k
FROM values2
#SAR622: great question and thanks for the algorithm.
Here is a t-SQL solution for SQL Server, just in case. Note that Cadastro de Pessoas Físicas (CPF) numbers can only have 11 digits (pre-panded by zeros), that is they cannot exceed 10^12-1. If you note 14 digit numbers in your dataset, these are likely to be Cadastro Nacional da Pessoa Jurídica (CNPJ) numbers issued to business (or typos or something else). The fake CPF and CNPJ numbers can be generated (in bulk) and validated (individually) here. Also this site provides more info about a business located by its CNPJ (think of it as an implicit CNPJ validation). When validating a CPF number remember to check if the number is in range [0, 10^12-1]. You may need to remove any punctuation symbols and other invalid characters (as users, we tend to make typos).
This input table has top 5 invalid CPF numbers and bottom 4 valid ones:
IF OBJECT_ID('tempdb..#x') IS NOT NULL DROP TABLE #x;
CREATE TABLE #x (CPF BIGINT default NULL);
INSERT INTO #x (CPF) VALUES (12345678900);
INSERT INTO #x (CPF) VALUES (11);
INSERT INTO #x (CPF) VALUES (1010101010101010);
INSERT INTO #x (CPF) VALUES (11111179011525590);
INSERT INTO #x (CPF) VALUES (-32081397641);
INSERT INTO #x (CPF) VALUES (00000008726210061);
INSERT INTO #x (CPF) VALUES (56000608314);
INSERT INTO #x (CPF) VALUES (73570630706);
INSERT INTO #x (CPF) VALUES (93957133564);
The following t-SQL function modularizes implementation, but will likely be slower than the raw t-SQL that follows. Alternatively, you can create a t-SQL function with a TABLE input/output or a stored procedure.
ALTER FUNCTION fnIsCPF(#n BIGINT) RETURNS INT AS
BEGIN
DECLARE #isValid BIT = 0;
IF (#n > 0 AND #n < 100000000000)
BEGIN
--Parse out numbers
DECLARE #a TINYINT = FLOOR( #n / 10000000000)% 10;
DECLARE #b TINYINT = FLOOR( #n / 1000000000)% 10;
DECLARE #c TINYINT = FLOOR( #n / 100000000)% 10;
DECLARE #d TINYINT = FLOOR( #n / 10000000)% 10;
DECLARE #e TINYINT = FLOOR( #n / 1000000)% 10;
DECLARE #f TINYINT = FLOOR( #n / 100000)% 10;
DECLARE #g TINYINT = FLOOR( #n / 10000)% 10;
DECLARE #h TINYINT = FLOOR( #n / 1000)% 10;
DECLARE #i TINYINT = FLOOR( #n / 100)% 10;
DECLARE #j TINYINT = ISNULL(NULLIF(NULLIF(11-( 10*#a + 9*#b + 8*#c + 7*#d + 6*#e + 5*#f + 4*#g + 3*#h + 2*#i) % 11, 11), 10), 0);
DECLARE #k TINYINT = ISNULL(NULLIF(NULLIF(11 - (11*#a +10*#b + 9*#c + 8*#d + 7*#e + 6*#f + 5*#g + 4*#h + 3*#i + 2 * #j)% 11, 11), 10), 0);
RETURN CASE WHEN #j=FLOOR(#n / 10)% 10 AND #k=FLOOR(#n)% 10 THEN 1 ELSE 0 END
END;
RETURN #isValid;
END;
The output is:
SELECT CPF, isValid=dbo.fnIsCPF(CPF) FROM #x
CPF isValid
12345678900 0
11 0
1010101010101010 0
11111179011525590 0
-32081397641 0
8726210061 1
56000608314 1
73570630706 1
93957133564 1
t-SQL for a table:
WITH digits ( CPF, a, b, c, d, e, f, g, h, i ) AS (
SELECT CPF,
FLOOR( CPF / 10000000000)% 10,
FLOOR( CPF / 1000000000)% 10,
FLOOR( CPF / 100000000)% 10,
FLOOR( CPF / 10000000)% 10,
FLOOR( CPF / 1000000)% 10,
FLOOR( CPF / 100000)% 10,
FLOOR( CPF / 10000)% 10,
FLOOR( CPF / 1000)% 10,
FLOOR( CPF / 100)% 10
FROM #x
),
jk ( CPF, j, k ) AS (
SELECT CPF, ISNULL(NULLIF(NULLIF(11-( 10*A + 9*B + 8*C + 7*D + 6*E + 5*F + 4*G + 3*H + 2*I) % 11, 11), 10), 0),
11*A +10*B + 9*C + 8*D + 7*E + 6*F + 5*G + 4*H + 3*I
FROM digits
),
jk2 ( CPF, j, k ) AS (
SELECT CPF, j, ISNULL(NULLIF(NULLIF(11 - (k + 2 * j)% 11, 11), 10), 0)
FROM jk
)
SELECT CPF, isValid=CASE WHEN CPF>0 AND CPF<99999999999 AND j=FLOOR( CPF / 10)% 10 AND k=FLOOR( CPF)% 10 THEN 1 ELSE 0 END
FROM jk2
yielding the same output.
How would I convert the following CTE into a recursive subquery? It's an implementation of Newtons Method.
Reasons:
1) I have no permissions to create functions or stored procs in the DB
2) I must do everything in TSQL
3) Not using Oracle
TESTDATA Table
PMT t V
6918.26 6 410000
3636.51 14 460000
3077.98 22 630000
1645.14 18 340000
8591.67 13 850000
Desired Output
PMT t V Newton
6918.26 6 410000 0.066340421
3636.51 14 460000 0.042449138
3077.98 22 630000 0.024132674
1645.14 18 340000 0.004921588
8591.67 13 850000 0.075982984
_
DECLARE #PMT AS FLOAT
DECLARE #t AS FLOAT
DECLARE #V AS FLOAT
--These will be only for 1 example.
SET #PMT = 6918.26740930922
SET #t = 6
SET #V = 410000
;With Newton (n, i,Fi,dFi) AS (
--base
SELECT
1,
CAST(0.1 AS FLOAT)
,#PMT * (1 - POWER((1 + CAST(0.1 AS FLOAT) / 12), (-#t * 12))) - #V * CAST(0.1 AS FLOAT) / 12
,#PMT * #t * 12 * POWER((1 + CAST(0.1 AS FLOAT) / 12), (-#t * 12 - 1)) - #V
UNION ALL
--recursion
SELECT
n + 1
,i - Fi/dFi
,#PMT * (1 - POWER((1 + i / 12), (-#t * 12))) - #V * i / 12
,#PMT * #t * 12 * POWER((1 + i / 12), (-#t * 12 - 1)) - #V
FROM Newton WHERE n < 500)
--to get the desired value for params above
SELECT [x].i
FROM (
SELECT n, i, Fi, dFi
FROM Newton
WHERE n = 500
) [x]
OPTION (MAXRECURSION 500)
_
I want Newton to evaluate on Every record of TestData as a stand alone column.
Any thoughts?
Is there a simple an elegant way to create a numerator in SQL the do the following:
Three digit number
Each digit can can have a number from 0-Z : Min 000, Max ZZZ
for example let's take : 129, After increasing by 1, the new number will be 12A
When number is 15Z, after increasing by one it will turn into 160 and so on.
I can only use SQL, no code behind !!.
I only need an enumerator and not a conversion between bases like other have suggested.
Can someone help me write a UDF.
I got an excellent and elegant answer which I marked below.
Who ever marked my question as negative please reconsider because this is a real problem and not a theoratic one and it saved me a lot of workarounds.
Thanks in advance.
Produce cartesian and take the first that is greater then input value:
declare #v varchar(10) = '129'
;with cte as(select * from (values('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9')
,('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H'),('I'),('J'),('K')
,('L'),('M'),('N'),('O'),('P'),('Q'),('R'),('S'),('T'),('U'),('V')
,('W'),('X'),('Y'),('Z')) t(n))
select top 1 c1.n + c2.n + c3.n as n
from cte c1
cross join cte c2
cross join cte c3
where c1.n + c2.n + c3.n > #v
order by n
You should store the values numeric and then convert them when needed to your format.
Here is a script to add one to the strange format. The script is not checking for overflow nor invalid data in the original value:
DECLARE #original char(3) = '129'
DECLARE #val int =
CASE WHEN RIGHT(#original, 1) > '9' THEN ASCII(RIGHT(#original, 1)) - 55
ELSE RIGHT(#original, 1) END
+CASE WHEN SUBSTRING(#original, 2,1) > '9'
THEN ASCII(SUBSTRING(#original, 2,1)) - 55 ELSE SUBSTRING(#original, 2,1) END * 36
+CASE WHEN LEFT(#original, 1) > '9'
THEN ASCII(LEFT(#original, 1)) - 55 ELSE LEFT(#original, 1) END * 36 * 36
+1 -- increase 1
SELECT
CHAR(CASE WHEN #val / 36 / 36 > 9 THEN #val / 36 / 36 + 55
ELSE #val / 36 / 36+48 END)+
CHAR(CASE WHEN (#val / 36) % 36 > 9 THEN (#val / 36) % 36 + 55
ELSE (#val / 36) % 36+48 END)+
CHAR(CASE WHEN #val % 36 % 36 > 9 THEN #val % 36 % 36 + 55
ELSE #val % 36 % 36+48 END)
Result:
12A
I suggest you create this table in your DB with this query
;WITH Ch AS
( select i,C=CASE WHEN i<=10 THEN Char(47+i) ELSE CHAR(54+i) END FROM (SELECT TOP 36 row_number() over(order by object_id) i from sys.objects) t )
SELECT row_number() over(order by Ch.c,Ch2.c,Ch3.c) i,
Ch.c + Ch2.c + Ch3.c C INTO B36 FROM Ch CROSS JOIN Ch Ch2 CROSS JOIN Ch Ch3
So you can do indexes in the i & C columns
Then you do queries from this table for conversions with index seeking fastest speed and optimized as follows:
SELECT C FROM B36 WHERE i=3728
Reverse encoding with
SELECT i FROM B36 WHERE C='AG7'
Function that converts bigint to Base36
CREATE FUNCTION dbo.fnBase36
(
#Val BIGINT
)
RETURNS VARCHAR(13)
AS
BEGIN
DECLARE #Result VARCHAR(13) = ''
IF (#Val <= 0)
BEGIN
RETURN '0'
END
WHILE (#Val > 0)
BEGIN
SELECT #Result = CHAR(#Val % 36 + CASE WHEN #Val % 36 < 10 THEN 48 ELSE 55 END) + #Result,
#Val = FLOOR(#Val/36)
END
RETURN #Result
END
GO
select dbo.fnBase36(321)
--returns 8X
I can give you a converter like this:
char(48 + (i / (36 * 36)) + CASE WHEN (i / (36 * 36)) > 9 THEN 7 ELSE 0 END) +
char(48 + (i / 36) % 36 + CASE WHEN (i / 36) % 36 > 9 THEN 7 ELSE 0 END) +
char(48 + i % 36 + CASE WHEN i % 36 > 9 THEN 7 ELSE 0 END)
that can convert an int value to what you want.
[SQL Fiddle Demo]
Edit
I also can give you inversion of above converter like this:
(E.g.: #t varchar(max) = '12Z')
(ASCII(LEFT(#t, 1)) - 48 - CASE WHEN ASCII(LEFT(#t, 1)) > ASCII('9') THEN 7 ELSE 0 END) * 36 * 36 +
(ASCII(SUBSTRING(#t,2,1)) - 48 - CASE WHEN ASCII(SUBSTRING(#t,2,1)) > ASCII('9') THEN 7 ELSE 0 END) * 36 +
(ASCII(RIGHT(#t, 1)) - 48 - CASE WHEN ASCII(RIGHT(#t, 1)) > ASCII('9') THEN 7 ELSE 0 END)
I recently responded to this question in the SSRS-2008 tag that required changing the day number in a date to the ordinal number (i.e. "1st", "2nd" instead of "1", "2"). The solution involved a VB.Net function. I'm curious how one would go about performing this task in SQL (t-sql and SQL Server in particular), or if there is some built in support.
So here is a scenario: say you have organized a footrace for 1000 runners and have the results in a table with the columns Name and Place (in normal numbers). You want to create a query that will display a user's name and their place in ordinal numbers.
Here's a scalable solution that should work for any number. I thought other's used % 100 for 11,12,13 but I was mistaken.
WITH CTE_Numbers
AS
(
SELECT 1 num
UNION ALL
SELECT num + 1
FROM CTE_Numbers
WHERE num < 1000
)
SELECT CAST(num AS VARCHAR(10))
+
CASE
WHEN num % 100 IN (11,12,13) THEN 'th' --first checks for exception
WHEN num % 10 = 1 THEN 'st'
WHEN num % 10 = 2 THEN 'nd'
WHEN num % 10 = 3 THEN 'rd'
ELSE 'th' --works for num % 10 IN (4,5,6,7,8,9,0)
END
FROM CTE_Numbers
OPTION (MAXRECURSION 0)
You can do that just as easily in SQL as in the app layer:
DECLARE #myDate DATETIME = '2015-05-21';
DECLARE #day INT;
SELECT #day = DAY(#myDate);
SELECT CASE WHEN #day IN ( 11, 12, 13 ) THEN CAST(#day AS VARCHAR(10)) + 'th'
WHEN #day % 10 = 1 THEN CAST(#day AS VARCHAR(10)) + 'st'
WHEN #day % 10 = 2 THEN CAST(#day AS VARCHAR(10)) + 'nd'
WHEN #day % 10 = 3 THEN CAST(#day AS VARCHAR(10)) + 'rd'
ELSE CAST(#day AS VARCHAR(10)) + 'th'
END
You could also put this in a scalar function if necessary.
EDIT
For your example, it would be:
SELECT Name ,
CASE WHEN Place IN ( 11, 12, 13 )
THEN CAST(Place AS VARCHAR(10)) + 'th'
WHEN Place % 10 = 1 THEN CAST(Place AS VARCHAR(10)) + 'st'
WHEN Place % 10 = 2 THEN CAST(Place AS VARCHAR(10)) + 'nd'
WHEN Place % 10 = 3 THEN CAST(Place AS VARCHAR(10)) + 'rd'
ELSE CAST(Place AS VARCHAR(10)) + 'th'
END AS Place
FROM FootRaceResults;
Be very afraid:
with
ArabicRomanConversions as (
select *
from ( values
( 0, '', '', '', '' ), ( 1, 'I', 'X', 'C', 'M' ), ( 2, 'II', 'XX', 'CC', 'MM' ), ( 3, 'III', 'XXX', 'CCC', 'MMM' ), ( 4, 'IV', 'XL', 'CD', '?' ),
( 5, 'V', 'L', 'D', '?' ), ( 6, 'VI', 'LX', 'DC', '?' ), ( 7, 'VII', 'LXX', 'DCC', '?' ), ( 8, 'VIII', 'LXXX', 'DCCC', '?' ), ( 9, 'IX', 'XC', 'CM', '?' )
) as Placeholder ( Arabic, Ones, Tens, Hundreds, Thousands )
),
OrdinalConversions as (
select *
from ( values
( 1, 'st' ), ( 2, 'nd' ), ( 3, 'rd' ), ( 11, 'th' ), ( 12, 'th' ), ( 13, 'th' )
) as Placeholder2 ( Number, Suffix )
),
Numbers as (
select 1 as Number
union all
select Number + 1
from Numbers
where Number < 3999 )
select Number as Arabic,
( select Thousands from ArabicRomanConversions where Arabic = Number / 1000 ) +
( select Hundreds from ArabicRomanConversions where Arabic = Number / 100 % 10 ) +
( select Tens from ArabicRomanConversions where Arabic = Number / 10 % 10 ) +
( select Ones from ArabicRomanConversions where Arabic = Number % 10 ) as Roman,
Cast( Number as VarChar(4) ) + Coalesce( (
select top 1 Suffix from OrdinalConversions where Number = Numbers.Number % 100 or Number = Numbers.Number % 10 order by Number desc ), 'th' ) as Ordinal
from Numbers option ( MaxRecursion 3998 );
You could use a case statement, I.e.,
UPDATE: Taking into account the teens, as mentioned by TPhe and refactored slightly.
SELECT
Name,
CASE
WHEN Place in(11, 12, 13) then CAST(Place as VARCHAR(20)) + 'th'
WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '1' then CAST(Place as VARCHAR(20)) + 'st'
WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '2' then CAST(Place as VARCHAR(20)) + 'nd'
WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '3' then CAST(Place as VARCHAR(20)) + 'rd'
ELSE CAST(Place as VARCHAR(20)) + 'th'
END as Place
FROM
RunnerTable
DECLARE #Number int = 94
SELECT
CONVERT(VARCHAR(10),#NUMBER) + CASE WHEN #Number % 100 IN (11, 12, 13) THEN 'th'
ELSE
CASE #Number % 10
WHEN 1 THEN 'st'
WHEN 2 THEN 'nd'
WHEN 3 THEN 'rd'
ELSE 'th'
END
END
This Would be much better for any number
create Function dbo.fn_Numbers_Ordinal (#N as bigint) returns varchar(50)
as Begin
Declare #a as varchar(50)= CAST(#N AS VARCHAR(50))
return(
SELECT CAST(#N AS VARCHAR(50))
+
CASE
WHEN Right(#a,2)='11' or Right(#a,2)='12' or Right(#a,2)='13' Then 'th'
WHEN #N % 10 = 1 THEN 'st'
WHEN #N % 10 = 2 THEN 'nd'
WHEN #N % 10 = 3 THEN 'rd'
ELSE 'th' --for #N % 10 IN (4,5,6,7,8,9,0)
END
)
end
Just figured I would add onto the various options. Here's a one-liner. I left this as a comment about a year ago. But someone suggested I submit it as an answer. So here ya go:
SELECT OrdinalRank = CONCAT(num, IIF(num % 100 IN (11,12,13),'th',COALESCE(CHOOSE(num % 10,'st','nd','rd'),'th')))
FROM (
VALUES (1),(2),(3),(4),(5),(10),(11),(20),(21),(22),(23),(24),(101),(102),(103)
) x(num)
--Result:
--1st
--2nd
--3rd
--4th
--5th
--10th
--11th
--20th
--21st
--22nd
--23rd
--24th
--101st
--102nd
--103rd
This takes advantage of the IIF and CHOOSE functions, which are only available in SQL 2012+.
DECLARE #Number int = 113,
#Superscript int
IF #Number IS NOT NULL
BEGIN
IF LEN(#Number) >= 2
SELECT #Superscript = RIGHT(#Number, 2)
ELSE
SELECT #Superscript = RIGHT(#Number, 1)
SELECT #Number as Number,
CASE WHEN #Superscript in (11,12,13) THEN 'th'
ELSE CASE WHEN #Superscript = 1 THEN 'st'
WHEN #Superscript = 2 THEN 'nd'
WHEN #Superscript = 3 THEN 'rd'
ELSE 'th'
END
END as Superscript
END ELSE
SELECT 0 as Number, 'th' as Superscript
Another option to solve this with the FORMAT function (also you can display month names in other languages):
;WITH cte AS (
SELECT 1 AS dayordinal ,'st' AS suffix
UNION
SELECT 2 AS dayordinal ,'nd' AS suffix
UNION
SELECT 3 AS dayordinal ,'rd' AS suffix
)
, YourTable AS --this is just for example
(SELECT CAST('1/1/2022' AS DATE) DateColumn
UNION
SELECT CAST('1/14/2022' AS DATE) DateColumn
UNION
SELECT CAST('4/4/2022' AS DATE) DateColumn
UNION
SELECT CAST('2/2/2022' AS DATE) DateColumn
UNION
SELECT CAST('3/13/2022' AS DATE) DateColumn
)
SELECT CAST(DATEPART(DAY, DateColumn) AS NVARCHAR(2))+ISNULL(c.suffix, 'th')+' '+ FORMAT(DateColumn, 'MMMM yyyy', 'fr-FR')
FROM YourTable t
LEFT JOIN cte c ON c.dayordinal=RIGHT(DATEPART(DAY, DateColumn),1)
Use the SSRS expression below:
= DAY(Globals!ExecutionTime) &
SWITCH(
DAY(Globals!ExecutionTime)= 1 OR DAY(Globals!ExecutionTime) = 21 OR DAY(Globals!ExecutionTime)=31, "st",
DAY(Globals!ExecutionTime)= 2 OR DAY(Globals!ExecutionTime) = 22 , "nd",
DAY(Globals!ExecutionTime)= 3 OR DAY(Globals!ExecutionTime) = 23 , "rd",
true, "th"
)
This is easy to implement and works well.
Public Function OrdinalNumberSuffix(ByVal InNumber As Integer) As String
Dim StrNumber As String, _
Digit As Byte, _
Suffix As String
StrNumber = Trim(Str(InNumber))
If Val(StrNumber) > 3 And Val(StrNumber) < 14 Then
Digit = Val(Right(StrNumber, 2))
Else
Digit = Val(Right(StrNumber, 1))
End If
Select Case Digit
Case 1: Suffix = "st"
Case 2: Suffix = "nd"
Case 3: Suffix = "rd"
Case Else: Suffix = "th"
End Select
OrdinalNumberSuffix = " " & StrNumber & Suffix & " "
End Function