Persisted column throwing non-deterministic error using convert - sql

Using SQL Server 2016 and I'm running into a little bit of a problem.
Here's my use case that is causing issues for me...
create table dbo.Example (
Id int identity (1, 1) not null,
[Name] nvarchar(100) not null,
Email nvarchar(255) not null,
DOB datetime2(7) not null,
RowHash as convert(nvarchar(66), hashbytes('SHA1', coalesce(
convert(nvarchar(max), [Name]),
convert(nvarchar(max), Email),
convert(nvarchar(max), DOB)
))) persisted
constraint [PK_Example] primary key clustered (Id asc)
);
drop table dbo.Example;
The message I'm getting is:
Msg 4936, Level 16, State 1, Line 1
Computed column 'RowHash' in table 'Example' cannot be persisted because the column is non-deterministic.
When I set the column to not be persisted, the data type is interpreted correctly as nvarchar(66) however I would like to have it persisted. The issue seems to be related to the datetime2 column however I have a mixture of data types on the table.
So the goals are to use a persisted hashbytes column to hold a hash of all the values in my table.
Any ideas?
Thx!

Why coalesce() and not concat()?
Example
create table dbo.Example (
Id int identity (1, 1) not null,
[Name] nvarchar(100) not null,
Email nvarchar(255) not null,
DOB datetime2(7) not null,
RowHash as convert(nvarchar(66), hashbytes('SHA1', concat(
[Name],
Email,
DOB
))) persisted
constraint [PK_Example] primary key clustered (Id asc)
);
Select * from [dbo].[Example]
--drop table dbo.Example;
Results

You can fix this by specifying a format for the date conversion:
create table dbo.Example (
Id int identity (1, 1) not null,
[Name] nvarchar(100) not null,
Email nvarchar(255) not null,
DOB date not null, -- I figure date is good enough
RowHash as convert(nvarchar(66), hashbytes('SHA1', concat(
convert(nvarchar(max), [Name]),
convert(nvarchar(max), Email),
convert(nvarchar(max), DOB, 121)
))) persisted
constraint [PK_Example] primary key clustered (Id asc)
);
The problem is that the default date-to-string conversion depends on system parameters, so it is not deterministic. For a persisted column, all components need to be deterministic.
I would love to say that the documentation covers this exotic point in well-balanced detail. Not quite. You can get the idea from this documentation. Just be forgiving -- it also applies to date, datetime2 and other data types.

Here's the final result which is a combo of both answers above. Thx very much for the help.
create table dbo.Example (
Id int identity (1, 1) not null,
[Name] nvarchar(100) not null,
Email nvarchar(255) not null,
DOB datetime2(7) null,
RowHash as convert(nvarchar(66), hashbytes('SHA1', concat(
convert(nvarchar(max), [Name]),
convert(nvarchar(max), Email),
convert(nvarchar(max), DOB, 121)
))) persisted
constraint [PK_Example] primary key clustered (Id asc)
);
drop table dbo.Example;

Related

How to save auto generated primary key Id in foreign key column in same table

