Combine data types in SQL Server - sql-server

Is there any way to combine data types in SQL Server? i.e. have the value of a cell to be both text and number where the text is always the same?
I have a table called Contract. The ContractID field should have the value of: 'TCwxyz' where 'TC' are string characters and 'wxyz' are integers.
I have the following but it doesn't seem to be working:
CREATE TYPE TenantContracts AS CHAR(6)
CHECK (SUBSTRING(VALUE,1,2)='TC'
AND (SUBSTRING(VALUE,2,4) AS SMALLINT)
Any assistance would be appreciated.

Constraints are added to the table definition, you don't need to create a type.
ALTER TABLE Contract
ADD CONSTRAINT chk_ContractID CHECK (
SUBSTRING(ContractID, 1, 2) = 'TC'
AND ISNUMERIC(SUBSTRING(ContractID, 3, 4)) = 1
)
This solution will accept a few incorrect values, for instance TC1.01. I'd just use this for the virtue of simplicity though, rather than trying to determine if the last 4 digits are an integer, which gets surprisingly tricky (T-sql - determine if value is integer).
Edit: If you did want to make a more robust integer check, perhaps best would be to simply check individually if each of the last 4 characters is numeric:
ALTER TABLE Contract
ADD CONSTRAINT chk_ContractID CHECK (
SUBSTRING(ContractID, 1, 2) = 'TC'
AND ISNUMERIC(SUBSTRING(ContractID, 3, 1)) = 1
AND ISNUMERIC(SUBSTRING(ContractID, 4, 1)) = 1
AND ISNUMERIC(SUBSTRING(ContractID, 5, 1)) = 1
AND ISNUMERIC(SUBSTRING(ContractID, 6, 1)) = 1
)

Related

Generate 6 digit alpha numeric value

I have the following code; the only issue with it is that sometimes it generates a digit alphanumeric code.
I am trying to modify it such that it always returns a 6 digit code. Any thoughts on how to tweak this?
I have another procedure the purges duplicates and it re-runs, so that is not an issue.
SET #HashCode = NULL
SELECT
#HashCode = ISNULL(#HashCode, '') + SUBSTRING('23456789ABCDEFGHJKLMNPQRSTUVWXYZ', (ABS(CHECKSUM(NEWID())) % 36) + 1, 1)
FROM
[master]..[spt_values] AS [spt_values] WITH (NOLOCK)
WHERE
[spt_values].[type] = 'P'
AND [spt_values].[number] < 6
A very simple way of achieving this is selecting the first 6 LEFT characters in the string generated by the NEWID() function:
SELECT LEFT(NEWID(), 6)
Examples:
4DF32A
DC70D5
8793B2
3D8416
EE2838
Note, because the NEWID() function generates a unique identifier (GUID), consisting of hexadecimal characters (A-F & 0-9), there is a chance the output will consist of only numbers or only letters; which may or may not fit your purpose if you require a strict alphanumeric value.
Examples:
946983
831814
DDFDBB
You can use it like this:
SET #HashCode = NULL
SELECT
#HashCode = LEFT(NEWID(), 6)

MS SQL Obfuscation of Varchar(10) to Integer and back?

I have a two fields that identify a user, one being a Integer and one being a varchar(10) in the format of 'AA111X' where the first two are alpha and the final x is alphanumeric, and need to convert that into an integer for the integer field as a translation. The integer value used to be provided for us but is no longer. The answer may well be this isn't possible, and a lookup table will have to be used but I'm trying to avoid the schema change if possible.
Is it necessary that you actually treat the first two characters as some value base 26, and the last character as some value base 36? Or is it only necessary that you can generate a unique integer for any possible input, in a way that can be converted back if necessary?
If the latter, and if the existing values are considered case insensitive, this solution will always result in a value that fits in a 4 byte integer:
declare #val varchar(10) = 'zz111z';
select cast(
concat(
ascii(substring(val, 1, 1)),
ascii(substring(val, 2, 1)),
substring(val, 3, 3),
ascii(substring(val, 6, 1))
)
as int
)
from (select upper(#val)) v(val);

Looping inside a Postgres UPDATE query

(Postgres 10.10)
I have the following fields in my_table:
loval INTEGER
hival INTEGER
valcount INTEGER
values INTEGER[]
I need to set values to an array containing valcount random integers each between loval and hival inclusive. So for:
loval: 3
hival: 22
valcount: 6
I'm looking to set values to something like:
{3, 6, 6, 13, 17, 22}
I know how to do this with an inefficient "loop through the cursor" solution, but I'm wondering if Postgres has a way to do a looping computation inline.
Note: I looked at generate_series, but I don't think it produces what I need.
generate_series() is indeed the solution:
update my_table
set "values" = array(select (random() * (hival - loval) + loval)::int
from generate_series(1, valcount));
Online example
Note that values is a reserved keyword, it's not a good idea to use that as a column name.

Postgres select by array element range

In my table I've got column facebook where I store facebook data ( comment count, share count etc.) and It's an array. For example:
{{total_count,14},{comment_count,0},{comment_plugin_count,0},{share_count,12},{reaction_count,2}}
Now I'm trying to SELECT rows that facebook total_count is between 5 and 10. I've tried this:
SELECT * FROM pl where regexp_matches(array_to_string(facebook, ' '), '(\d+).*')::numeric[] BETWEEN 5 and 10;
But I'm getting an error:
ERROR: operator does not exist: numeric[] >= integer
Any ideas?
There is no need to convert the array to text and use regexp. You can access a particular element of the array, e.g.:
with pl(facebook) as (
values ('{{total_count,14},{comment_count,0},{comment_plugin_count,0},{share_count,12},{reaction_count,2}}'::text[])
)
select facebook[1][2] as total_count
from pl;
total_count
-------------
14
(1 row)
Your query may look like this:
select *
from pl
where facebook[1][2]::numeric between 5 and 10
Update. You could avoid the troubles described in the comments if you would use the word null instead of empty strings ''''.
with pl(id, facebook) as (
values
(1, '{{total_count,14},{comment_count,0}}'::text[]),
(2, '{{total_count,null},{comment_count,null}}'::text[]),
(3, '{{total_count,7},{comment_count,10}}'::text[])
)
select *
from pl
where facebook[1][2]::numeric between 5 and 10
id | facebook
----+--------------------------------------
3 | {{total_count,7},{comment_count,10}}
(1 row)
However, it would be unfair to leave your problems without an additional comment. The case is suitable as an example for the lecture How not to use arrays in Postgres. You have at least a few better options. The most performant and natural is to simply use regular integer columns:
create table pl (
...
facebook_total_count integer,
facebook_comment_count integer,
...
);
If for some reason you need to separate this data from others in the table, create a new secondary table with a foreign key to the main table.
If for some mysterious reason you have to store the data in a single column, use the jsonb type, example:
with pl(id, facebook) as (
values
(1, '{"total_count": 14, "comment_count": 0}'::jsonb),
(2, '{"total_count": null, "comment_count": null}'::jsonb),
(3, '{"total_count": 7, "comment_count": 10}'::jsonb)
)
select *
from pl
where (facebook->>'total_count')::integer between 5 and 10
hstore can be an alternative to jsonb.
All these ways are much easier to maintain and much more efficient than your current model. Time to move to the bright side of power.

Create calculated field in Visual Basic

I would like to create a calculated field within one of my tables within the database. I am using visual basic and my IDE is visual studio.
I recently created the same database using access and used the following calculation within a calculated field’s expression:
(([Grade1]/10)*2)+(([Grade2]/10)*3)+(([Grade3]/10)*5)
As a first go I just pasted to code into the default value or binding of the field as a long shot, but got an error. Guess I’m not allowed to make calculations using data from existing columns.
Can anyone help me with creating this field? The field does not need to be within the database it would be preferred however if it could be calculated outside of the database on a form using datagridview or something like that would be fine.
Grade 1, 2 and 3 contain's a number from 0 to 100.
I can’t find any tutorials unless anyone can point me in the right direction?
In order to get the calculated field as described you should use the following T-SQL code:
create table dbo.Enrolement
(
StudentID int not null,
ModuleNUmber int not null,
Grade1 nchar(3) not null,
Grade2 nchar(3) not null,
Grade3 nchar(3) not null,
Overall as cast(((Grade1 / 10 * 2) + (Grade2 / 10 * 3) + (Grade3 / 10 * 5)) as nchar(10))
)
Yet, I do not understand why you are storing the grades as text. More over, your T-SQL code requires an implicit conversions of the text to numbers, make the calculation, and then revert it back to text again. Furthermore, during the conversion you are not telling SQL what do convert to. So, if you have grades with decimal places then a SELECT from the table will fail.
insert into dbo.Enrolement
(
StudentID,
ModuleNUmber,
Grade1,
Grade2,
Grade3
)
values (
2, -- StudentID - int
101, -- ModuleNUmber - int
N'1.2', -- Grade1 - nchar(3)
N'1.8', -- Grade2 - nchar(3)
N'3.9' -- Grade3 - nchar(3)
)
select *
from dbo.Enrolement
Msg 245, Level 16, State 1, Line 17 Conversion failed when converting
the nvarchar value '1.2' to data type int.
So, I'd very much recommend you using numbers to store your grades if they are to be used in a calculation afterwards. Here is the same table with numbers instead:
create table dbo.Enrolement
(
StudentID int not null,
ModuleNUmber int not null,
Grade1 decimal(9, 3) not null,
Grade2 decimal(9, 3) not null,
Grade3 decimal(9, 3) not null,
Overall as ((Grade1 / 10 * 2) + (Grade2 / 10 * 3) + (Grade3 / 10 * 5))
)
I have chosen to use decimal(9,3) because any precision from 1 to 9 uses the same amount of storage in bytes. At the same time I limited the decimal places to three and thus allow for rounding on two decimal places. At the same time there is ample of room to potentially "add" grades (if this should ever make sense) to a grand total of 999999.999

Resources