How to build smiliar VLOOKUP function in SQL - sql

I have a small check digit algorithm in excel and it is basically a VLOOKUP function. Now I would like to make function in sql with the same result.
To check digit i ned a static table for this.
I'm beginning with 0 (orange) and than my vlookup function is:
=SVERWEIS(C16;$A$4:$K$13;B17+2;FALSCH)
translated in englisch
=VLOOKUP(C16;$A$4:$K$13;B17+2;FALSE)
how do i check the "check digit"
first of all i'm looking in the 0 (C16) column (orange cell)
than in in the transfer column 9th row (b17) the combination of this two digits = 5 (my new transfer)
next one
now im looing in the
transfer column 5th row and (B18) =6 -> that is the column
the combination is now 5th(transfer) row and 6 column -> my new transfer is 9
next one
the combination is now 9th row (transfer) and 9 column -> my new transfer is 3
and so one...
the last digit should be 2 (C42) in the first example ... this number calculate minus 10 -> 10-2 = 8
What i have done so far:
i created a new table in sql that contains the CheckDigit Tbl.
CREATE TABLE CheckTbl(
transfer INTEGER NOT NULL PRIMARY KEY
,0 INTEGER NOT NULL
,1 INTEGER NOT NULL
,2 INTEGER NOT NULL
,3 INTEGER NOT NULL
,4 INTEGER NOT NULL
,5 INTEGER NOT NULL
,6 INTEGER NOT NULL
,7 INTEGER NOT NULL
,8 INTEGER NOT NULL
,9 INTEGER NOT NULL
,check_digit INTEGER NOT NULL
);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (0,0,9,4,6,8,2,7,1,3,5,0);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (1,9,4,6,8,2,7,1,3,5,0,9);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (2,4,6,8,2,7,1,3,5,0,9,8);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (3,6,8,2,7,1,3,5,0,9,4,7);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (4,8,2,7,1,3,5,0,9,4,6,6);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (5,2,7,1,3,5,0,9,4,6,8,5);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (6,7,1,3,5,0,9,4,6,8,2,4);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (7,1,3,5,0,9,4,6,8,2,7,3);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (8,3,5,0,9,4,6,8,2,7,1,2);
INSERT INTO CheckTbl(transfer,0,1,2,3,4,5,6,7,8,9,check_digit) VALUES (9,5,0,9,4,6,8,2,7,1,3,1);
my sql:
DECLARE #refNr nvarchar(30) = '9699100000030000201830'
DECLARE #str VARCHAR(50), #Inc INT, #len INT, #char VARCHAR(50)
SET #str = #refNr
SET #Inc = 1
SET #len = LEN(#str)
WHILE #Inc<= #len
BEGIN
SET #char = COALESCE(#char+',' ,'') + SUBSTRING(#str, #Inc, 1)
SET #Inc=#Inc+1
END
SELECT [value] FROM string_split(#char, ',') WHERE RTRIM(value) <> '';
SELECT [transfer]
,[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]
,[check_digit]
FROM [CCHelper].[dbo].[CheckTbl]
SELECT
[value]
FROM
string_split(#char, ',') as SS
LEFT JOIN CheckTbl CT on (SS.[value] = CT. .....)
I'm also splitting the long number into rows.
Unfortunately i don't know exactly how do write the on condition to join the tables.
I need only one number... the check digit number.
SOLUTION:
finally i had some time to rethink the solution:
Create FUNCTION [dbo].[CheckDigit] (
#long_number VARCHAR(80))
RETURNS INT
AS
BEGIN
DECLARE #check_digit INT;
DECLARE #numbers VARCHAR(50) = '0946827135';
DECLARE #check_digits TABLE (
id INT IDENTITY(1,1),
alg INT
);
DECLARE #numbers_len INT;
SELECT #numbers_len = LEN(#numbers);
DECLARE #item INT = 1;
WHILE #item <= #numbers_len
BEGIN
INSERT INTO #check_digits (alg) SELECT CONVERT(INT, SUBSTRING(#numbers, #item, 1));
SELECT #item = #item + 1;
END;
DECLARE #offset TABLE (
id INT IDENTITY(1,1),
offset INT,
r2 INT
);
DECLARE #len INT;
SELECT #len = LEN(#long_number);
DECLARE #pos INT = 1;
DECLARE #rpr INT;
DECLARE #r2 INT = 0;
WHILE #pos <= #len
BEGIN
INSERT INTO #offset (offset) SELECT CONVERT(INT, SUBSTRING(#long_number, #pos, 1));
SELECT #rpr = #r2 + (SELECT offset FROM #offset WHERE id = #pos)
SELECT #r2 = (SELECT alg FROM #check_digits WHERE id= ((#rpr % 10)+1))
UPDATE #offset SET r2 = #r2 WHERE id = #pos;
SELECT #pos = #pos + 1;
END;
SELECT #check_digit = (SELECT (10-r2) AS Result FROM #offset WHERE id = #len)%10
RETURN #check_digit;
END;
Now it's working.

I don't think I'm actually 100% understanding how this works, but I worked through the Excel VLOOKUP, and I can get the answer for your first example, but it's not particularly pleasant to look at!
SET NOCOUNT ON;
DECLARE #check_digits TABLE (
[transfer] INT,
d0 INT,
d1 INT,
d2 INT,
d3 INT,
d4 INT,
d5 INT,
d6 INT,
d7 INT,
d8 INT,
d9 INT,
check_digit INT);
INSERT INTO #check_digits SELECT 0,0,9,4,6,8,2,7,1,3,5,0;
INSERT INTO #check_digits SELECT 1,9,4,6,8,2,7,1,3,5,0,9;
INSERT INTO #check_digits SELECT 2,4,6,8,2,7,1,3,5,0,9,8;
INSERT INTO #check_digits SELECT 3,6,8,2,7,1,3,5,0,9,4,7;
INSERT INTO #check_digits SELECT 4,8,2,7,1,3,5,0,9,4,6,6;
INSERT INTO #check_digits SELECT 5,2,7,1,3,5,0,9,4,6,8,5;
INSERT INTO #check_digits SELECT 6,7,1,3,5,0,9,4,6,8,2,4;
INSERT INTO #check_digits SELECT 7,1,3,5,0,9,4,6,8,2,7,3;
INSERT INTO #check_digits SELECT 8,3,5,0,9,4,6,8,2,7,1,2;
INSERT INTO #check_digits SELECT 9,5,0,9,4,6,8,2,7,1,3,1;
DECLARE #offset TABLE (
id INT,
offset INT);
INSERT INTO #offset
SELECT 1, 9
UNION ALL
SELECT 2, 6
UNION ALL
SELECT 3, 9
UNION ALL
SELECT 4, 9
UNION ALL
SELECT 5, 1
UNION ALL
SELECT 6, 0
UNION ALL
SELECT 7, 0
UNION ALL
SELECT 8, 0
UNION ALL
SELECT 9, 0
UNION ALL
SELECT 10, 0
UNION ALL
SELECT 11, 0
UNION ALL
SELECT 12, 3
UNION ALL
SELECT 13, 0
UNION ALL
SELECT 14, 0
UNION ALL
SELECT 15, 0
UNION ALL
SELECT 16, 0
UNION ALL
SELECT 17, 2
UNION ALL
SELECT 18, 0
UNION ALL
SELECT 19, 1
UNION ALL
SELECT 20, 8
UNION ALL
SELECT 21, 3
UNION ALL
SELECT 22, 0
UNION ALL
SELECT 23, 0
UNION ALL
SELECT 24, 0
UNION ALL
SELECT 25, 8
UNION ALL
SELECT 26, 7;
DECLARE #transfer INT = 0;
DECLARE #offset_value INT;
DECLARE #iterations INT = 1;
WHILE #iterations <= 26
BEGIN
SELECT #offset_value = offset + 2 FROM #offset WHERE id = #iterations;
SELECT #transfer =
CASE
WHEN #offset_value = 2 THEN d0
WHEN #offset_value = 3 THEN d1
WHEN #offset_value = 4 THEN d2
WHEN #offset_value = 5 THEN d3
WHEN #offset_value = 6 THEN d4
WHEN #offset_value = 7 THEN d5
WHEN #offset_value = 8 THEN d6
WHEN #offset_value = 9 THEN d7
WHEN #offset_value = 10 THEN d8
WHEN #offset_value = 11 THEN d9
END
FROM
#check_digits
WHERE
[transfer] = #transfer;
SELECT #iterations = #iterations + 1;
END;
PRINT 'Check Digit is ' + CONVERT(CHAR(1), 10 - #transfer);
SET NOCOUNT OFF;
This really needs to be set-based, probably using recursion? However, I don't really see the business use for this. I assume that there's a long number 96991000000300002018300087 and you want the checksum for this? I don't really get where your (O) Orange numbers come into play, so I just set a number to zero (as in your example) and sort of ignored this part.
Anyway, if you actually run that (it's non-destructive, as in it doesn't materialise anything) then you get the right answer of 2. Where you would go from here is unclear. Probably a better way to get the long number in? Probably something to add recursion for each digit in your long number, etc.?
I think I probably spent too long on this, but I got it working as a UDF:
CREATE FUNCTION dbo.CheckDigit (
#long_number VARCHAR(50))
RETURNS INT
AS
BEGIN
--Hardcoded check digits
DECLARE #check_digit INT;
DECLARE #check_digits TABLE (
[transfer] INT,
d0 INT,
d1 INT,
d2 INT,
d3 INT,
d4 INT,
d5 INT,
d6 INT,
d7 INT,
d8 INT,
d9 INT,
check_digit INT);
INSERT INTO #check_digits SELECT 0,0,9,4,6,8,2,7,1,3,5,0;
INSERT INTO #check_digits SELECT 1,9,4,6,8,2,7,1,3,5,0,9;
INSERT INTO #check_digits SELECT 2,4,6,8,2,7,1,3,5,0,9,8;
INSERT INTO #check_digits SELECT 3,6,8,2,7,1,3,5,0,9,4,7;
INSERT INTO #check_digits SELECT 4,8,2,7,1,3,5,0,9,4,6,6;
INSERT INTO #check_digits SELECT 5,2,7,1,3,5,0,9,4,6,8,5;
INSERT INTO #check_digits SELECT 6,7,1,3,5,0,9,4,6,8,2,4;
INSERT INTO #check_digits SELECT 7,1,3,5,0,9,4,6,8,2,7,3;
INSERT INTO #check_digits SELECT 8,3,5,0,9,4,6,8,2,7,1,2;
INSERT INTO #check_digits SELECT 9,5,0,9,4,6,8,2,7,1,3,1;
--Make the long number into an indexed list
DECLARE #offset TABLE (
id INT IDENTITY(1,1),
offset INT);
DECLARE #len INT;
SELECT #len = LEN(#long_number);
DECLARE #pos INT = 1;
WHILE #pos <= #len
BEGIN
INSERT INTO #offset (offset) SELECT CONVERT(INT, SUBSTRING(#long_number, #pos, 1));
SELECT #pos = #pos + 1;
END;
--Use recursive CTE
WITH cte1 AS (
SELECT
1 AS iterations,
offset,
offset + 2 AS new_offset
FROM
#offset
WHERE
id = 1
UNION ALL
SELECT
iterations + 1 AS iterations,
o.offset,
o.offset + 2 AS new_offset
FROM
cte1 c
INNER JOIN #offset o ON o.id = c.iterations + 1
WHERE
c.iterations <= #len),
cte2 AS (
SELECT
1 AS iterations,
c.new_offset,
CASE
WHEN c.new_offset = 2 THEN d0
WHEN c.new_offset = 3 THEN d1
WHEN c.new_offset = 4 THEN d2
WHEN c.new_offset = 5 THEN d3
WHEN c.new_offset = 6 THEN d4
WHEN c.new_offset = 7 THEN d5
WHEN c.new_offset = 8 THEN d6
WHEN c.new_offset = 9 THEN d7
WHEN c.new_offset = 10 THEN d8
WHEN c.new_offset = 11 THEN d9
END AS [transfer]
FROM
cte1 c
INNER JOIN #check_digits cd ON cd.[transfer] = 0
WHERE
iterations = 1
UNION ALL
SELECT
c.iterations + 1 AS iterations,
c1.new_offset,
CASE
WHEN c1.new_offset = 2 THEN d0
WHEN c1.new_offset = 3 THEN d1
WHEN c1.new_offset = 4 THEN d2
WHEN c1.new_offset = 5 THEN d3
WHEN c1.new_offset = 6 THEN d4
WHEN c1.new_offset = 7 THEN d5
WHEN c1.new_offset = 8 THEN d6
WHEN c1.new_offset = 9 THEN d7
WHEN c1.new_offset = 10 THEN d8
WHEN c1.new_offset = 11 THEN d9
END AS [transfer]
FROM
cte2 c
INNER JOIN #check_digits cd ON cd.[transfer] = c.[transfer]
INNER JOIN cte1 c1 ON c1.iterations = c.iterations + 1
WHERE
c1.iterations <= #len)
SELECT #check_digit = 10 - [transfer] FROM cte2 WHERE iterations = #len;
RETURN #check_digit;
END;
GO
SELECT dbo.CheckDigit('96991000000300002018300087');
SELECT dbo.CheckDigit('96991000000300002018300086');
SELECT dbo.CheckDigit('96991000000300002018300085');
...and I learned something today; I didn't know you could use recursion twice in the same query :D
If you run that then you should get 8, 2 and 4... which matches your Excel.

Related

"Array" usage in T-SQL

Trying to create a temp table where the AlphaSeq column would go through an "array" of alphanumeric characters that do not repeat and follow a flow as shown in the example below.
Example:
AlphaSeq
NumericSeq
A
1
A1
2
B
3
B1
4
AA
5
AB
6
BA
7
BB
8
[ETC]
[ETC]
Here's what I have so far (Obviously the "IG" is static as it is written now, so Im trying to make that field more fluid)
CREATE TABLE #Index
(
AlphaSeq VARCHAR(2),
NumericSeq TINYINT
)
DECLARE #ArrayNum AS TINYINT;
DECLARE #ReceiptSeq AS VARCHAR(2);
SET #ArrayNum = 0
SET #ReceiptSeq = 'IG'
WHILE #ArrayNum < 100
BEGIN
SET #ArrayNum = #ArrayNum + 1
INSERT INTO #Index (AlphaSeq, NumericSeq)
VALUES (#ReceiptSeq, #ArrayNum)
END
SELECT *
FROM #Index
You can simply cross-join the list of characters
CREATE TABLE #Index
(
AlphaSeq VARCHAR(2) NOT NULL,
NumericSeq SMALLINT NOT NULL IDENTITY
);
WITH Chars AS (
SELECT *
FROM (VALUES
('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')
) v(chr)
),
CharsWithNums AS (
SELECT *
FROM (VALUES
('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('0')
) v(chr)
UNION ALL
SELECT *
FROM Chars
)
INSERT #Index (AlphaSeq)
SELECT chr AS AlphaSeq
FROM Chars
UNION ALL
SELECT a.chr + b.chr
FROM Chars a
CROSS JOIN CharsWithNums b
ORDER BY AlphaSeq;

return two character from function containing numbers and alphabet in SQL

I want to make function which will return two character based on the condition.
let say I have a table tbl_Dummy
CREATE TABLE [dbo].[tbl_Dummy](
[Id] [varchar](2) NOT NULL,
[Name] [varchar](30) NOT NULL
)
if Max value of Id in tbl_dummy is 01, then it returns 02, and so on, when it passes to 09, then it returns 0A til 0Z, after that it will return 10 and so on.
I have done this, but this is not working in my scenerio
create FUNCTION dbo.GenerateValue ()
RETURNS VARCHAR(250)
AS BEGIN
DECLARE #counter int = 1;
DECLARE #Work VARCHAR(2)
DECLARE #temp VARCHAR(2)
DECLARE #tempW VARCHAR(2)
declare #value int
select #Work = MAX(id) from tbl_Dummy
WHILE #counter <= DATALENGTH(#Work)
BEGIN
SELECT #temp = ASCII(SUBSTRING(#Work, #counter, 1))
SET #counter = #counter + 1
if #temp >= '48' and #temp <= '56' or #temp >= '65' and #temp <= '89'
begin
select #value = CONVERT(INT, #temp)
set #value = #temp + 1
end
else if #temp = '57'
set #value = 'A'
else if #temp = '90'
set #tempW = '0'
set #tempW += CHAR(ASCII(SUBSTRING(#Work, #counter, 1)))
END
RETURN #work
END
Instead of getting the MAX(Id) every time, you should add an IDENTITY column in your table and a computed column to compute the correct Id.
CREATE TABLE tbl_Dummy(
TempId INT IDENTITY(1, 1),
Id AS ISNULL(dbo.GenerateValue(TempId),'') PERSISTED,
Name VARCHAR(30) NOT NULL
)
This way, once you insert a row in tbl_Dummy you don't always have to compute for the latest Id. The TempId will give that for you. As for how to compute the desired Id, here is one way without looping:
CREATE FUNCTION dbo.GenerateValue (#N INT)
RETURNS CHAR(2) WITH SCHEMABINDING AS
BEGIN
RETURN
(
SELECT returnStr =
SUBSTRING(Str1, (#N/36) % LEN(Str1) + 1, 1) + --LeftMost
SUBSTRING(Str1, (#N/1) % LEN(Str1) + 1, 1) --RightMost
FROM (
SELECT '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
) d(Str1)
);
Sample Usage:
INSERT INTO dbo.tbl_Dummy(Name)
SELECT TOP 20
SomethingElse = 'Random' + CONVERT(VARCHAR(10), ROW_NUMBER() OVER(ORDER BY (SELECT NULL)))
FROM sys.all_columns ac1
SELECT * FROM dbo.tbl_DUmmy
Result:
TempId Id Name
----------- ---- ------------------------------
1 01 Random1
2 02 Random2
3 03 Random3
4 04 Random4
5 05 Random5
6 06 Random6
7 07 Random7
8 08 Random8
9 09 Random9
10 0A Random10
11 0B Random11
12 0C Random12
13 0D Random13
14 0E Random14
15 0F Random15
16 0G Random16
17 0H Random17
18 0I Random18
19 0J Random19
20 0K Random20
Reference:
An answer by Jeff Moden on a similar problem:

Fill up column with random values from different table

I have 2 tables. Table Table_View has 5 columns and 200 rows
and Table_Random has 3 columns with 10 rows
I need to poulate column Table_View.A_random with values from
Table_Random.FixValues. If the query reaches the end of row in Table_Random i.e row 10, it should start again with
values from the top row i.e row 1. until it fills up the 200 rows.
Given that all tables has primary keys.
Any Ideas?
Thanks in advance
This will work for any count of rows in destination and source tables.
The idea is to calculate count of rows in random table and then assign number rn % #c to each row in destination table. And then update based on join:
DECLARE #count INT = 21
DECLARE #i INT = 1
DECLARE #c INT = 0
DECLARE #t TABLE ( ID INT, Random INT )
DECLARE #r TABLE ( ID INT, Random INT )
INSERT INTO #r
VALUES ( 1, 10 ),
( 3, 20 ),
( 4, 30 ),
( 6, 40 ),
( 8, 50 ),
( 11, 60 ),
( 14, 70 ),
( 17, 80 ),
( 19, 90 ),
( 21, 100 )
WHILE #i <= #count
BEGIN
INSERT INTO #t
VALUES ( #i, NULL )
SET #i = #i + 1
END;
SELECT #c = COUNT(*)
FROM #r;
WITH ctet1
AS ( SELECT * , ROW_NUMBER() OVER ( ORDER BY ID ) AS rn
FROM #t
),
ctet2
AS ( SELECT * ,
CASE WHEN rn % #c = 0 THEN #c
ELSE rn % #c
END AS rnn
FROM ctet1
),
cter
AS ( SELECT * , ROW_NUMBER() OVER ( ORDER BY ID ) AS rn
FROM #r
)
UPDATE ct
SET Random = cr.Random
FROM ctet2 ct
JOIN cter cr ON cr.rn = ct.rnn
SELECT * FROM #t
Output:
ID Random
1 10
2 20
3 30
4 40
5 50
6 60
7 70
8 80
9 90
10 100
11 10
12 20
13 30
14 40
15 50
16 60
17 70
18 80
19 90
20 100
21 10
If you didn't want cycle update then no need for views, functions and needless stuff. Just update:
UPDATE #t SET Random = (SELECT TOP 1 Random FROM #r ORDER BY NEWID())
yes you can make this out.
First of all you need to create and view which'll return a single value from Random Table(Table_Random) for every call.
Create View vMyRand as
Select top 1 val from myRand order by NewID();
then create a function to return value from created view.
CREATE FUNCTION GetMyRand ()
RETURNS varchar(5)
--WITH EXECUTE AS CALLER
AS
BEGIN
Declare #RetValue varchar(5)
--#configVar =
Select #RetValue = val from vmyRand
RETURN(#retValue)
END;
Fiddle Demo Here
Full Code:
create table tab_1
(
id bigint identity(1,1),
name varchar(50),
email varchar(50)
)
insert into tab_1(name,email) values
('a','a#mail.com'), ('b','c#mail.com'),
('a1','a1#mail.com'), ('a2','a2#mail.com'),
('a3','a3#mail.com'), ('a4','a4#mail.com'),
('b1','b1#mail.com'),('b2','b2#mail.com')
create table myRand(val varchar(50))
insert into myRand values('Q1654'),('F2597'),
('Y9405'),('B6735'),('D8732'),('C4893'),('I9732'),
('L1060'),('H6720');
Create View vMyRand as
Select top 1 val from myRand order by NewID();
CREATE FUNCTION GetMyRand ()
RETURNS varchar(5)
--WITH EXECUTE AS CALLER
AS
BEGIN
Declare #RetValue varchar(5)
--#configVar =
Select #RetValue = val from vmyRand
RETURN(#retValue)
END;
Update Code:
update tab_1 set name=(select dbo.getMyRand())
Hope This'll Help You.
Thanks. :)

Can I do without this cursor

I have a junction table which holds dependencies between items. I am using this to programmatically create a gantt chart showing these dependencies. I have a working stored procedure now, however my company's policy is to avoid cursors where possible. I put this to the gurus out there, is it possible to do this without the cursor?
DATA:
declare #BaseTable Table
(
[IssueDependencyId] [bigint] IDENTITY(1,1) NOT NULL,
[IssueId] [bigint] NOT NULL,
[DependsOnIssueId] [bigint] NOT NULL
)
INSERT INTO #BaseTable
SELECT
48, 0
UNION ALL SELECT
49, 48
UNION ALL SELECT
50, 48
UNION ALL SELECT
51, 48
UNION ALL SELECT
55, 48
UNION ALL SELECT
56, 48
UNION ALL SELECT
52, 49
UNION ALL SELECT
52, 50
UNION ALL SELECT
52, 51
UNION ALL SELECT
53, 52
UNION ALL SELECT
57, 54
UNION ALL SELECT
54, 55
UNION ALL SELECT
57, 56
SELECT * FROM #BaseTable
STORED PROC code:
DECLARE #IssueId int, #DependsOnIssueId int, #StartPoint int, #EndPoint int
SET #StartPoint = 0
SET #EndPoint = 10
DECLARE #ResultsTable TABLE (
IssueId int not null,
DependsOnIssueId int not null,
Start_Point int,
End_Point int
)
Select IssueId, DependsOnIssueId
INTO #tmp1
FROM IssueDependency
WHERE UpperLevelIssueId = 48
ORDER BY DependsOnIssueId
declare MyCursor Cursor for (Select IssueId, DependsOnIssueId from #tmp1);
OPEN MyCursor
FETCH NEXT FROM MyCursor
INTO #IssueId, #DependsOnIssueId
WHILE ##FETCH_STATUS = 0
BEGIN
--get parent position to set start
SELECT #StartPoint = ISNULL(End_Point, 0)
FROM #ResultsTable WHERE IssueId = #DependsOnIssueId
SET #EndPoint = #StartPoint + 10
INSERT INTO #ResultsTable VALUES
(#IssueId, #DependsOnIssueId, #StartPoint, #EndPoint)
FETCH NEXT FROM MyCursor
INTO #IssueId, #DependsOnIssueId
END
Close MyCursor
DEALLOCATE MyCursor;
SELECT IssueId,
MAX(start_point) max_start_point,
MAX(end_point) max_end_point
INTO #MaxPoints
from #ResultsTable
GROUP BY IssueId
SELECT r.IssueId,DependsOnIssueId,
max_start_point start_point,
max_end_point end_point
FROM #ResultsTable r
JOIN #MaxPoints m ON m.IssueId = r.IssueId
ORDER BY r.IssueId
RESULTING DATA
IssueId DependsOnIssueId Start_Point End_Point
--------------------------------------------------------------------
48 0 0 10
49 48 10 20
50 48 10 20
51 48 10 20
52 49 20 30
52 50 20 30
52 51 20 30
53 52 30 40
54 55 20 30
55 48 10 20
56 48 10 20
57 54 30 40
57 56 30 40
Your help much appreciated!!
Generally, many of the cursor T-SQL statements can be rewrite using Recursive Common Table Expressions Recursive CTE . You can also search for some articles about how performance
is better when you are using this technique.
In you case (this is full working example), the solution looks like this:
SET NOCOUNT ON
GO
DECLARE #DataSource TABLE
(
[IssueDependencyId] BIGINT IDENTITY(1,1) NOT NULL,
[IssueId] BIGINT NOT NULL,
[DependsOnIssueId] BIGINT NOT NULL
)
INSERT INTO #DataSource ( [IssueId], [DependsOnIssueId])
VALUES (48, 0)
,(49, 48)
,(50, 48)
,(51, 48)
,(55, 48)
,(56, 48)
,(52, 49)
,(52, 50)
,(52, 51)
,(53, 52)
,(57, 54)
,(54, 55)
,(57, 56)
;WITH DataSource ([IssueId], [DependsOnIssueId], [Start_Point], [End_Point]) AS
(
SELECT AnchorMebemr.[IssueId]
,AnchorMebemr.[DependsOnIssueId]
,0
,10
FROM #DataSource AS AnchorMebemr
WHERE AnchorMebemr.[IssueId] = 48
UNION ALL
SELECT RecursiveMebemer.[IssueId]
,RecursiveMebemer.[DependsOnIssueId]
,DS.[End_Point]
,DS.[End_Point] + 10
FROM #DataSource AS RecursiveMebemer
INNER JOIN DataSource DS
ON RecursiveMebemer.[DependsOnIssueId] = DS.[IssueId]
)
SELECT DISTINCT DS.[IssueId]
,DS.[DependsOnIssueId]
,DS.[Start_Point]
,DS.[End_Point]
FROM DataSource DS
ORDER BY DS.[IssueId]
,DS.[DependsOnIssueId]
SET NOCOUNT OFF
GO
The screenshot below displays the output after the execution of the T-SQL statements above:
Note: I have noticed that in your last row you might have syntax error (as I have understand the logic):
Anyway, If I have misunderstood something, I am sure you have got the idea.
I have not tested this. I am using a autoincrement column to loop through the temp1 table. Here it goes:
DECLARE #tmp1 table
(
_ID int identity (1,1) , -- will be used for looping
IssueId int not null,
DependsOnIssueId int not null
)
DECLARE #i as int
DECLARE #max as int
INSERT INTO #tmp1 (IssueId, DependsOnIssueId )
Select IssueId, DependsOnIssueId
FROM IssueDependency
WHERE UpperLevelIssueId = 48
ORDER BY DependsOnIssueId
SELECT #i = 1, #max = MAX(_ID) FROM #tmp1
WHILE #i <= #max
BEGIN
SELECT #IssueId = IssueId, #DependsOnIssueId = DependsOnIssueId
FROM #tmp1 WHERE _ID = #i
--get parent position to set start
SELECT #StartPoint = ISNULL(End_Point, 0)
FROM #ResultsTable WHERE IssueId = #DependsOnIssueId
SET #EndPoint = #StartPoint + 10
INSERT INTO #ResultsTable VALUES
(#IssueId, #DependsOnIssueId, #StartPoint, #EndPoint)
SET #i = #i + 1
END

Sorting VARCHAR column with alphanumeric entries

I am using SQL Server, the column is a VARCHAR(50) and I want to sort it like this:
1A
1B
2
2
3
4A
4B
4C
5A
5B
5C
5N
14 Draft
21
22A
22B
23A
23B
23C
23D
23E
25
26
FR01584
MISC
What I have so far is:
Select *
From viewASD
ORDER BY
Case When IsNumeric(LEFT(asdNumNew,1)) = 1
Then CASE When IsNumeric(asdNumNew) = 1
Then Right(Replicate('0',20) + asdNumNew + '0', 20)
Else Right(Replicate('0',20) + asdNumNew, 20)
END
When IsNumeric(LEFT(asdNumNew,1)) = 0
Then Left(asdNumNew + Replicate('',21), 20)
End
But this SQL statement puts '14 Draft' right after '26'.
Could someone help? Thanks
Your WHERE statement is... oddly complex.
It looks like you want to sort by any leading numeric digits in integer order, and then sort by the remainder. If so, you should do that as separate clauses, rather than trying to do it all in one. The specific issue you're having is that you're only allowing for a single-digit number, instead of two or more. (And there's No such thing as two.)
Here's your fix, along with a SQLFiddle, using two separate calculated columns tests for your ORDER BY. (Note that this assumes the numeric portion of asdNumNew will fit in a T-SQL int. If not, you'll need to adjust the CAST and the maximum value on the first ELSE.)
SELECT * FROM viewASD
ORDER BY
CASE
WHEN ISNUMERIC(asdNumNew)=1
THEN CAST(asdNumNew as int)
WHEN PATINDEX('%[^0-9]%',asdNumNew) > 1
THEN CAST(
LEFT(
asdNumNew,
PATINDEX('%[^0-9]%',asdNumNew) - 1
) as int)
ELSE 2147483648
END,
CASE
WHEN ISNUMERIC(asdNumNew)=1
THEN NULL
WHEN PATINDEX('%[^0-9]%',asdNumNew) > 1
THEN SUBSTRING(
asdNumNew,
PATINDEX('%[^0-9]%',asdNumNew) ,
50
)
ELSE asdNumNew
END
If all numbers within the string are reasonably small, say not exceeding 10 digits,
you may expand all the numbers in the string to be exactly 10 digits:
123A -> 0000000123A
S4 -> S0000000004
A3B89 -> A0000000003B0000000089
and so on and then sort them
-- Expand all numbers within S by zeros to be MaxLen
create function [dbo].ExpandNumbers(#S VarChar(4000), #maxlen integer) returns VarChar(4000)
as
begin
declare #result VarChar(4000);
declare #buffer VarChar(4000);
declare #Ch Char;
declare #i integer;
set #buffer = '';
set #result = '';
set #i = 1;
while (#i <= len(#S))
begin
set #Ch = substring(#S, #i, 1);
if ((#Ch >= '0') and (#Ch <= '9'))
set #buffer = #buffer + #Ch
else
begin
if (len(#buffer) > 0)
set #result = #result + right(replicate('0', #maxlen) + #buffer, #maxlen);
set #buffer = '';
set #result = #result + #Ch;
end;
set #i = #i + 1;
end;
if (len(#buffer) > 0)
set #result = #result + right(replicate('0', #maxlen) + #buffer, #maxlen);
return #result;
end;
-- Final query is
select *
from viewASD
order by [dbo].ExpandNumbers(asdNumNew)
I had something similar, but with the possibility of dashes as leading characters as well as trailing spaces. This code worked for me.
SELECT
my_column,
PATINDEX('%[^0-9]%',my_column) AS first_alpha_position,
CONVERT(INT,
CASE
WHEN PATINDEX('%[^0-9]%',my_column) = 0 OR PATINDEX('-%',my_column) = 1
THEN ABS(my_column)
ELSE SUBSTRING(my_column,1,PATINDEX('%[^0-9]%',my_column) -1)
END) AS numeric_value,
LTRIM(
SUBSTRING(my_column,PATINDEX('%[^0-9]%',my_column),LEN(my_column)-PATINDEX('%[^0-9]%',my_column)+1)
) AS alpha_chars
FROM my_table
ORDER BY numeric_value,alpha_chars
TRY THIS
DECLARE #t table (Number nvarchar(20))
INSERT INTO #t
SELECT 'L010'
UNION ALL SELECT 'L011'
UNION ALL SELECT 'L011'
UNION ALL SELECT 'L001'
UNION ALL SELECT 'L012'
UNION ALL SELECT '18'
UNION ALL SELECT '8'
UNION ALL SELECT '17'
UNION ALL SELECT 'B004'
UNION ALL SELECT 'B006'
UNION ALL SELECT 'B008'
UNION ALL SELECT 'B018'
UNION ALL SELECT 'UG001'
UNION ALL SELECT 'UG011'
UNION ALL SELECT 'G001'
UNION ALL SELECT 'G002'
UNION ALL SELECT 'G011';
SELECT Number
FROM #t
ORDER BY
CAST
(
SUBSTRING
(
Number
, 1
, CASE
WHEN patindex('%[^0-9]%',Number) > 0 THEN patindex('%[^0-9]%',Number) - 1
ELSE LEN(Number) END
) AS int
)
, Number
What worked for me is I split up the numeric and the alpha parts and then sorted based on the Alpha, then the Numeric:
CREATE FUNCTION [admin].[GetUnitNumberAsIntFunc](#UnitNumber varchar(20))
RETURNS int
BEGIN
DECLARE #intPosition int
SET #intPosition = PATINDEX('%[^0-9]%', #UnitNumber)
WHILE #intNumber > 0
BEGIN
SET #UnitNumber = STUFF(#UnitNumber, #intNumber, 1, '')
SET #intPosition = PATINDEX('%[^0-9]%', #UnitNumber)
END
RETURN ISNULL(#UnitNumber,9999)
END;
CREATE FUNCTION [admin].[GetUnitNumberAsStrFunc](#UnitNumber varchar(20))
RETURNS varchar(20)
BEGIN
DECLARE #intPosition int
SET #intPosition = PATINDEX('%[0-9]%', #UnitNumber)
SET #UnitNumber = STUFF(#UnitNumber, #intPosition, 6, '')
RETURN ISNULL(#UnitNumber,9999)
END;