Following is the table structure:
CREATE TABLE [User] (
[Id] bigint identity(1,1) not null,
[FirstName] nvarchar(100) not null,
[LastName] nvarchar(100) not null,
[Title] nvarchar(5) null,
[UserName] nvarchar(100) not null,
[Password] nvarchar(100) not null,
[Inactive] bit null,
[Created] Datetime not null,
[Creator] bigint not null,
[Modified] DateTime null,
[Modifier] bigint null
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[Id] Asc
)
);
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[FK_User_Creator]') AND parent_object_id = OBJECT_ID(N'[User]'))
ALTER TABLE [User] ADD CONSTRAINT [FK_User_Creator] FOREIGN KEY([Creator]) REFERENCES [User]([Id])
GO
INSERT INTO [User] (Creator) Values ([Id] ?)
This is a case when table is empty and first user is going to add in table. Otherwise I don't have issue.
How can I insert Id in creator column with insert statement at the same time?
One way could be using Sequence instead of identity column. The below script might serve the same purpose:
CREATE SEQUENCE dbo.useridsequence
AS int
START WITH 1
INCREMENT BY 1 ;
GO
CREATE TABLE [User] (
[Id] bigint DEFAULT (NEXT VALUE FOR dbo.useridsequence) ,
[FirstName] nvarchar(100) not null,
[LastName] nvarchar(100) not null,
[Title] nvarchar(5) null,
[UserName] nvarchar(100) not null,
[Password] nvarchar(100) not null,
[Inactive] bit null,
[Created] Datetime not null,
[Creator] bigint DEFAULT NEXT VALUE FOR dbo.useridsequence ,
[Modified] DateTime null,
[Modifier] bigint null
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[Id] Asc
)
);
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[FK_User_Creator]') AND parent_object_id = OBJECT_ID(N'[User]'))
ALTER TABLE [User] ADD CONSTRAINT [FK_User_Creator] FOREIGN KEY([Creator]) REFERENCES [User]([Id])
GO
INSERT INTO [User]
(
-- Id -- this column value is auto-generated
FirstName,
LastName,
Title,
UserName,
[Password],
Inactive,
Created,
Creator,
Modified,
Modifier
)
VALUES
(
'Foo',
'Bar',
'Title',
'UserName ',
'Password',
0,
GETDATE(),
DEFAULT,
GETDATE(),
1
)
SELECT * FROM [User] AS u
Result :
The short answer is that you can't do this. And I suggest your model is logically flawed in the first place. Do you intend to define all actual database users (e.g., create user ... for login ...) as rows in [Users]? You need to think about that - but the typical answer is no. If the answer is yes, then you don't need the creator column at all because it is redundant. All you need is the created date - for which you probably should have defined a default.
But if you want to do this, you will need to do it in two steps (and you will need to make the column nullable). You insert a row (or rows) with values for the "real" data columns. Then update those same rows with the identity values generated for id. An example showing different ways to do this
use tempdb;
set nocount on;
CREATE TABLE dbo.[user] (
[user_id] smallint identity(3,10) not null primary key,
[name] nvarchar(20) not null,
[active] bit not null default (1),
[created] Datetime not null default (current_timestamp),
[creator] smallint null
);
ALTER TABLE dbo.[user] ADD CONSTRAINT [fk_user] FOREIGN KEY(creator) REFERENCES dbo.[user](user_id);
GO
-- add first row
insert dbo.[user] (name) values ('test');
update dbo.[user] set creator = SCOPE_IDENTITY() where user_id = SCOPE_IDENTITY()
-- add two more rows
declare #ids table (user_id smallint not null);
insert dbo.[user] (name) output inserted.user_id into #ids
values ('nerk'), ('pom');
update t1 set creator = t1.user_id
from #ids as newrows inner join dbo.[user] as t1 on newrows.user_id = t1.user_id;
select * from dbo.[user] order by user_id;
-- mess things up a bit
delete dbo.[user] where name = 'pom';
-- create an error, consume an identity value
insert dbo.[user](name) values (null);
-- add 2 morerows
delete #ids;
insert dbo.[user] (name) output inserted.user_id into #ids
values ('nerk'), ('pom');
update t1 set creator = t1.user_id
from #ids as newrows inner join dbo.[user] as t1 on newrows.user_id = t1.user_id;
select * from dbo.[user] order by user_id;
drop table dbo.[user];
And I changed the identity specification to demonstrate something few developers realize. It isn't always defined as (1,1) and the next inserted value can jump for many reasons - errors and caching/restarts for example. Lastly, I think you will regret naming a table with a reserved word since references to it will require the use of delimiters. Reduce the pain.

Computed persisted column does not pick index and shows conversion warning in SQL Server 2016 sp1-cu4

I created PK based on identity column a computed persisted column as
('P'+ RIGHT('000000000' + CONVERT([VARCHAR](8), [ID], (0)), (7))) PERSISTED
I created both tables, in the same way, a persisted columns. When I tried to join both persisted columns on inner join, it does not pick the index and warning is being shown
Type conversion expression convert(varchar(8), id, 0) may effect cardinality estimate in version Microsoft SQL Server 2016 -sp1cu4
Table #1:
create table dbo.master_designation
(
id int identity(1,1) not null,
dept_code varchar(8) not null,
user_type_code varchar(8) not null,
desig_code as ('P'+ RIGHT('0000000' + CONVERT([VARCHAR](8), [ID], (0)), (7))) PERSISTED,
name varchar(30) not null,
shortname varchar(30) not null
) on primary
Create index:
create nonclustered index idx2
on master_designation (desig_code);
Table #2:
create table dbo.mapping_employee_designation
(
id int identity(1,1) not null,
emp_code varchar(8) not null,
dept_code varchar(8) not null,
desig_code as ('P'+ RIGHT('0000000' + CONVERT([VARCHAR](8), [ID], (0)), (7))) PERSISTED,
location_code varchar(5) null,
report_to varchar(8) null,
active bit not null
) on primary
Create index
create nonclustered index idx1
on mapping_employee_designation (desig_code);

How can I debug a SQL Server trigger that is giving error "Conversion failed when converting date and/or time from character string"?

