I have an auto-increment transactionID type=MEDIUMINT(9) in my table. I want to also display a unique 4-character (which can grow over time, but 4 for now) alphabetical Redemption Code to my users. What is the best way to derive this alphabetical code from my transactionID, preferably straight from the SELECT statement?
That mostly depends on what alphabet you want to use.
You may use TO_BASE64 to convert it it to base64 encoded string or simply do something like:
select REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(your_number, '0', 'A')
, '1', 'B')
, '2', 'C')
, '3', 'D')
, '4', 'E')
, '5', 'F')
, '6', 'G')
, '7', 'H')
, '8', 'I')
, '9', 'J')
if you want custom alphabet.
In case you want something shorter, you can go a slightly harder way:
You use 9-digit decimal (maximum 999999999), which translates to 8 hex digits (0x3B9AC9FF), i.e. 4 bytes. What you can do is divide your number in 4 binary octets, convert them to chars, construct new string and feed it to TO_BASE64():
select TO_BASE64(CONCAT(CHAR(FLOOR(your_number/(256*256*256))%256),CHAR(FLOOR(your_number/(256*256))%256),CHAR(FLOOR(your_number/256)%256),CHAR(your_number%256)))
Note, that TO_BASE64() function is available only in MySQL 5.6 on-wards.
Now, for those on older versions - we don't want to implement base64 encoding with our bare hands, don't we? So, lets go the easier way: we have 30 bits in those 9 decimal digits, which would be 30/6=5 characters, if we use 64 continuous character alphabet after CHAR(32), which is space, which we don't want to use:
SELECT CONCAT(`enter code here`CHAR(FLOOR(your_number/(64*64*64*64))%64+33),CHAR(FLOOR(your_number/(64*64*64))%64+33),CHAR(FLOOR(your_number/(64*64))%64+33),CHAR(FLOOR(your_number/64)%64+64),CHAR(your_number%64+33))
I was just looking for something like this and I found a way to do it with the CONV function.
CONV(9+your_number, 10, 36)
This converts 1 to A, 2 to B etc.
The way it works is by adding 9 and then converting to base 36, in which 10 is A, 11 is B etc.
You can generate a hash with the value of transaction id.
Like:
SELECT MD5(transactionID) FROM `yourtable`;
There are several other types of similar functions you can use.
Maybe can use CASE WHEN() END; and stored procedure
For example:
CREATE DEFINER = 'USERNAME'#'HOST' STORED PROCEDURE `ConvertNumberToAlphabetic`
BEGIN
SELECT
(CASE
WHEN (your_number = '1') THEN 'A'
WHEN (your_number = '2') THEN 'B'
WHEN (your_number = '3') THEN 'C'
WHEN (your_number = '4') THEN 'D'
WHEN (your_number = '5') THEN 'E'
WHEN (your_number = '6') THEN 'F'
WHEN (your_number = '7') THEN 'G'
WHEN (your_number = '8') THEN 'H'
WHEN (your_number = '9') THEN 'J'
WHEN (your_number = '10') THEN 'K'
END) RedeemCode
FROM tblTransaction;
END
SELECT SUBSTRING(MD5(transactionId) FROM 1 FOR 4) AS RedemptionCode
This creates a 4 character (easy to change, as you can see) string where the characters are from the MD5 command (and therefore in the range a-z and 0-9).
Related
I have some code that converts numbers from Hexadecimal to Decimal, and checks to see if greater than or equal to. Today, I discovered this doesn't always work, and I have no idea why.
I have tried using the raw values, and that works. Conversions outside of the compare work.
select case when 5 >= 40 then 'Fail' else 'Pass' end as 'Check';
select case WHEN CONV('0005',16,10) >= CONV('28',16,10) THEN 'Fail' else 'Pass' end as 'Check';
select CONV('0005',16,10) as '1', CONV('28',16,10) as '2';
The first select works as intended (Pass). The second select does not. The third select just shows that the conversion is working properly (i.e. 0005 HEX = 5 Decimal, and 28 Hex = 40 Decimal).
What am I missing?
From the docs:
CONV(N,from_base,to_base)
... Returns a string representation of the
number N ...
The results are strings and compared as such. For strings '5' > '40'. So you need to cast the ressults either explicitly with CAST
select case
WHEN CAST(CONV('0005',16,10) as SIGNED) >= CAST(CONV('28',16,10) as SIGNED )
THEN 'Fail'
else 'Pass' end
as 'Check';
or implicitly with a numeric operation like +0
select case WHEN CONV('0005',16,10)+0 >= CONV('28',16,10)+0 THEN 'Fail' else 'Pass' end as 'Check';
If you know the max length of the HEX strings, you could also fill them with zeros using LPAD()
select case
WHEN lpad('0005', 20, 0) >= lpad('28', 20, 0)
THEN 'Fail'
else 'Pass'
end as 'Check';
I have a requirement where I need to mask all but characters in position 1,4,8,12,16.. for a variable length string with 'X'
For example:
Input string - 'John Doe'
Output String - 'JXXn xxE'
SPACE between the two strings must be retained.
Kindly help or reach out for more details if required.
I think maybe an external function would be best here, but if that's too much to bite off, you can get crafty with strtok_split_to_table, xml_agg and regexp_replace to rip the string apart, replace out characters using your criteria, and stitch it back together:
WITH cte AS (SELECT REGEXP_REPLACE('this is a test of this functionality', '(.)', '\1,') AS fullname FROM Sys_Calendar.calendar WHERE calendar_date = CURRENT_DATE)
SELECT
REGEXP_REPLACE(REGEXP_REPLACE((XMLAGG(tokenout ORDER BY tokennum) (VARCHAR(200))), '(.) (.)', '\1\2') , '(.) (.)', '\1\2')
FROM
(
SELECT
tokennum,
outkey,
CASE WHEN tokennum = 1 OR tokennum mod 4 = 0 OR token = ' ' THEN token ELSE 'X' END AS tokenout
FROM TABLE (strtok_split_to_table(cte.fullname, cte.fullname, ',')
RETURNS (outkey VARCHAR(200), tokennum integer, token VARCHAR(200) CHARACTER SET UNICODE)) AS d
) stringshred
GROUP BY outkey
This won't be fast on a large data set, but it might suffice depending on how much data you have to process.
Breaking this down:
WITH cte AS (SELECT REGEXP_REPLACE('this is a test of this functionality', '(.)', '\1,') AS fullname FROM Sys_Calendar.calendar WHERE calendar_date = CURRENT_DATE)
This CTE is just adding a comma between every character of our incoming string using that regexp_replace function. Your name will come out like J,o,h,n, ,D,o,e. You can ignore the sys_calendar part, I just put that in so it would spit out exactly 1 record for testing.
SELECT
tokennum,
outkey,
CASE WHEN tokennum = 1 OR tokennum mod 4 = 0 OR token = ' ' THEN token ELSE 'X' END AS tokenout
FROM TABLE (strtok_split_to_table(cte.fullname, cte.fullname, ',')
RETURNS (outkey VARCHAR(200), tokennum integer, token VARCHAR(200) CHARACTER SET UNICODE)) AS d
This subquery is the important bit. Here we create a record for every character in your incoming name. strtok_split_to_table is doing the work here splitting that incoming name by comma (which we added in the CTE)
The Case statement just runs your criteria swapping out 'X' in the correct positions (record 1, or a multiple of 4, and not a space).
SELECT
REGEXP_REPLACE(REGEXP_REPLACE((XMLAGG(tokenout ORDER BY tokennum) (VARCHAR(200))), '(.) (.)', '\1\2') , '(.) (.)', '\1\2')
Finally we use XMLAGG to combine the many records back into one string in a single record. Because XMLAGG adds a space in between each character we have to hit it a couple of times with regexp_replace to flip those spaces back to nothing.
So... it's ugly, but it does the job.
The code above spits out:
tXXs XX X XeXX oX XhXX fXXXtXXXaXXXy
I couldn't think of a solution, but then #JNevill inspired me with his idea to add a comma to each character :-)
SELECT
RegExp_Replace(
RegExp_Replace(
RegExp_Replace(inputString, '(.)(.)?(.)?(.)?', '(\1(\2[\3(\4', 2)
,'(\([^ ])', 'X')
,'(\(|\[)')
,'this is a test of this functionality' AS inputString
tXXs XX X XeXX oX XhXX fXXXtXXXaXXXy
The 1st RegExp_Replace starts at the 2nd character (keep the 1st character as-is) and processes groups of (up to) 4 characters adding either a ( (characters #1,#2,#4, to be replaced by X unless it's a space) or [ (character #3, no replacement), which results in :
t(h(i[s( (i(s[ (a( (t[e(s(t( [o(f( (t[h(i(s( [f(u(n(c[t(i(o(n[a(l(i(t[y(
Of course this assumes that both characters don't exists in your input data, otherwise you have to choose different ones.
The 2nd RegExp_Replace replaces the ( and the following character with X unless it's a space, which results in:
tXX[s( XX[ X( X[eXX( [oX( X[hXX( [fXXX[tXXX[aXXX[y(
Now there are some (& [ left which are removed by the 3rd RegExp_Replace.
As I still consider me as a beginner in Regular Expressions, there will be better solutions :-)
Edit:
In older Teradata versions not all parameters were optional, then you might have to add values for those:
RegExp_Replace(
RegExp_Replace(
RegExp_Replace(inputString, '(.)(.)?(.)?(.)?', '(\1(\2[\3(\4', 2, 0 'c')
,'(\([^ ])', 'X', 1, 0 'c')
,'(\(|\[)', '', 1, 0 'c')
I have an instance where sometimes we aren't sure to filter by the name text or the name id. I've solved this with a case statement and isnumeric. For example, we have both the id and name values but we're not sure which column is being asked to filter. rtresource.id is numeric, and in this case we have the value '183' to work with. If rtresource.rname (varchar) is trying to filter, then we have the rname for that id 'Jane Thompson'.
So the filter is either
rtresource.id=183
Or
rtresource.rname='Jane Thompson'
Instead becomes
rtresource.rname in (CASE IsNumeric(rtresource.rname) WHEN 1 then '183' else 'Jane Thompson' End)
This works awesome. The issue is having more than one set of id/rname being passed. Typically, we would ask either rtresource.id in (183, 23) or rtresource.rname in ('Jane Thompson','John Doe'). How can I solve this with a case statement?
rtresource.rname in (CASE IsNumeric(rtresource.rname) WHEN 1 then ('183','23') else ('Jane Thompson','John Doe') End)
Above complains over the commas between values. I've also tried:
rtresource.rname in (CASE IsNumeric(rtresource.rname) WHEN 1 then ('183'+','+'23') else ('Jane Thompson'+','+'John Doe') End)
Which doesn't work either. Ideas? Thanks for any help in advance.
You have to repeat the case for every value in the list:
where rtresource.rname in (
CASE IsNumeric(rtresource.rname) WHEN 1 then '183' else 'Jane Thompson' End,
CASE IsNumeric(rtresource.rname) WHEN 1 then '23' else 'John Doe' End
)
or since the values don't intersect, just:
where rtresource.rname in (183, 23, 'Jane Thompson', 'John Doe')
which would perform much better as it will use an index (if any) of the column, and is easier to code, understand and scale.
Figured it out. Skip the case and instead:
(IsNumeric(rtresource.rname) = 1 and rtresource.rname in ('183','23'))
or
(IsNumeric(rtresource.rname) != 1 and rtresource.rname in ('Jane Thompson','John Doe'))
On an old project because of not thought through design I have a column which actually should be set to auto_increment, though it cannot be because it are alphanumeric entries as follows:
c01
c02
c03
(c99 would continue to c100 and more), the letter happened in the past and it would require to overhaul the system to take it out, thus I rather prefer this workaround.
Now I need a way to imitate the auto_increment functionality with the SQL statement myself, my own attempt has gotten as far as the following:
INSERT INTO tags (tag_id, tag_name, tag_description, added_by_user_id, creation_date, last_edited) VALUES (SELECT(MAX(tag_id)+1),
'Love', 'All about love', 7, now(), 0);
This one does not work as is, though the idea was to select the highest entry in the column "tag_id" and then simply increase it by the value 1.
Any ideas how to accomplish this?
By the way I am also not sure if you simply can increase an alphanumeric entry through this way, though I know it can be done, I just don't know how.
If you want to safely get the largest integer value of a tag id of the form c##.., you could use the following expression:
max( convert( substring(tag_id, 2) , unsigned integer) )
^^^ largest ^^^^^^^^^ after 'c' ^^^^^^^^^^^^^^^^ convert to positive number
Then your insert statement would look something like this:
set #newid = convert(
(select
max(convert( (substring(tag_id, 2)) , unsigned integer))+1
from tags), char(10)
);
set #newid = if(length(#newid) = 1, concat('0', #newid), #newid);
set #newid = concat('c', #newid);
INSERT INTO tags (tag_id, tag_name, tag_description, added_by_user_id,
creation_date, last_edited)
VALUES (#newid, 'Love', 'All about love', 7, now(), '2012-04-15');
Demo: http://www.sqlfiddle.com/#!2/0bd9f/1
this will increase from c01 to c02 to c03 ... to c99 to c100 to c101 ... to c999 to c1000 etc.
set #nextID = (SELECT CONCAT(SUBSTRING(`tag_id`, 1, 1), IF(CHAR_LENGTH(CAST(SUBSTRING(`tag_id`, 2)
AS UNSIGNED)) < 2, LPAD(CAST(CAST(SUBSTRING(`tag_id`, 2) AS UNSIGNED) + 1 AS CHAR), 2,
'0'), CAST(CAST(SUBSTRING(`tag_id`, 2) AS UNSIGNED) + 1 AS CHAR))) FROM `tags` ORDER BY
`tag_id` DESC LIMIT 1);
INSERT INTO tags (tag_id, tag_name, tag_description, added_by_user_id,
creation_date, last_edited) VALUES (#nextID, 'Love', 'All about love', 7, NOW(), null);
I'm wondering if it is possible to exclude a value inside a range defined by "between".
Here is an example:
...
WHEN left(name,2) between 'AA' and 'AZ' then 'HALLO'
...
I want to exclude from this range, for example, the value 'AM'
Is there a short way to obtain this or I need to split the range into two different ones as follow?
...
WHEN left(name,2) between 'AA' and 'AL' then 'HALLO'
WHEN left(name,2) between 'AN' and 'AZ' then 'HALLO'
...
Thanks in advance.
Just put your more restrictive case first:
CASE
WHEN left(name,2) == 'AM' then 'wohoo'
WHEN left(name,2) between 'AA' and 'AZ' then 'HALLO'
END
If it matches 'AM' then since that case is first in the statement, its action will be taken, even though the second case also matches 'AM'.
WHEN left(name,2) between 'AA' and 'AZ' AND NOT left(name,2) = 'AM' then 'HALLO'
You it is possible:
...
WHEN (left(name,2) between 'AA' and 'AZ') AND (left(name,2) <> 'AM')
then 'HALLO'
...