Use user-defined function in CREATE TABLE statement - sql

I'm trying to create the following table
CREATE TABLE Ingredient.Ingredient
(
GUID UNIQUEIDENTIFIER NOT NULL ROWGUIDCOL,
Name NVARCHAR(MAX) NOT NULL UNIQUE
)
but I've come to realize that the max size of a NVARCHAR UNIQUE column is 450 (at least in the current version of SQL Server). In order to not use magic literals I've created a user-defined function that returns the current max size of a NVARCHAR UNIQUE column.
CREATE FUNCTION [Max NVARCHAR Index Size]()
RETURNS INTEGER
BEGIN
RETURN(450)
END
This function runs correctly when called as
SELECT dbo.[Max NVARCHAR Index Size]()
I was hoping to use this function in a CREATE TABLE statement, but it errors as shown below.
CREATE TABLE Ingredient.Ingredient
(
GUID UNIQUEIDENTIFIER NOT NULL ROWGUIDCOL,
Name NVARCHAR(dbo.[Max NVARCHAR Index Size]()) NOT NULL UNIQUE
)
Error:
Msg 102, Level 15, State 1, Line 13
Incorrect syntax near '('
To try and circumvent this I made a variable with the value of the function, and then using the variable, but that didn't work either.
DECLARE
#NVARCHARIndexSize INTEGER = dbo.[MAX NVARCHAR Index Size]()
CREATE TABLE Ingredient.Ingredient
(
GUID UNIQUEIDENTIFIER NOT NULL ROWGUIDCOL,
Name NVARCHAR(#NVARCHARIndexSize) NOT NULL UNIQUE
)
Error:
Msg 102, Level 15, State 1, Line 13
Incorrect syntax near '#NVARCHARIndexSize'
where line 13 is Name NVARCHAR(#NVARCHARIndexSize) NOT NULL UNIQUE.
Is there a way to use variables/functions instead of literals in a CREATE TABLE statement?
Thanks in advance.

You can create a custom type in SQL Server with following syntax
CREATE TYPE MyCustomType
FROM NVARCHAR(420);
And later on can use the custom type while creating tables
CREATE TABLE Ingredient
(
GUID UNIQUEIDENTIFIER NOT NULL ROWGUIDCOL,
[Name] MyCustomType NOT NULL UNIQUE
)

DDL can't be parameterized. You'd have to use dynamic SQL for that. eg
DECLARE
#NVARCHARIndexSize INTEGER = dbo.[MAX NVARCHAR Index Size]()
declare #sql nvarchar(max) = concat('
CREATE TABLE Ingredient.Ingredient
(
GUID UNIQUEIDENTIFIER NOT NULL ROWGUIDCOL,
Name NVARCHAR(',#NVARCHARIndexSize,') NOT NULL UNIQUE
)'
)
exec (#sql)

Prior to SQL Server 2016, the maximum key length was 900 bytes. MSDN Reference
Index Key Size
The maximum size for an index key is 900 bytes for a clustered index and 1,700 bytes for a nonclustered index. (Before
SQL Database and SQL Server 2016 (13.x) the limit was always 900
bytes.) Indexes on varchar columns that exceed the byte limit can be
created if the existing data in the columns do not exceed the limit at
the time the index is created; however, subsequent insert or update
actions on the columns that cause the total size to be greater than
the limit will fail. The index key of a clustered index cannot contain
varchar columns that have existing data in the ROW_OVERFLOW_DATA
allocation unit. If a clustered index is created on a varchar column
and the existing data is in the IN_ROW_DATA allocation unit,
subsequent insert or update actions on the column that would push the
data off-row will fail.
Nonclustered indexes can include non-key columns in the leaf level of
the index. These columns are not considered by the Database Engine
when calculating the index key size
You can define a NVARCHAR(450) column with check constraint, to ensure that your data does not go beyond 450 characters. I would suggest you to use DATALENGTH to ensure that column length is <= 900.
CREATE TABLE #test(id int identity(1,1) not null,
a NVARCHAR(500) CHECK (DATALENGTH(a) <= 900),
CONSTRAINT ak_a unique(a))
insert into #test
values('a') -- 1 row affected
insert into #test
values(REPLICATE('a',450)) -- 1 row affected
insert into #test
values(REPLICATE('a',451)) -- Error
Msg 547, Level 16, State 0, Line 12 The INSERT statement conflicted
with the CHECK constraint "CK__#test__________a__AC6651A7". The
conflict occurred in database "tempdb", table "#test", column 'a'.
In future, when you move to higher versions, you can increase length of NVARCHAR and CHECK constraint accordingly.

Related

SQL Server Computed Column: how to set it to NOT NULL, without persistence

I am adding a computed column, which should be NOT NULL on an existing table.
Both my source column for the computed value are NOT NULL of course. I would like to make the computed column not persistent, to save space, but I fail to do so:
ALTER TABLE UnitHistory
ADD
[PrefixedUnitId] AS ((UnitHistory.UnitIdPrefix + cast(UnitHistory.UnitId
AS nvarchar(8)))) NOT NULL
GO
gives
Only UNIQUE or PRIMARY KEY constraints can be created on computed
columns, while CHECK, FOREIGN KEY, and NOT NULL constraints require
that computed columns be persisted. Msg 15135, Level 16, State 15,
Procedure sp_addextendedproperty, Line 72 Object is invalid. Extended
properties are not permitted on 'dbo.UnitHistory.PrefixedUnitId', or
the object does not exist.
Now, I understand what the message says, and sure enough, using PERSISTED NOT NULL does produce the column.
To save space however, I would like to have the column not be persisted.
Is there a way to have a computed column NOT NULL, but not persistent, when the underlying columns are NOT NULL?
Note: It should work on a SQL Server 2012 SP2
This seems totally redundant, but you can repeat the condition in a check constraint:
ALTER TABLE UnitHistory
ADD PrefixedUnitId AS ( UnitHistory.UnitIdPrefix + cast(UnitHistory.UnitId AS nvarchar(8)) );
ALTER TABLE UnitHistory
ADD CONSTRAINT CHECK ( UnitHistory.UnitIdPrefix + cast(UnitHistory.UnitId AS nvarchar(8)) IS NOT NULL );
However, because the source columns are NOT NULL, the result can never be NULL.
The notion of non-persisted computed columns is that they are calculated on output not input. I think this explains why check constraints are not appropriate.
For instance, you can have a table that has a computed column that generates an error and still fully use the table:
create table t (
id int identity primary key,
x int,
computed as (1 / 0)
);
-- works
insert into t (x) values (1);
-- works
select x
from t;
-- fails
select *
from t;
Here is a db<>fiddle.
Incidentally, a computed column that always generates an error has a nice side effect -- it prevents users from using select * if that is something you want to do.

How to alter column of a table with indexes?

I would like to change the column size of the a column in a table with some indexes from varchar(200) to varchar(8000). How should I proceed?
Since is VARCHAR and you're increasing the size, then simply ALTER TABLE ... ALTER COLUMN ... should be enough.
The data type of columns included in an index cannot be changed unless the column is a varchar, nvarchar, or varbinary data type, and the new size is equal to or larger than the old size.
Otherwise you would drop the index(es), alter the column, then add back the index(es).
Be aware though that SQL Server maximum index key size is 900 (or 1700 for newer editions), so even though the ALTER will succeed, a future INSERT of
data over the 900 length limit will fail with error:
Msg 1946, Level 16, State 3, Line 13
Operation failed. The index entry of length ... bytes for the index '...' exceeds the maximum length of 900 bytes.
Try this for MSSQL:
drop index person.idx_pers_fullname
ALTER table person alter COLUMN pers_firstname nvarchar(8000)
create index idx_pers_fullname on person(pers_firstname)
Example in Oracle:
CREATE TABLE EMP (NAME VARCHAR(200));
ALTER TABLE EMP MODIFY NAME VARCHAR(800);

Create a column with an if condition in SQL server

i am not sure if i could use conditional statement while creating new columns.
Code:
create table Employees(
Emp_ID int primary key identity (1,1),
Hours_worked int,
Rate int default '')
/*Now here in default value i want to set different rates depending upon hours worked. like if hour worked is greater than 8 then rate is 300, if hours worked is less than 8 hour the rate is 200.) How to write this as a Default value in sql server 2008.
My second question is:
Why i get error if i write like this,
create table tbl_1(
col_1 varchar(max) unique
)
The error is
Column 'col_1' in table 'tbl_1' is of a type that is invalid for use as a key column in an index.
Msg 1750, Level 16, State 0, Line 1
Regards
Waqar
you can use COMPUTED Column, http://msdn.microsoft.com/en-us/library/ms191250.aspx
create table Employees(
Emp_ID int primary key identity (1,1),
Hours_worked int,
Rate as (case when Hours_worked > 8 then 300 else 200 end) persisted )
The default value cannot refer to any other column names. So the "default" value of Rate won't know the value of Hours_worked. You could handle it with a trigger or whatever is doing the actual inserting could contain this logic.
http://msdn.microsoft.com/en-us/library/ms173565(v=sql.100).aspx
You cannot but a UNIQUE constraint on a VARCHAR(MAX) field.

How to create Unique key for a varchar column for entity framework database?

I had watch youtube video tutorial teaching how to create unique key
http://www.youtube.com/watch?v=oqrsfatxTYE&list=PL08903FB7ACA1C2FB&index=9
In the video, he has created a unique key for Email(nvarchar) column, I could create it when I create database manually, but when I try create unique key for a database created with entity framework code first, using the next query
ALTER TABLE Peoples
ADD CONSTRAINT UQ_MyTable_Email UNIQUE (email)
It will generate a error:
Msg 1919, Level 16, State 1, Line 2
Column 'email' in table 'Peoples' is of a type that is invalid for use as a key column in an index.
What is problem? what can I do for create unique key for nvarchar(max) column?
say If you create this table
CREATE TABLE ConstTable
(ID INT,
Email VARCHAR(1000)
CONSTRAINT uc_Email UNIQUE (Email)
)
GO
you will get a warning :
Warning! The maximum key length is 900 bytes. The index 'uc_Email' has
maximum length of 1000 bytes. For some combination of large values,
the insert/update operation will fail
Your column on which you want to define a unique constraint should be less then or equal to 900 bytes, so you can have a VARCHAR(900) or NVARCHAR(450) column if you want to be able to create a unique constraint on that column
Same table above with VARCHAR(450) gets created without any warning
CREATE TABLE ConstTable
(ID INT,
Email VARCHAR(900)
CONSTRAINT uc_Email UNIQUE (Email)
)
GO
Result
Command(s) completed successfully.
Test For your Table
say this is your table
CREATE TABLE ConstTable
(ID INT,
Email VARCHAR(MAX)
)
GO
Now try to create any index on the VARCHAR(MAX) data type and you will get the same error.
CREATE INDEX ix_SomeIdex
ON ConstTable (Email)
Error Message
Msg 1919, Level 16, State 1, Line 1 Column 'Email' in table
'ConstTable' is of a type that is invalid for use as a key column in
an index.

is of a type that is invalid for use as a key column in an index

I have an error at
Column 'key' in table 'misc_info' is of a type that is invalid for use as a key column in an index.
where key is a nvarchar(max). A quick google search finds that the maximum length of an index is 450 chars. However, this doesn't explain what a solution is. How do I create something like Dictionary where the key and value are both strings and obviously the key must be unique and is single? My sql statement was
create table [misc_info] (
[id] INTEGER PRIMARY KEY IDENTITY NOT NULL,
[key] nvarchar(max) UNIQUE NOT NULL,
[value] nvarchar(max) NOT NULL);
A unique constraint can't be over 8000 bytes per row and will only use the first 900 bytes even then so the safest maximum size for your keys would be:
create table [misc_info]
(
[id] INTEGER PRIMARY KEY IDENTITY NOT NULL,
[key] nvarchar(450) UNIQUE NOT NULL,
[value] nvarchar(max) NOT NULL
)
i.e. the key can't be over 450 characters. If you can switch to varchar instead of nvarchar (e.g. if you don't need to store characters from more than one codepage) then that could increase to 900 characters.
There is a limitation in SQL Server (up till 2008 R2) that varchar(MAX) and nvarchar(MAX) (and several other types like text, ntext ) cannot be used in indices. You have 2 options:
1. Set a limited size on the key field ex. nvarchar(100)
2. Create a check constraint that compares the value with all the keys in the table.
The condition is:
([dbo].[CheckKey]([key])=(1))
and [dbo].[CheckKey] is a scalar function defined as:
CREATE FUNCTION [dbo].[CheckKey]
(
#key nvarchar(max)
)
RETURNS bit
AS
BEGIN
declare #res bit
if exists(select * from key_value where [key] = #key)
set #res = 0
else
set #res = 1
return #res
END
But note that a native index is more performant than a check constraint so unless you really can't specify a length, don't use the check constraint.
The only solution is to use less data in your Unique Index. Your key can be NVARCHAR(450) at most.
"SQL Server retains the 900-byte limit for the maximum total size of all index key columns."
Read more at MSDN
A solution would be to declare your key as nvarchar(20).
Noting klaisbyskov's comment about your key length needing to be gigabytes in size, and assuming that you do in fact need this, then I think your only options are:
use a hash of the key value
Create a column on nchar(40) (for a sha1 hash, for example),
put a unique key on the hash column.
generate the hash when saving or updating the record
triggers to query the table for an existing match on insert or update.
Hashing comes with the caveat that one day, you might get a collision.
Triggers will scan the entire table.
Over to you...