I have a C# app with a SQL Server backend. In the backend I have two tables:
MyTable
MyTableHistory
I just added a trigger to put an entry in MyTableHistory when you do an update on MyTable. I am getting and error when I add this trigger:
Conversion failed when converting date and/or time from character
string
Here is the trigger:
CREATE TRIGGER [TU_MyTable]
ON dbo.[MyTable]
AFTER UPDATE
AS
SET NOCOUNT ON
INSERT INTO dbo.[MyTableHistory]
SELECT *
FROM deleted
GO
Here is my table schema
CREATE TABLE dbo.[MyTable]
(
[Id] int IDENTITY NOT NULL
CONSTRAINT [PK_MyTable] PRIMARY KEY,
[Timestamp] NOT NULL,
[IsDeleted] bit NOT NULL,
[Name] nvarchar(200) NOT NULL,
[LastUpdated] datetime NOT NULL,
[LastUpdatedBy] nvarchar(50) NOT NULL
)
GO
and here is the history table schema
CREATE TABLE dbo.[MyTableHistory]
(
[Id] int NOT NULL,
[Timestamp] binary(8) NOT NULL,
[IsDeleted] bit NOT NULL,
CONSTRAINT [PK_MyTableHistory] PRIMARY KEY ([Id], [Timestamp]),
[LastUpdated] datetime NOT NULL,
[LastUpdatedBy] nvarchar(50) NOT NULL,
[Name] nvarchar(200) NOT NULL
)
GO
Is there anyway to figure out what field is causing this issue and is there anyway to debug inside the database trigger to help me diagnose?
The error is due to conversion of NVARCHAR to DATETIME. In MyTable, the column Name is placed before the LastUpdated column. In short, the order of columns in both tables is not the same. You should specify the columns in your INSERT statement.
INSERT INTO MyTableHistory(
Id,
[Timestamp],
IsDeleted,
Name,
LastUpdated,
LastUpdatedBy
)
SELECT
Id,
[Timestamp],
IsDeleted,
Name,
LastUpdated,
LastUpdatedBy
FROM deleted
Doing an insert without a column list is dangerous. Include the list and don't use *:
Insert into dbo.[MyTableHistory]([Id], [Timestamp], [IsDeleted], [LastUpdated],
[LastUpdatedBy], [Name])
SELECT id, [Timestamp], IsDeleted, LastUpdated, LastUpdatedBy, Name
from deleted;
Do not depend on the ordering of columns in a table -- it causes bugs that are hard to find.

Create Table column date format dd-mm-yyyy SQL Server

I have a table where there are 3 columns to store different dates for different purposes. I would like to store the date in dd-mm-yyyy. Below is the code:
create table PoojaDetails
(
PoojaDetailsID int identity constraint pk_PoojaDetailsID Primary Key,
ReceiptNo AS 'PB' + '/' + cast(datepart(yy,getdate()) as varchar(25)) + '/' + RIGHT('000000000' + CAST(PoojaDetailsID AS VARCHAR(10)), 9) ,
ReceiptDate date not null constraint df_ReceiptDate default convert(date,getdate()),
FirstName varchar(100) not null,
LastName varchar(100) not null,
TelNo bigint,
Star char(50) not null,
Rasi char(50) not null,
Gothram char(100) not null,
PoojaDietyMasterID int not null,
Schedule char(1) not null constraint df_schedule default 'F',
PoojaDate date not null constraint df_pdate default convert(date, '29122013'),
PayMode bit not null,
isDonate bit not null constraint df_isDonate default 1,
DonateAmount float(10),
ChequeNo int,
BankName varchar(255),
ChequeDated date,
UserID int,
SupID int,
ChangeDate date,
Remarks varchar(255),
isPrint char(1) constraint df_isPrint default 'N',
isDeleted bit not null constraint df_isDeleted default 1
)
I would like to have the format for:
ReceiptDate
PoojaDate
ChequeDate
ChangeDate
Thanks :)
Your best bet will be to store the date (if you are using SQL 2008, you should use the DATE datatype) in the universal format of yyyymmdd in the database and then use
CONVERT(Date,YourColumn,105)
when reading the data, to get it in the format you desire.
CONVERT(VARCHAR(10), ReceiptDate, 105) AS [DD-MM-YYYY]
CONVERT(VARCHAR(10), PoojaDate, 105) AS [DD-MM-YYYY]
CONVERT(VARCHAR(10), ChequeDate , 105) AS [DD-MM-YYYY]
use this link for referrence....

How to increment a primary key in an insert statement in SQL Server 2005

