Unique alphanumeric key generation in MSSQL - sql

I have table with char(4) field which is used as key. Value for that is generated (max + 1). But not it has reached to it's max(9999). Cannot change the data type of the column since that is used it so many places.
I came up with a solution like below...
9999 -> A000...A999->B000...B999->Z999 -> ZA00->ZZZZ (zzzz will be OK for another 10 years)
Please tell me is there an easy way to generate this other than doing substring() and string manipulation ?
Current Code :
DECLARE #cle smallint
SELECT #cle = isnull(MAX(CONVERT(smallint,RTRIM(cle))),0) from smnf
INSERT INTO smnf (sup, cle, dsc, exc, dirty) VALUES (#sup, CONVERT(char(4), #cle+1), #dsc, 0, 'A')

SELECT i INTO #t FROM (SELECT '0' i UNION ALL SELECT '1' UNION ALL SELECT '2' UNION ALL SELECT '3' UNION ALL SELECT '4' UNION ALL SELECT '5' UNION ALL SELECT '6' UNION ALL SELECT '7' UNION ALL SELECT '8' UNION ALL SELECT '9' UNION ALL SELECT 'A' UNION ALL SELECT 'B' UNION ALL SELECT 'C' UNION ALL SELECT 'D' UNION ALL SELECT 'E' UNION ALL SELECT 'F' UNION ALL SELECT 'G' UNION ALL SELECT 'H' UNION ALL SELECT 'I' UNION ALL SELECT 'J' UNION ALL SELECT 'K' UNION ALL SELECT 'L' UNION ALL SELECT 'M' UNION ALL SELECT 'N' UNION ALL SELECT 'O' UNION ALL SELECT 'P' UNION ALL SELECT 'Q' UNION ALL SELECT 'R' UNION ALL SELECT 'S' UNION ALL SELECT 'T' UNION ALL SELECT 'U' UNION ALL SELECT 'V' UNION ALL SELECT 'W' UNION ALL SELECT 'X' UNION ALL SELECT 'Y' UNION ALL SELECT 'Z') t
CREATE TABLE id_list (id char(4) PRIMARY KEY);
INSERT id_list
SELECT c1.i+c2.i+c3.i+c4.i
FROM #t c1, #t c2, #t c3, #t c4
WHERE c1.i+c2.i+c3.i+c4.i NOT LIKE '[0-9]%[A-Z]%' --Eliminate alphas in the first 10000 to preserve existing ids.
When you need the next id:
DECLARE #curr_id char(4) = '9ZZZ'
DECLARE #next_id char(4)
SELECT TOP 1 #next_id = [id] FROM id_list WHERE [id] > #curr_id
PRINT #next_id
A000

Related

Dynamic Column Name for a Derived Column - ORACLE SQL

I'm trying to set a dynamic column name on my query using "select from dual".
Is this possible? If not, kindly recommend alternatives for me to achieve this.
I need this on a normal select query, not by using a stored procedure.
I'm trying to achieve the query below:
SELECT A.NO
,A.SUB_NO
,A.DCY
,A.STATE
,NVL(TO_CHAR(M1.NUMERATOR),'0') AUG_NUM --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-2),'MON')||'_NUM' FROM DUAL
,NVL(TO_CHAR(M1.DENOMINATOR),'0') AUG_DEN --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-2),'MON')||'_DEN' FROM DUAL
,NVL(TO_CHAR(M2.NUMERATOR),'0') JUL_NUM --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-3),'MON')||'_NUM' FROM DUAL
,NVL(TO_CHAR(M2.DENOMINATOR),'0') JUL_DEN --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-3),'MON')||'_DEN' FROM DUAL
,NVL(TO_CHAR(M3.NUMERATOR),'0') JUN_NUM --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-4),'MON')||'_NUM' FROM DUAL
,NVL(TO_CHAR(M3.DENOMINATOR),'0') JUN_DEN --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-4),'MON')||'_DEN' FROM DUAL
,M1.M1_CALC
,M2.M2_CALC
,M3.M3_CALC
FROM A, M1,M2,M3;
Thank you in advance for your help.
I'm pretty sure the closest you're going to get to what you want is by using a UNION ALL.
SELECT NULL no,
NULL sub_no,
NULL dcy,
NULL state,
TO_CHAR(ADD_MONTHS(SYSDATE,-2),'MON')||'_NUM' AUG_NUM,
TO_CHAR(ADD_MONTHS(SYSDATE,-2),'MON')||'_DEN' AUG_DEN,
TO_CHAR(ADD_MONTHS(SYSDATE,-3),'MON')||'_NUM' JUL_NUM,
TO_CHAR(ADD_MONTHS(SYSDATE,-3),'MON')||'_DEN' JUL_DEN,
TO_CHAR(ADD_MONTHS(SYSDATE,-4),'MON')||'_NUM' JUN_NUM,
TO_CHAR(ADD_MONTHS(SYSDATE,-4),'MON')||'_DEN' JUN_DEN,
NULL M1_CAL,
NULL M2_CALC,
NULL M3_CALC
FROM dual
UNION ALL
SELECT A.NO
,A.SUB_NO
,A.DCY
,A.STATE
,NVL(TO_CHAR(M1.NUMERATOR),'0') AUG_NUM --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-2),'MON')||'_NUM' FROM DUAL
,NVL(TO_CHAR(M1.DENOMINATOR),'0') AUG_DEN --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-2),'MON')||'_DEN' FROM DUAL
,NVL(TO_CHAR(M2.NUMERATOR),'0') JUL_NUM --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-3),'MON')||'_NUM' FROM DUAL
,NVL(TO_CHAR(M2.DENOMINATOR),'0') JUL_DEN --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-3),'MON')||'_DEN' FROM DUAL
,NVL(TO_CHAR(M3.NUMERATOR),'0') JUN_NUM --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-4),'MON')||'_NUM' FROM DUAL
,NVL(TO_CHAR(M3.DENOMINATOR),'0') JUN_DEN --AS SELECT TO_CHAR(ADD_MONTHS(SYSDATE,-4),'MON')||'_DEN' FROM DUAL
,M1.M1_CALC
,M2.M2_CALC
,M3.M3_CALC
FROM A, M1,M2,M3;
WITH A AS
(
SELECT
636 "NO", 159 "SUB_NO", To_Char(SYSDATE, 'yyyy') "DCY", 'State 1' "STATE"
FROM dual --This could be records from any of your data tables
UNION
SELECT
1272 "NO", 318 "SUB_NO", To_Char(SYSDATE, 'yyyy') "DCY", 'State 2' "STATE"
FROM dual --This could be records from any of your data tables
)
SELECT
NO, SUB_NO, DCY, STATE,
MONTH_MINUS_2_NUM, MONTH_MINUS_2_DEN,
MONTH_MINUS_3_NUM, MONTH_MINUS_3_DEN,
MONTH_MINUS_4_NUM, MONTH_MINUS_4_DEN,
M1_CALC, M2_CALC, M3_CALC
FROM
a
MODEL
DIMENSION BY( DCY, STATE)
MEASURES( NO, SUB_NO,
CAST('x' as VarChar2(20)) as MONTH_MINUS_2_NUM,
CAST('x' as VarChar2(20)) as MONTH_MINUS_2_DEN,
CAST('x' as VarChar2(20)) as MONTH_MINUS_3_NUM,
CAST('x' as VarChar2(20)) as MONTH_MINUS_3_DEN,
CAST('x' as VarChar2(20)) as MONTH_MINUS_4_NUM,
CAST('x' as VarChar2(20)) as MONTH_MINUS_4_DEN,
0 as M1_CALC,
0 as M2_CALC,
0 as M3_CALC
)
RULES
(
MONTH_MINUS_2_NUM[ANY, ANY] = TO_CHAR(ADD_MONTHS(SYSDATE,-2),'MON')||'_NUM',
MONTH_MINUS_2_DEN[ANY, ANY] = TO_CHAR(ADD_MONTHS(SYSDATE,-2),'MON')||'_DEN',
MONTH_MINUS_3_NUM[ANY, ANY] = TO_CHAR(ADD_MONTHS(SYSDATE,-3),'MON')||'_NUM',
MONTH_MINUS_3_DEN[ANY, ANY] = TO_CHAR(ADD_MONTHS(SYSDATE,-3),'MON')||'_DEN',
MONTH_MINUS_4_NUM[ANY, ANY] = TO_CHAR(ADD_MONTHS(SYSDATE,-4),'MON')||'_NUM',
MONTH_MINUS_4_DEN[ANY, ANY] = TO_CHAR(ADD_MONTHS(SYSDATE,-4),'MON')||'_DEN',
-- Dynamic Calculations -> you can calculate prety much anything you want
M1_CALC[2022, 'State 1'] = NO[CV(), CV()] / 2,
M2_CALC[2022, 'State 1'] = NO[CV(), CV()] / 3,
M3_CALC[2022, 'State 1'] = NO[CV(), CV()] / 4,
M1_CALC[2022, 'State 2'] = NO[CV(), CV()] / 2,
M2_CALC[2022, 'State 2'] = NO[CV(), CV()] / 3,
M3_CALC[2022, 'State 2'] = NO[CV(), CV()] / 4
)

