SQL Server ALTER COLUMN NOT NULL has no effect - sql

The designers of table SOME_TABLE did not define a primary key, and worse, they set one of the columns that could define the primary key as NULLable (the others are OK).
The data for SOME_TABLE.PrinterPos does not contain any NULL values.
I am writing an upgrade script to apply to ~50 databases.
The following code is failing:
ALTER TABLE dbo.SOME_TABLE
ALTER COLUMN PrinterPos smallint NOT NULL;
ALTER TABLE dbo.SOME_TABLE
ADD CONSTRAINT PK_SOME_TABLE
PRIMARY KEY CLUSTERED (SOME_TABLE_ID ASC, Store_ID ASC, PrinterPos ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];
I get the message
Cannot define PRIMARY KEY constraint on nullable column in table 'SOME_TABLE'.
It looks line the first command is being totally ignored. Although there is no message to indicate this.
To put it in it's own batch, I have tried executing the first command using sp_executesql to no effect.
If I execute the first command in SQL Server Management Studio followed by the second then it executes OK.
I need to get this change fully automated. How can I get this to work via script?

Try adding a GO keyword between the two ALTER TABLE commands

Related

EF Update Statement is Violating a Unique Index

I've got a bit of an issue which has exposed my lack of understanding of Uniqueness Constraints. This is the table in question and there is a unique, non-clustered index on the DataManagerId, SurgeonId and HospitalId columns of a table, as shown in this image (apologies, I don't have Visio):
I am using Entity Framework ("EF") and it has generated an UPDATE statement akin to this:
exec sp_executesql N'UPDATE [dbo].[DataManagerSurgeon]
SET [DataManagerId] = #0, [SurgeonId] = #1, [HospitalId] = #2, [DateAdded] = #3, [DateRescinded] = #4
WHERE ([Id] = #5)',N'#0 int,#1 int,#2 int,#3 datetime2(7),#4 datetime2(7),#5 int',#0=24,#1=221,#2=10001,#3='2018-01-02 08:58:29.1061413',#4='2018-01-02 08:58:29.1061413',#5=122
I'll clean that up a bit for the purposes of this question:
UPDATE [dbo].[DataManagerSurgeon]
SET [DataManagerId] = 24, [SurgeonId] = 221, [HospitalId] = 10001, [DateAdded] = '2018-01-02 08:58:29.1061413', [DateRescinded] = '2018-01-02 08:58:29.1061413'
WHERE [Id] = 122
The problem is, this is seeking to alter data (hence the update statement). And there is already a row with the combination (24,221,10001) for (DataManagerId, HospitalId,SurgeonId). As a result, the database is having none of it:
Violation of UNIQUE KEY constraint 'UC_ManagerSurgeonHospital'. Cannot insert duplicate key in object 'dbo.DataManagerSurgeon'. The duplicate key value is (24, 221, 10001).
As further background, I am only trying to update the DateRescinded column and if I were to write the statement manually, it would look like:
UPDATE [dbo].[DataManagerSurgeon]
SET [DateRescinded] = '2018-01-02 08:58:29.1061413'
WHERE [Id] = 122
I guess I learnt that you can violate a uniqueness constraint with an UPDATE statement, even when identifying the row by primary key in such a way that, at the end of the transaction, the conditions of the uniqueness constraint would still be met.
But my question remains, if EF is generating an UPDATE statement which violates a uniqueness constraint (index), how do I get around that? Note: the least optimal solution would be writing manual SQL and sending that to the database. It can be done, but defeats the purpose of using an ORM like EF.
Also note that I added the following code in the Mapping file for that table to make EF "aware" of the index:
this.HasIndex(t => new { t.DataManagerId, t.SurgeonId, t.HospitalId })
.IsUnique(true)
.IsClustered(false)
.HasName("UC_ManagerSurgeonHospital");
Final addendum - I have been referring to this as an Index and I'm not sure how SQL Server distinguishes these from CONSTRAINTS, but the create statement for such an Index looks like:
ALTER TABLE [dbo].[DataManagerSurgeon] ADD CONSTRAINT [UC_ManagerSurgeonHospital] UNIQUE NONCLUSTERED
(
[DataManagerId] ASC,
[SurgeonId] ASC,
[HospitalId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
I only mention that because people might ask why I implemented it as an Index and not a CONSTRAINT. But apparently, this is how you do multi-column constraints in SQL Server.
Thanks

Why does an insert that groups by the primary key throw a primary key constraint violation error?

I have an insert statement that's throwing a primary key error but I don't see how I could possibly be inserting duplicate key values.
First I create a temp table with a primary key.
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED //Note: I've tried committed and uncommited, neither materially affects the behavior. See screenshots below for proof.
IF (OBJECT_ID('TEMPDB..#P')) IS NOT NULL DROP TABLE #P;
CREATE TABLE #P(idIsbn INT NOT NULL PRIMARY KEY, price SMALLMONEY, priceChangedDate DATETIME);
Then I pull prices from the Prices table, grouping by idIsbn, which is the primary key in the temp table.
INSERT INTO #P(idIsbn, price, priceChangedDate)
SELECT idIsbn ,
MIN(lowestPrice) ,
MIN(priceChangedDate)
FROM Price p
WHERE p.idMarketplace = 3100
GROUP BY p.idIsbn
I understand that grouping by idIsbn by definition makes it unique. The idIsbn in the prices table is: [idIsbn] [int] NOT NULL.
But every once in a while when I run this query I get this error:
Violation of PRIMARY KEY constraint 'PK__#P________AED35F8119E85FC5'. Cannot insert duplicate key in object 'dbo.#P'. The duplicate key value is (1447858).
NOTE: I've got a lot of questions about timing. I will select this statement, press F5, and no error will occur. Then I'll do it again, and it will fail, then I'll run it again and again and it will succeed a couple times before it fails again. I guess what I'm saying is that I can find no pattern for when it will succeed and when it won't.
How can I be inserting duplicate rows if (A) I just created the table brand new before inserting into it and (B) I'm grouping by the column designed to be the primary key?
For now, I'm solving the problem with IGNORE_DUP_KEY = ON, but I'd really like to know the root cause of the problem.
Here is what I'm actually seeing in my SSMS window. There is nothing more and nothing less:
##Version is:
Microsoft SQL Server 2008 (SP3) - 10.0.5538.0 (X64)
Apr 3 2015 14:50:02
Copyright (c) 1988-2008 Microsoft Corporation
Standard Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1)
Execution Plan:
Here is an example of what it looks like when it runs fine. Here I'm using READ COMMITTED, but it doesn't matter b/c I get the error no matter whether I read it committed or uncommited.
Here is another example of it failing, this time w/ READ COMMITTED.
Also:
I get the same error whether I'm populating a temp table or a
persistent table.
When I add option (maxdop 1) to the end of the insert it seems to fail every time, though I can't be exhaustively sure of that b/c I can't run it for infinity. But it seems to be the case.
Here is the definition of the price table. Table has 25M rows. 108,529 updates in the last hour.
CREATE TABLE [dbo].[Price](
[idPrice] [int] IDENTITY(1,1) NOT NULL,
[idIsbn] [int] NOT NULL,
[idMarketplace] [int] NOT NULL,
[lowestPrice] [smallmoney] NULL,
[offers] [smallint] NULL,
[priceDate] [smalldatetime] NOT NULL,
[priceChangedDate] [smalldatetime] NULL,
CONSTRAINT [pk_Price] PRIMARY KEY CLUSTERED
(
[idPrice] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [uc_idIsbn_idMarketplace] UNIQUE NONCLUSTERED
(
[idIsbn] ASC,
[idMarketplace] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
And the two non-clustered indexes:
CREATE NONCLUSTERED INDEX [IX_Price_idMarketplace_INC_idIsbn_lowestPrice_priceDate] ON [dbo].[Price]
(
[idMarketplace] ASC
)
INCLUDE ( [idIsbn],
[lowestPrice],
[priceDate]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_Price_idMarketplace_priceChangedDate_INC_idIsbn_lowestPrice] ON [dbo].[Price]
(
[idMarketplace] ASC,
[priceChangedDate] ASC
)
INCLUDE ( [idIsbn],
[lowestPrice]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
You hadn't supplied your table structure.
This is a repro with some assumed details that causes the problem at read committed (NB: now you have supplied the definition I can see in your case updates to the priceChangedDate column will move rows around in the IX_Price_idMarketplace_priceChangedDate_INC_idIsbn_lowestPrice index if that's the one being seeked)
Connection 1 (Set up tables)
USE tempdb;
CREATE TABLE Price
(
SomeKey INT PRIMARY KEY CLUSTERED,
idIsbn INT IDENTITY UNIQUE,
idMarketplace INT DEFAULT 3100,
lowestPrice SMALLMONEY DEFAULT $1.23,
priceChangedDate DATETIME DEFAULT GETDATE()
);
CREATE NONCLUSTERED INDEX ix
ON Price(idMarketplace)
INCLUDE (idIsbn, lowestPrice, priceChangedDate);
INSERT INTO Price
(SomeKey)
SELECT number
FROM master..spt_values
WHERE number BETWEEN 1 AND 2000
AND type = 'P';
Connection 2
Concurrent DataModifications that move a row from the beginning of the seeked range (3100,1) to the end (3100,2001) and back again repeatedly.
USE tempdb;
WHILE 1=1
BEGIN
UPDATE Price SET SomeKey = 2001 WHERE SomeKey = 1
UPDATE Price SET SomeKey = 1 WHERE SomeKey = 2001
END
Connection 3 (Do the insert into a temp table with a unique constraint)
USE tempdb;
CREATE TABLE #P
(
idIsbn INT NOT NULL PRIMARY KEY,
price SMALLMONEY,
priceChangedDate DATETIME
);
WHILE 1 = 1
BEGIN
TRUNCATE TABLE #P
INSERT INTO #P
(idIsbn,
price,
priceChangedDate)
SELECT idIsbn,
MIN(lowestPrice),
MIN(priceChangedDate)
FROM Price p
WHERE p.idMarketplace = 3100
GROUP BY p.idIsbn
END
The plan has no aggregate as there is a unique constraint on idIsbn (a unique constraint on idIsbn,idMarketplace would also work) therefore the group by can be optimised out as there are no duplicate values.
But at read committed isolation level shared row locks are released as soon as the row is read. So it is possible for a row to move places and be read a second time by the same seek or scan.
The index ix doesn't explicitly include SomeKey as a secondary key column but as it is not declared unique SQL Server silently includes the clustering key behind the scenes, hence updating that column value can move rows around in it.

create primary key on existing table with data

As part of a migration project, we have imported data from a JDE iSeries DB2 database. An SSIS package was created to create the destination tables and import data. The import went successfully.
Now comes the problem - The customer wants Primary Keys created in the destination DB (SQL 2008 R2). The problem table in this case, would be one table that has 104 columns and 7.5 million rows of data. The PK required for this table is composite and has 7 columns.
We are considering this :
BEGIN TRANSACTION
GO
ALTER TABLE [dbo].[F0911] ADD CONSTRAINT [F0911_PK] PRIMARY KEY CLUSTERED
(
[GLDCT] ASC,
[GLDOC] ASC,
[GLKCO] ASC,
[GLDGJ] ASC,
[GLJELN] ASC,
[GLLT] ASC,
[GLEXTL] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
COMMIT
or this:
-- Rename existing tables
sp_RENAME '[F0911]' , '[F0911_old]'
GO
-- Create new table
SELECT * INTO F0911 FROM F0911_old WHERE 1=0
GO
--Create PK constraints
ALTER TABLE [dbo].[F0911] ADD CONSTRAINT [F0911_PK] PRIMARY KEY CLUSTERED
(
[GLDCT] ASC,
[GLDOC] ASC,
[GLKCO] ASC,
[GLDGJ] ASC,
[GLJELN] ASC,
[GLLT] ASC,
[GLEXTL] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
--Insert data into new tables
INSERT INTO F0911
SELECT * FROM F0911_old
GO
-- Drop old tables
DROP TABLE F0911_old
GO
Which would be a more efficient approach, performance wise? I have a gut feeling that both are the same and even the first approach does the same thing as the second one does, implicitly. Is this understanding correct?
Please note that all these columns already exist in the table and we cannot modify the table definition.
Thanks,
Raj
They're the same. The effect of creating a clustered index is to arrange the pages which will happen in both cases. For non-clustered indexes it will help to disable the index and then turn it back on and rebuilding it.
I think the first approach is right, but I don't understand the reason of BEGIN Transaction and END transaction. I don't think Transaction keyword is necessary because you are not modifying data of the table. Transaction is used where we have to lock the data and we are modifying real time data so the old data is not used.

How to set a unique field in a already existing SQL Server database?

I have a question about unique fields in a SQL Server database.
I have a already existing database with an index and some foreign keys and so on.
Now I need to set one column e.g. name, to be UNIQUE.
How to do this?
ALTER TABLE yourtable
ADD CONSTRAINT
ix_uniquename UNIQUE NONCLUSTERED
(
name
)
WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
or view the design of the table, right click and select Indexes/Keys
Ff you're talking about adding an index, rather than a constraint then the script is something like:
CREATE UNIQUE INDEX ix_uniquename ON dbo.yourtable([name])

Drop and recreate primary key

I want to change a primary key/index on a database table to change the index option ignore_dup_key to 'on'.
According to this question " Can I set ignore_dup_key on for a primary key? " I need to drop the index and create a new one.
Before I drop the index, how can I get the command to recreate it? Then I'll just change the ignore_dup_key option.
Right click the primary key in SSMS, and choose Script -> As Create -> To New Window
As indicated by a commenter on another related question, you can enable IGNORE_DUP_KEY for the table:
ALTER TABLE [TableName] REBUILD WITH (IGNORE_DUP_KEY = ON);
and revert:
ALTER TABLE [TableName] REBUILD WITH (IGNORE_DUP_KEY = OFF);
I did not think SQL would allow a PK with IGNORE_DUP_KEY = on so I tested on SQL 2008 R2.
Via script could create a table with PK IGNORE_DUP_KEY = on. But even with drop and create could not change a PK from off to on. What is interesting is the script ran with no error but it did not change the PK from off to on. You may find different results in your environment.
To Drop the Index
You can run
DROP INDEX [IndexName] ON [TableName]
To Create the index with Ignore_Dup_Key option
CREATE UNIQUE INDEX [IndexName] ON [TableName]([ColumnNam])
WITH (IGNORE_DUP_KEY = ON)