I need to write an insert statement into a table the columns looks like this
demandtypeid (PK, FK, int, not null)
characvalueid (PK, FK, int, not null)
percentage (int null)
lastuser (varchar(100), null)
lastedited (datetime, null)
Here is the INSERT statement. Notice the there is not values at the
value( , , 'Bob')
as I think that's where the auto-increment command should go
insert into tr_demandtypecharac(demandtypeID, characvalueid, lastuser)
values( , , 'Bob')
Please help with a simple little statement
I just want to know how to manually insert into this table
Here's my table structure:
CREATE TABLE [dbo].[tr_demandtypecharac](
[demandtypeid] [int] NOT NULL,
[characvalueid] [int] NOT NULL,
[percentage] [int] NULL,
[lastuser] [varchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[lastedited] [datetime] NULL,
CONSTRAINT [PK_tr_dtc_pkey] PRIMARY KEY CLUSTERED
(
[demandtypeid] ASC,
[characvalueid] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[tr_demandtypecharac] WITH CHECK
ADD CONSTRAINT [FK_tr_dtc_cvid]
FOREIGN KEY([characvalueid]) REFERENCES [dbo].[tr_characvalue] ([characvalueid])
ALTER TABLE [dbo].[tr_demandtypecharac] WITH CHECK
ADD CONSTRAINT [FK_tr_dtc_dtid]
FOREIGN KEY([demandtypeid]) REFERENCES [dbo].[tr_demandtype] ([demandtypeid])
If you want an int column that is unique and autoincrementing, use the IDENTITY keyword:
CREATE TABLE new_employees
(
id_num int IDENTITY(1,1),
fname varchar (20),
minit char(1),
lname varchar(30)
)
Then when you insert into the table, do not insert anything for that column -- it will autoincrement itself.
Given the CREATE TABLE statement you posted, without auto-increment (aka identity) columns, you would insert providing all columns and values, like this:
insert into tr_demandtypecharac(
demandtypeid, characvalueid,
percentage, lastuser, lastedited)
values(2, 3, 80, 'Bob', '01/01/2012')
If, however, you do make them auto-increment by changing the CREATE TABLE to:
CREATE TABLE [dbo].[tr_demandtypecharac](
[demandtypeid] [int] NOT NULL IDENTITY(1,1),
[characvalueid] [int] NOT NULL IDENTITY(1,1),
[percentage] [int] NULL,
[lastuser] [varchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[lastedited] [datetime] NULL,
CONSTRAINT [PK_tr_dtc_pkey] PRIMARY KEY CLUSTERED
(
[demandtypeid] ASC,
[characvalueid] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
)
Then you would insert providing all non-identity (non-autoincrement) columns like this:
insert into tr_demandtypecharac(
percentage, lastuser,
lastedited)
values(80, 'Bob', '01/01/2012')
However, it is not common to have more than one column as an identity (autoincrement) column, and generally, this column is the only PRIMARY KEY column.
If a column is an autoincement column (which is different than a primary key column) then you omit the column in your insert statement and it will be filled in.
INSERT INTO tr_demandtypecharac (lastuser) VALUES ('Bob')
I had a similar issue and needed to update a purchased database with a set of records. My solution was to find the highest key used so far, then use that as the base of my insert. The core of it was ROWNUMBER() OVER(ORDER BY PART_CODE).
The key is the "recnum" field in the inadjinf table. I determined that the highest current key was 675400 and updated my query to be:
insert into inadjinf (recnum, user_id, adj_type, adj_status, trans_date, part_code, lotqty, uom, cost_ctr, lot, location, to_cost_ctr, to_location, rec_status, to_part_grade, to_rec_status, remarks1, uom_conv)
select ROW_NUMBER() OVER(ORDER BY INVDET.PART_CODE) + 675400 as recnum, 'CHRSTR' as user_id, 'M' as adj_type, 'O' as adj_status, '2020-10-23' as trans_date, invdet.part_code, sum(lotqty) as lotqty, uom,
cost_ctr, lot, location, 'NS' as to_cost_ctr, '500A' as to_location, rec_status, 'Q' as to_part_grade, 'H' as to_rec_status, 'NS Scrap Request from MSobers 10/21/2020' as remarks1, '1' as uom_conv
from invdet
inner join partmstr on invdet.part_code = partmstr.part_code
where
invdet.part_code In
(
'86038',
'1271',
'VM-0021',
'CO-0107',
...
'FO-0391',
'FO-0376'
)
and lot not in (select lot from inadjinf where trans_date = '2020-10-23' and user_id = 'CHRSTR')
group by invdet.part_code, uom, cost_ctr, lot, location, rec_status
My output started with 675401 and went up from there. In the end, I updated the system's internal "next id field" table record.
You should not use int as primary keys... heres a article about it: http://techtrainedmonkey.com/2012/07/30/why-integers-are-lousy-primary-keys/
but if you do... set the field as identity and Sql Server will do it for you... check it out: http://msdn.microsoft.com/en-us/library/ms186775.aspx