I want to generate an ID in MSSQL Server 2008. Which will be Prefix + Numeric Number + suffix Like 'PV#000001#SV'. Which will be user defined (depends on configuration ) prefix, numeric length, suffix and starting number. Numeric number will be increased every time.
I tied to write this :
Blockquote
ALTER PROCEDURE [dbo].[spACC_SELECT_VOUCHER_NUMBER]
#COMPANY_ID uniqueidentifier,
#VOUCHER_TYPE INT
AS BEGIN
DECLARE #IS_AUTOMETIC BIT = (SELECT VOUCHER_CONFIG_NUMBERING_METHOD
FROM ACC_VOUCHER_CONFIG WHERE
ACC_VOUCHER_CONFIG.VOUCHER_CONFIG_VALUE=#VOUCHER_TYPE )
IF(#IS_AUTOMETIC=1)
BEGIN
SELECT CASE WHEN SUBSTRING(V.VOUCHER_CODE, 7, 23) IS NULL
THEN CASE WHEN VC.VOUCHER_CONFIG_PREFIX IS NULL THEN '' ELSE VC.VOUCHER_CONFIG_PREFIX END +
RIGHT ('0000000000000'+ CAST( VC.VOUCHER_CONFIG_BEGINING_NUMBER AS VARCHAR), VC.VOUCHER_CONFIG_NUMERIC_WIDTH) +
CASE WHEN VC.VOUCHER_CONFIG_SUFFIX IS NULL THEN '' ELSE VC.VOUCHER_CONFIG_SUFFIX END
ELSE CASE WHEN VC.VOUCHER_CONFIG_PREFIX IS NULL THEN '' ELSE VC.VOUCHER_CONFIG_PREFIX END +
RIGHT ('0000000000000'+ CAST((CAST( SUBSTRING(V.VOUCHER_CODE, 7, 23) AS INT)+1) AS VARCHAR), VC.VOUCHER_CONFIG_NUMERIC_WIDTH) +
CASE WHEN VC.VOUCHER_CONFIG_SUFFIX IS NULL THEN '' ELSE VC.VOUCHER_CONFIG_SUFFIX END
END AS VOUCHER_CODE FROM ACC_VOUCHER_CONFIG VC
LEFT OUTER JOIN ACC_VOUCHER V ON VC.VOUCHER_CONFIG_VALUE = V.VOUCHER_TYPE
WHERE VC.COMPANY_ID=#COMPANY_ID AND VC.VOUCHER_CONFIG_VALUE=#VOUCHER_TYPE
END
END
When I change the numeric length / suffix its not working.
Thanks
Nahid
For the six-digit number you're struggling with, add leading zeroes like this:
SELECT RIGHT('00000'+ CONVERT(VARCHAR,Num),6) AS NUM FROM your_table
Where Num is your sequential number.
This prepends 5 zeroes and then takes the right 6 characters from the resulting string.
A more detailed writeup of custom ID generation is here:
http://www.sqlteam.com/article/custom-auto-generated-sequences-with-sql-server
My suggestion would be to store just a number in the database (i.e. an int) and format the ID client side with tools that are better suited for it (i.e. a programming language that has sprintf or equivalent string formatting).
Related
How can I select only strings in the format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX where X is any number or any UPPERcase (not lowercase) character?
Example dataset:
ed54cb09-b402-4551-912d-b8e0fec69d9e --I do not want to select this one
00029B19-80CC-4FF8-BE11-BDB55FC7FC2A --I do want to select this one
Some are all-caps, some are not, and this is a varchar field. I want to select only these all-caps UUIDs, excluding the rest.
Currently I'm using the _ wildcard to look for the basic UUID format, but looks like UPPER doesn't apply to the _ wildcard. Ex.:
SELECT mycolumn
FROM mytable t
WHERE t.mycolumn like UPPER('________-____-____-____-____________') COLLATE SQL_Latin1_General_Cp1_CS_AS
As mentioned, you can first check its all uppercase by comparing upper of your value with itself using a case sensitive collation. And then use your wildcard pattern (or a more precise one as shown below) to confirm the format is correct.
select
-- Precise check on allowed characters
case when upper(X.Test) = X.Test collate Latin1_General_CS_AI
and X.Test like '[A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]-[A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]-[A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]-[A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]-[A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]' then 1 else 0 end
-- Loose check on allowed character, but definitely upper case
, case when upper(X.Test) = X.Test collate Latin1_General_CS_AI
and X.Test like '________-____-____-____-____________' then 1 else 0 end
-- Not sure why HABO deleted their answer, but the following works also
, case when upper(X.Test) = X.Test collate Latin1_General_CS_AI
and try_convert(uniqueidentifier,X.Test) is not null then 1 else 0 end
-- And combining Shmiel's and Charleface's suggestions gives
, case when X.Test like replicate('[A-F0-9]', 8) + '-' + replicate('[A-F0-9]', 4) + '-' + replicate('[A-F0-9]', 4) + '-' + replicate('[A-F0-9]', 4) + '-' + replicate('[A-F0-9]', 12) collate Latin1_General_100_BIN2 then 1 else 0 end
from (
values ('ed54cb09-b402-4551-912d-b8e0fec69d9e'), ('00029B19-80CC-4FF8-BE11-BDB55FC7FC2A')
) X (Test);
I have to echo Larnu's comment here, that it seems quite an odd requirement which might be better solved by a system change elsewhere.
Check if the string is following the correct format or not. The correct format is as follows:
2 upper case letters; 2 digits; 1 to 30 characters alpha-numerical (case insensitive)
e.g. GB29RBOS60161331926819,
GB29RBOS60161331926819A,
GB29RBOS60161331926819B1
So far this is what i have got...
declare #accountNumber varchar(1000) = 'GB99AERF12FDG8AERF12FDG8AERF12FDG8'
select
case when #accountNumber not like '[A-Z][A-Z][0-9][0-9][0-9a-zA-Z]{30}$'
then 'ERROR' else null end
First, your structure assumes a case sensitive collation. Second, SQL Server doesn't recognize {} or $, so you have to repeat the pattern. However, you want up to 30 characters, so splitting the pieces apart is probably the best solution:
select (case when len(#accountNumber) not between 5 and 34 or
#accountNumber not like '[A-Z][A-Z][0-9][0-9]%' or
right(#accountNumber, 34) like '%[^A-Za-z0-9]%'
then 'ERROR'
end)
I think this should work... taking some tips from John.
declare #table table (i varchar(36))
insert into #table
values
('GR09xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'), --30 x's
('GR09xxxxxxxxxxxxxxxxxxxxxxxxxxxx'), --28 x's
('GR09xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'), --31 x's
('Gx09xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'), --lower case 2'd letter
('G509xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'), --digit second letter
('GRg9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') --charcater first number (3rd index)
select
case
when i + replicate('a',case when 34-len(i) < 0 then 0 else 34-len(i) end) not like '[A-Z][A-Z][0-9][0-9]' + replicate('[a-zA-Z0-9]',30)
then 'ERROR' else null end
from #table
I had a SQL procedure that increments through each row and and pads some trailing zeros on values depending on the length of the value after a decimal point. Trying to carry this over to a PSQL environment I realized there was a lot of syntax differences between SQL and PSQL. I managed to make the conversion over time but I am still getting a syntax error and cant figure out why. Can someone help me figure out why this wont run? I am currently running it in PGadmin if that makes any difference.
DO $$
DECLARE
counter integer;
before decimal;
after decimal;
BEGIN
counter := 1;
WHILE counter <> 2 LOOP
before = (select code from table where ID = counter);
after = (SELECT SUBSTRING(code, CHARINDEX('.', code) + 1, LEN(code)) as Afterward from table where ID = counter);
IF before = after
THEN
update table set code = before + '.0000' where ID = counter;
ELSE
IF length(after) = 1 THEN
update table set code = before + '000' where ID = counter;
ELSE IF length(after) = 2 THEN
update table set code = before + '00' where ID = counter;
ELSE IF length(after) = 3 THEN
update table set code = before + '0' where ID = counter;
ELSE
select before;
END IF;
END IF;
counter := counter + 1;
END LOOP
END $$;
Some examples of the input/output of the intended result:
Input 55.5 > Output 55.5000
Input 55 > Output 55.0000
Thanks for your help,
Justin
There is no need for a function or even an update on the table to format values when displaying them.
Assuming the values are in fact numbers stored in a decimal or float column, all you need to do is to apply the to_char() function when retrieving them:
select to_char(code, 'FM999999990.0000')
from data;
This will output 55.5000 or 55.0000
The drawback of the to_char() function is that you need to anticipate the maximum number of digits of that can occur. If you have not enough 9 in the format mask, the output will be something like #.###. But as too many digits in the format mask don't hurt, I usually throw a lot into the format mask.
For more information on formatting functions, please see the manual: https://www.postgresql.org/docs/current/static/functions-formatting.html#FUNCTIONS-FORMATTING-NUMERIC-TABLE
If you insist on storing formatted data, you can use to_char() to update the table:
update the_table
set code = to_char(code::numeric, 'FM999999990.0000');
Casting the value to a number will of course fail if there a non-numeric values in the column.
But again: I strong recommend to store numbers as numbers, not as strings.
If you want to compare this to a user input, it's better to convert the user input to a proper number and compare that to the (number) values stored in the database.
The string matching that you are after doesn't actually require a function either. Using substring() with a regex will do that:
update the_table
set code = code || case length(coalesce(substring(code from '\.[0-9]*$'), ''))
when 4 then '0'
when 3 then '00'
when 2 then '000'
when 1 then '0000'
when 0 then '.0000'
else ''
end
where length(coalesce(substring(code from '\.[0-9]*$'), '')) < 5;
substring(code from '\.[0-9]*$') extracts everything the . followed by numbers that is at the end of the string. So for 55.0 it returns .0 for 55.50 it returns .50 if there is no . in the value, then it returns null that's why the coalesce is needed.
The length of that substring tells us how many digits are present. Depending on that we can then append the necessary number of zeros. The case can be shortened so that not all possible length have to be listed (but it's not simpler):
update the_table
set code = code || case length(coalesce(substring(code from '\.[0-9]*$'), ''))
when 0 then '.0000'
else lpad('0', 5- length(coalesce(substring(code from '\.[0-9]*$'), '')), '0')
end
where length(coalesce(substring(code from '\.[0-9]*$'), '')) < 5;
Another option is to use the position of the . inside the string to calculate the number of 0 that need to be added:
update the_table
set code =
code || case
when strpos(code, '.') = 0 then '0000'
else rpad('0', 4 - (length(code) - strpos(code, '.')), '0')
end
where length(code) - strpos(code, '.') < 4;
Regular expressions are quite expensive not using them will make this faster. The above will however only work if there is always at most one . in the value.
But if you can be sure that every value can be cast to a number, the to_char() method with a cast is definitely the most robust one.
To only process rows where the code columns contains correct numbers, you can use a where clause in the SQL statement:
where code ~ '^[0-9]+(\.[0-9][0-9]?)?$'
To change the column type to numeric:
alter table t alter column code type numeric
I have a database with a list of old ItemID's that need updating to a new format.
The old format is of the form 8046Y and the new format moves the 4th digit to the end and prepends a hyphen and adds a 0 if it's a single digit. The old format also uses alpha characters when the number goes over 9 for example 464HB where the H represents 17. I also need to add a 1 to the beginning of the new format. All this can be done with string manipulation in SQL I hope.
Some examples:
8046Y becomes 1804Y-06
464HB becomes 1464B-17 (H = 17)
Can anyone give me some pointers as to how to go about this in SQL?
I got as far as:
select '1' + LEFT(ItemID, 3) + RIGHT(ItemID,1) + '-' + '0' + SUBSTRING(ItemID,3,1) from items
But the conversion from a=10 to z=36 stumped me
Try this:
select
'1'
+ left(#str,3)
+ right(#str,1)
+ '-'
+ case
when substring(#str,4,1) like '%[0-9]%' 1 then right('00' + substring(#str,4,1),2)
else cast(10 + ascii(substring(#str,4,1))-ascii('A') as varchar(2))
end
Explanation: If the 4th character is a digit, then do not change the value and append it after padding with a zero. Otherwise, use ASCII to get the ASCII value for that character, get it's difference with the value for 'A' and add the offset of 10.
Demo
Since I don't know if there are any other special character to consider except 'H' only included this single character.
DECLARE #val CHAR(5) = '464HB'
SELECT #val, '1' + LEFT(#val,3)+SUBSTRING(#val,5,1)+'-'+CASE WHEN +SUBSTRING(#val,4,1)='H' THEN '17' ELSE '0'+SUBSTRING(#val,4,1) END
select '1' + LEFT(ItemID, 3) + RIGHT(ItemID,1) + '-'
+CASE RIGHT(LEFT(ItemID,1),2)
WHEN 'a' then 10
WHEN 'b' THEN 11
etc...
END [NewItemID]
from items
Just add the appropriate cases in that format.
I do it only for the challenge, i dont recommend to use
DECLARE #id varchar(5) = '8046Y'
--SET #id = '464HB'
SELECT
'1' +
LEFT(#id, 3) +
RIGHT(#id, 1) +
'-' +
CASE WHEN ISNUMERIC(RIGHT(LEFT(#id, 4), 1)) = 1 THEN
RIGHT(LEFT(#id, 4), 1)
ELSE
RIGHT('00' + CONVERT(VARCHAR, ASCII(RIGHT(LEFT(#id, 4), 1)) - 64 + 9), 2)
END
-64 for the Start of ASCII A and +9 for your convention
Personally, I'd create a function for it.
Create a variable to handle the new value.
Manipulate the positions through SUBSTRING, you can also use RIGHT or LEFT
When adding zeros in single digit numbers, just do conditional statement
Regarding the conversion of letters to numbers(e.g. letter H), the converted value of the first letter is 65 assuming it's all capital. So, A=65, B=66, H=72 and so on. Use this data in manipulating the values. Uhm, I'll give you my thoughts but you can optimize it (since I don't have lots of time).
Taking your example of H=17, so A=10. Just subtract 55 in the conversions. So H=72(-55) becomes 17. This is applicable to all letters (in uppercase only).
I think this much is more than enough to guide you. Hope this would help.
In the code below I am inserting values into a table and getting the error "String or binary data would be truncated."
My table definition:
CREATE TABLE urs_prem_feed_out_control
(
bd_pr_cntl_rec_type char(7) NULL ,
pd_pr_cntl_acctg_dte char(6) NULL ,
bd_pr_cntl_run_dte char(10) NULL ,
bd_pr_cntl_start_dte char(10) NULL ,
bd_pr_cntl_end_dte char(10) NULL ,
bd_pr_cntl_rec_count char(16) NULL ,
bd_pr_tot_premium char(16) NULL ,
bd_pr_tot_commission char(16) NULL ,
fd_ctl_nbr integer NOT NULL
)
DECLARE #cur_fd_ctl_nbr INT = 2,
#acctg_cyc_ym_2 CHAR(6) = '201402',
#rundate CHAR (10) = CONVERT(CHAR(10),GETDATE(),101),
#cycle_start_dt DATETIME = '2014-02-17',
#cycle_end_dt DATETIME = '2014-02-24',
#record_count INT = 24704,
#tot_pr_premium DECIMAL(18,2) = 476922242,
#tot_pr_comm DECIMAL(18,2) = 2624209257
Insert code (I've declared the variables as constant values for testing, I took these values from what they were at runtime):
INSERT INTO urs_prem_feed_out_control
SELECT fd_ctl_nbr = #cur_fd_ctl_nbr,
bd_pr_cntl_rec_type = 'CONTROL',
bd_pr_cntl_acctg_dte = #acctg_cyc_ym_2,
bd_pr_cntl_run_dte = #rundate,
bd_pr_cntl_start_dte = CONVERT(CHAR(10),#cycle_start_dt,101),
bd_pr_cntl_end_dte = CONVERT(CHAR(10),#cycle_end_dt,101),
bd_pr_cntl_rec_count = RIGHT('0000000000000000' + RTRIM(CONVERT(CHAR(16),#record_count)),16),
bd_pr_tot_premium = CASE
WHEN #tot_pr_premium < 0
THEN '-' + SUBSTRING(RIGHT('000000000000000' + LTRIM(RTRIM(CONVERT(VARCHAR,ABS(#tot_pr_premium)*100))),18),1,15)
ELSE
'+' + SUBSTRING(RIGHT('000000000000000' + LTRIM(RTRIM(CONVERT(VARCHAR,ABS(#tot_pr_premium)*100))),18),1,15)
END,
bd_pr_tot_commission = CASE
WHEN #tot_pr_comm < 0
THEN '-' + SUBSTRING(RIGHT('000000000000000' + LTRIM(RTRIM(CONVERT(VARCHAR,ABS(#tot_pr_comm)*100))),18),1,15)
ELSE
'+' + SUBSTRING(RIGHT('000000000000000' + LTRIM(RTRIM(CONVERT(VARCHAR,ABS(#tot_pr_comm)*100))),18),1,15)
END
When I look at each value individually it seems like they are all within the variable length constraints of the table. Any idea why I'm getting this error?
Thanks!
The problem with your insert query is THE ORDER OF INSERTION :
SELECT fd_ctl_nbr = #cur_fd_ctl_nbr,
This column must be defined at the last in the INSERT as its the last column defined in the create table script.
Change your query to this:
INSERT INTO #urs_prem_feed_out_control (fd_ctl_nbr, bd_pr_cntl_rec_type, pd_pr_cntl_acctg_dte, bd_pr_cntl_run_dte, bd_pr_cntl_start_dte, bd_pr_cntl_end_dte, bd_pr_cntl_rec_count, bd_pr_tot_premium, bd_pr_tot_commission)
SELECT fd_ctl_nbr = #cur_fd_ctl_nbr,
bd_pr_cntl_rec_type = 'CONTROL',
bd_pr_cntl_acctg_dte = #acctg_cyc_ym_2,
bd_pr_cntl_run_dte = #rundate,
bd_pr_cntl_start_dte = CONVERT(CHAR(10),#cycle_start_dt,101),
bd_pr_cntl_end_dte = CONVERT(CHAR(10),#cycle_end_dt,101),
bd_pr_cntl_rec_count = RIGHT('0000000000000000' + RTRIM(CONVERT(CHAR(16),#record_count)),16),
bd_pr_tot_premium = CASE
WHEN #tot_pr_premium < 0
THEN '-' + SUBSTRING(RIGHT('000000000000000' + LTRIM(RTRIM(CONVERT(VARCHAR,ABS(#tot_pr_premium)*100))),18),1,15)
ELSE
'+' + SUBSTRING(RIGHT('000000000000000' + LTRIM(RTRIM(CONVERT(VARCHAR,ABS(#tot_pr_premium)*100))),18),1,15)
END,
bd_pr_tot_commission = CASE
WHEN #tot_pr_comm < 0
THEN '-' + SUBSTRING(RIGHT('000000000000000' + LTRIM(RTRIM(CONVERT(VARCHAR,ABS(#tot_pr_comm)*100))),18),1,15)
ELSE
'+' + SUBSTRING(RIGHT('000000000000000' + LTRIM(RTRIM(CONVERT(VARCHAR,ABS(#tot_pr_comm)*100))),18),1,15)
END
Doing this would also work. Notice that the first column here in the SELECT within the INSERT is just the way you have provided in your question.
See this here-> http://sqlfiddle.com/#!3/0e09b/1
Hope this helps!!!
This is why you should never write an insert statement without specifying the columns. Since you did not, it wil try to put the data in the columns in the order they are in the table which is not at all the order you have them in.
Another thing that can happen when you get this sort of message (but which I don't think applies in your case, I include it for people searching later) is that the eeror is actually coming from a trigger and not the main insert.
Finally a note on database design, you should not be using char for dates, you should be using date fields. You cannot do date math on a char field and it will accept incorrect date values like feb30, 2014. It is always a bad idea to store dates as anything except date or datetime values. In general char should only be used rarely when a column will always have the same number of characters (like a 2 column state abbreviation), it should not be used as the default datatype. You need to do a better job of defining datatypes that match the type and size of data being stored. You can run into problems with queries as 'VA' is not the same thing as 'VA '. In general my experience is that less than 1 % of all database fields should be Char.
I think it may be beneficial to double check your work. Consider the following example code:
DECLARE #Table TABLE (Name NVARCHAR(1))
INSERT INTO #Table (Name) SELECT 'ab'
INSERT INTO #Table (Name) SELECT SUBSTRING('ab',1,2)
INSERT INTO #Table (Name) SELECT RIGHT('abc',2)
All yield the following: String or binary data would be truncated.