How to use select with a variable

To simplify I have a table and I just want to make a select with the data at blue, is tricky for me because I must use the where textanswer='dimibilli d2' but I will only get the records 5, 7, 10, 12 but I want to get the 6, 8, 9 to
How can I do this?
select rows with required text column and match corresponding Id's to get required output.
declare #table table (transdocnumber int, textanswer varchar(20))
insert #table (transdocnumber,textanswer)
select 4631, 'Dimibilli D4' union all
select 4631, '' union all
select 5055, 'Dimibilli D2' union all
select 5055, '' union all
select 5270, 'Dimibilli D2' union all
select 5270, '' union all
select 5270, '' union all
select 5513, 'Dimibilli D2' union all
select 6279, 'Dimibilli D4' union all
select 6616, 'Dimibilli D2' union all
select 6773, 'Dimibilli D4'
select t.transdocnumber,t.textanswer from (
select transdocnumber from #table where textanswer = 'Dimibilli D2' ) x
inner join #table t on x.transdocnumber = t.transdocnumber where t.textanswer = ''
union all
select transdocnumber,textanswer from #table where textanswer = 'Dimibilli D2'
order by transdocnumber
Solution:
SELECT t.transdocnumber, t.extrafield_id, t.textanswer, t.BooleanAnswer, t.DtAlt
FROM (
SELECT *
FROM TABLE
WHERE textanswer = 'Dimibilli D2'
) x
INNER JOIN TABLE t ON x.transdocnumber = t.transdocnumber
WHERE t.textanswer = ''
UNION ALL
SELECT transdocnumber, extrafield_id, textanswer, BooleanAnswer, DtAlt
FROM TABLE
WHERE textanswer = 'Dimibilli D2'
ORDER BY transdocnumber
You seem to want:
select t.*
from t
where t.tansdocnumber in (select t2.transdocnumber from t t2 where t2.textanswer = 'Dimobili D2);
select * from table
where textanswer not in ('dimobilli d4')
for what I have understood you want to get back rows with textanswer='dimibilli d2' and textanswer='dimibilli d2' but not row 4.
You can't do that in this way, you should differentiate the row that you want in some way.
using
select * from table
where (textanswer='dimibilli d2' or textanswer = '')
as Ven suggested you will have line 4,5,6,7,8,9,10,12 as result, but it seems that you want only 5,6,7,8,9,10,12
for example you can use:
select * from table
where (textanswer='dimibilli d2' or textanswer = '') and TransDocNumber>5000;
this should work
Try to use the below query:
select * from table
where (TextAnswer='Dimobili D2' or BooleanAnswer = '1') and TextAnswer!=''

Print result by merging records in a table

I have a table with name "PrintWord" and column name as col_letter and data in it is as follows:
"col_letter"
S
A
C
H
I
N
I would like to print the o/p from this table as:
SACHIN
Thanks!
DECLARE #t table
(
Name varchar(10)
)
INSERT INTO #t
SELECT 's' UNION ALL
SELECT 'a' UNION ALL
SELECT 'c' UNION ALL
SELECT 'h' UNION ALL
SELECT 'i' UNION ALL
SELECT 'n'
SELECT DISTINCT
stuff(
(
SELECT ' '+ [Name] FROM #t FOR XML PATH('')
),1,1,'')
FROM (SELECT DISTINCT Name FROM #t ) t
There is a hard-coded version :
SELECT col_letter
FROM PrintWord
ORDER BY
CASE col_letter
WHEN 'S' THEN 1
WHEN 'A' THEN 2
WHEN 'C' THEN 3
WHEN 'H' THEN 4
WHEN 'I' THEN 5
WHEN 'N' THEN 6
END
FOR XML PATH('')
You need an ORDER BY clause to guarantee the order of the letters.

SQL to generate next sequence in an alphanumeric id

I gained some help from this question, but still need some further assistance.
I need to be able to generate the next available 2-digit alphanumeric code. I cannot change the table definition, before you ask. I am working in T-SQL.
So, for example, let's say I have the sequence
00, 01, 02,..., 09, 0A, 0B, 0C,..., 0Y, 0Z, 10, 11,...1Y, 1Z, 20, 21,..., 9Y, 9Z, I would like for the next id to be A0,
then A1, A2, ..., A9, AA, AB, AC, ..., AZ, I would like for the next id to be B0, then B1, etc.
So, in short, I would like to go from 00 all the way to ZZ and each time look for the MAX in that field and assign a new code 1 greater than the max. I would understand that A > 9, and the first column greater than the second, so A0 > 99 and AA > A9.
I wish I could just assign a numeric id to all of this, but the table definition is more critical at this point and so I'm not allowed to change it, so I am trying to maximize the available ids I'll have in such a limited space.
Thank you for your help.
Have a look at this. This is a really nasty problem for ID's. You've effectively limited yourself a low number of permutations of the key with 2 characters. Also you have a problem that you'll need to deal with if ZZ is used and this algorithm runs again. I have expanded these into as logical steps as possible for demonstration, but feel free to condense as needed.
DECLARE #ExistingTable TABLE (ID CHAR(2))
INSERT INTO #ExistingTable (ID) VALUES ('5A'),('5B')
DECLARE #NewID CHAR(2)
;WITH
Ranks AS (
SELECT '0' AS [Character] UNION SELECT '1' AS [Character] UNION SELECT '2' UNION SELECT '3' UNION SELECT '4' UNION SELECT '5' UNION SELECT '6' UNION
SELECT '7' UNION SELECT '8' UNION SELECT '9' UNION SELECT 'A' UNION SELECT 'B'UNION
SELECT 'C' UNION SELECT 'D' UNION SELECT 'E' UNION SELECT 'F' UNION SELECT 'G' UNION SELECT 'H' UNION
SELECT 'I' UNION SELECT 'J' UNION SELECT 'K' UNION SELECT 'L' UNION SELECT 'M' UNION SELECT 'N' UNION
SELECT 'O' UNION SELECT 'P' UNION SELECT 'Q' UNION SELECT 'R' UNION SELECT 'S' UNION SELECT 'T' UNION
SELECT 'U' UNION SELECT 'V' UNION SELECT 'W' UNION SELECT 'X' UNION SELECT 'Y' UNION SELECT 'Z'
), Permutations AS (
SELECT SecondChar.[Character] + FirstChar.[Character] AS PermuteID
FROM Ranks AS FirstChar
CROSS JOIN Ranks AS SecondChar
), PermutationsKeyed AS (
SELECT ROW_NUMBER() OVER (ORDER BY PermuteID ASC) AS PrimaryKeyHolder,
PermuteID
FROM Permutations
), MaxPK AS (
SELECT MAX(Perm.PrimaryKeyHolder) + 1 AS MaxPK
FROM #ExistingTable AS E
INNER JOIN PermutationsKeyed AS Perm ON (E.ID = Perm.PermuteID)
)
SELECT #NewID = Perm.PermuteID
FROM PermutationsKeyed AS Perm
INNER JOIN MaxPK AS M ON (Perm.PrimaryKeyHolder = M.MaxPK)
SELECT #NewID
I'm not sure how you wanted to go about returning the next value but I think this a simple and efficient ways to get all your values. Let me know if you need anything else.
DECLARE #values TABLE (val CHAR(1));
DECLARE #int INT = 48,
#letters INT = 65;
IF OBJECT_ID('dbo.tbl_keys') IS NOT NULL
DROP TABLE dbo.tbl_keys;
--This will hold the values so you can always reference them
CREATE TABLE dbo.tbl_Keys
(
--Primary key will create a clustered index on rank_id by default
rank_id INT PRIMARY KEY,
ID_Code CHAR(2)
);
--Another index on ID_Code
CREATE NONCLUSTERED INDEX idx_ID_Code ON tbl_keys(ID_Code);
--This is how I get all your individual values
WHILE (SELECT COUNT(*) FROM #values) < 36
BEGIN
IF(#int <= 57)
INSERT INTO #values VALUES(CHAR(#int));
INSERT INTO #values
VALUES (CHAR(#letters))
SET #int = #int + 1;
SET #letters = #letters + 1;
END
--Insert all possible combinations and rank them
INSERT INTO tbl_Keys
--ASCII is your best friend. It returns the ASCII code(numeric value) for characters
SELECT ROW_NUMBER() OVER (ORDER BY ASCII(A.val),ASCII(B.val)) AS rank_id,
A.val + B.val ID
FROM #values A
CROSS JOIN #values B;
I provide two different ways of getting the next ID_code(Read comments):
--Here's some dummy data
WITH CTE_DummyTable
AS
(
SELECT '00' ID_Code
UNION ALL
SELECT '01'
UNION ALL
SELECT '02'
)
----Here's how to get the next value with the assumption there are no gaps in your data
--SELECT MIN(ID_Code) next_id_code
--FROM tbl_Keys
--WHERE ID_code > (SELECT MAX(id_code) FROM CTE_DummyTable)
--This one doesn't assume the gaps and returns the lowest available ID_code
SELECT MIN(ID_Code) next_id_code
FROM tbl_Keys
WHERE ID_code NOT IN (SELECT DISTINCT id_code FROM CTE_DummyTable)
Note: If you were ever to want to convert your alphanumeric values really easily for whatever reason without changing the rank try this.
SELECT rank_id,
ID_code,
CAST(CONCAT(ASCII(LEFT(id_code,1)),ASCII(RIGHT(id_code,1))) AS INT) AS numeric_id_code
FROM tbl_Keys

create a list of the alphabet via SQL

I would like to produce results from the alphabet via SQL?
Something like this:
A
B
C
D
E
F
I have tried:
SELECT
'A','B','C'
But this just produces the letters across in columns.
Use table spt_values and convert values to chars
SELECT Char(number+65)
FROM master.dbo.spt_values
WHERE name IS NULL AND
number < 26
EDIT: This table is undocumented. But, it's used by many system storedprocedures and it's extremely unlikely for this table to disappear, since all those procs should be rewritten. This would be like poking a sleeping lion.
--
-- tally = 9466 rows in my db, select upper & lower alphas
--
;
with
cte_tally as
(
select row_number() over (order by (select 1)) as n
from sys.all_columns
)
select
char(n) as alpha
from
cte_tally
where
(n > 64 and n < 91) or
(n > 96 and n < 123);
go
The sys.all_columns is a documented table. It will be around for a while.
http://technet.microsoft.com/en-us/library/ms177522.aspx
It seems clear that the table, sp_values, is undocumented and can be removed in the future without any comment from Microsoft.
Try:
select 'A' union
select 'B' union
select 'C'
If you want to print from A to Z, then:
DECLARE #i int=65
WHILE #i < 91
BEGIN
PRINT CHAR(#i);
SET #i=#i+1;
END
You could use U-SQL
Select
[letter]
From
(
Values
('A')
,('B')
,('C')
) As [Letters]([letter])
with AlphabetList as
(
select char(65) letter
union all
select char(ascii(letter) + 1)
from AlphabetList
where letter <> 'Z'
)
select *
from AlphabetList
Using a recursive CTE (common table expression) to output the alphabet, A-Z, one row per letter/character:
;WITH cteAZ AS
(
SELECT ASCII('A') [AlphaCode],CAST('A' AS CHAR(1)) [Alpha]
UNION ALL
SELECT a.AlphaCode + 1 [AlphaCode],CAST(CHAR(a.AlphaCode + 1) AS CHAR(1)) [Alpha]
FROM cteAZ a WHERE a.AlphaCode < ASCII('Z')
)
SELECT
az.AlphaCode,az.Alpha
FROM
cteAZ az
Try this
;WITH CHARA2Z
AS (
SELECT
[ASCII] = ASCII('A'),
[LETTER] = CHAR(ASCII('A'))
UNION ALL
SELECT
[ASCII] + 1,
[LETTER] = CHAR([ASCII]+1)
FROM
CHARA2Z
WHERE
[ASCII] < ASCII('Z')
)
SELECT * FROM CHARA2Z
Replace 'A' & 'Z' by 'a' & 'z' for small letters.
Stemming from #MarkoJuvančič's answer, but a solution that will work on every SQL DBMS:
CREATE TEMPORARY TABLE alphabet (ltr CHAR(1));
SET #row_number = 0;
INSERT INTO alphabet
SELECT
CHAR((#row_number:=#row_number + 1) +64) -- 'A' is the 65th character on the ASCII table
FROM customer -- any table with 26 or more rows could suffice for this job
WHERE #row_number < 26;
SELECT 'A' letter
UNION ALL
SELECT 'B' letter
UNION ALL
SELECT 'C' letter
UNION ALL
SELECT 'D' letter
UNION ALL
SELECT 'E' letter
UNION ALL
SELECT 'F' letter
UNION ALL
SELECT 'G' letter
UNION ALL
SELECT 'H' letter
UNION ALL
SELECT 'I' letter
UNION ALL
SELECT 'J' letter
UNION ALL
SELECT 'K' letter
UNION ALL
SELECT 'L' letter
UNION ALL
SELECT 'M' letter
UNION ALL
SELECT 'N' letter
UNION ALL
SELECT 'O' letter
UNION ALL
SELECT 'P' letter
UNION ALL
SELECT 'Q' letter
UNION ALL
SELECT 'R' letter
UNION ALL
SELECT 'S' letter
UNION ALL
SELECT 'T' letter
UNION ALL
SELECT 'U' letter
UNION ALL
SELECT 'V' letter
UNION ALL
SELECT 'W' letter
UNION ALL
SELECT 'X' letter
UNION ALL
SELECT 'Y' letter
UNION ALL
SELECT 'Z' letter;