Can not update newly created column - sql-server

Hello i am altering an SQL table in which i want to add a new column A , and , set its default value based on the value of another column B .How could i do that ?
I have tried so far:
IF NOT EXISTS(SELECT *FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='T' AND COLUMN_NAME='B')
BEGIN
ALTER TABLE T ADD B bit default 0;
UPDATE T
SET B = A
I keep getting the error:
Msg 207, Level 16, State 1, Line 23
Invalid column name 'B'.
P.S : I want to do the two statements atomically ( alter table and update the newly created column)
I was expecting this to be a problem with Intellisense in MSSQL but to be able to run commands in sequential order.
Update
I have also tried to separate into two statements,still to no avail:
IF NOT EXISTS(SELECT *FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME='T' AND COLUMN_NAME='B')
ALTER TABLE T ADD B bit default NULL;
UPDATE T
SET B = A
WHERE B = NULL
END

Before a batch is run, it is parsed by the data engine and any syntax errors and invalid object references are raised. Unfortunately, some DDL statements aren't counted for latter statements. Firstly, if we CREATE and try to INSERT into a table, this works fine:
CREATE TABLE dbo.YourTable (A bit);
INSERT INTO dbo.YourTable (A)
VALUES(1),(0),(1),(1),(0);
On the other hand, if we then try to (in a separate batch) ALTER the table and then UPDATE the value of said column, the batch will fail (as in your example):
ALTER TABLE YourTable ADD B bit default 0;
UPDATE YourTable
SET B = A
One method is to separate the statements into separate batches, however, you won't be able to wrap both statements in your IF then. Therefore, in this scenario, it would be better to have the statement be parsed in the batch later, by executing it with sys.sp_executesql:
IF NOT EXISTS(SELECT *FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='T' AND COLUMN_NAME='B')
BEGIN
ALTER TABLE T ADD B bit default 0;
EXEC sys.sp_executesql N'UPDATE T SET B = A;';
END

You need the "GO" command between alter and update.
ALTER TABLE YourTable ADD B bit default 0;
GO
UPDATE YourTable
SET B = A
or
IF NOT EXISTS(SELECT *FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='T' AND COLUMN_NAME='B')
BEGIN
ALTER TABLE T ADD B bit default 0;
EXEC ('UPDATE product SET B = 1')
END
IF NOT EXISTS(SELECT *FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='T' AND COLUMN_NAME='C')
BEGIN
ALTER TABLE T ADD C varchar(20) default null;
EXEC ('UPDATE product SET C = ''test''')
END

Related

SQL Server trigger : when a new row is inserted if column A is blank or null SET value to X

I am trying to create a SQL Server trigger so that when a new row is inserted in CONTACT, if column Contact_Type is blank '' or NULL, it should be set to PCON.
The trigger I have created doesn't work. I have not created any triggers before so I am well out of my depth, any help would be greatly appreciated.
I assume I am messing something up in my where clause in regards to the inserted table?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[EJ_ConTACT_TYPE_DefaultValue_INS]
ON [dbo].[CONTACT]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #defaultvalue varchar(6) = 'PCON'
UPDATE [dbo].[CONTACT]
SET Contact_Type = #defaultvalue
FROM inserted i
WHERE [dbo].[CONTACT].[Contact_Type] = i.Contact_Type
AND (i.[Contact_Type] = '' OR i.[Contact_Type] = NULL)
END
GO
Personally, like I mention in the comments, I would simply create a default constraint:
ALTER TABLE dbo.Contact ADD CONSTRAINT df_Contact_ContactType DEFAULT 'PCON' FOR Contact_Type;
Then you simply omit the column in your INSERT and the column will have the value. For example:
INSERT INTO dbo.Contact (Column1, Column2, Column3)
VALUES('abc',1,'20200916');
This will cause the column Contact_Type to have the value 'PCON'.
As for why your trigger isn't working, one problem is because of your WHERE. [dbo].[CONTACT].[ConTACT_TYPE] = i.ConTACT_TYPE will never be true is the column ConTACT_TYPE (does your column really have that odd casing in it's name..?) if it's value is NULL; NULL is equal to nothing including NULL itself. This also applies to your clause OR [ConTACT_TYPE] = NULL. Finally, I doubt that that value is unique, so it'll update rows it shouldn't.
If you "must" do this in a TRIGGER (I suggest you don't), use the Primary Key for the JOIN. Also, don't use 3+ part naming for columns: 3+ part naming on Columns will be Deprecated. This results in the below:
CREATE TRIGGER [dbo].[EJ_Contact_Type_DefaultValue] ON [dbo].[contact]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
--DECLARE #defaultvalue varchar(6) = 'PCON'
UPDATE C
SET Contact_type = 'PCON'
FROM dbo.Contact C
JOIN inserted i ON C.ID = i.ID
WHERE i.Contact_type = ''
OR i.Contact_type IS NULL;
END;
GO

Can I run an update on a column right after adding it? [duplicate]

Does anyone see what's wrong with this code for SQL Server?
IF NOT EXISTS(SELECT *
FROM sys.columns
WHERE Name = 'OPT_LOCK'
AND object_ID = Object_id('REP_DSGN_SEC_GRP_LNK'))
BEGIN
ALTER TABLE REP_DSGN_SEC_GRP_LNK
ADD OPT_LOCK NUMERIC(10, 0)
UPDATE REP_DSGN_SEC_GRP_LNK
SET OPT_LOCK = 0
ALTER TABLE REP_DSGN_SEC_GRP_LNK
ALTER COLUMN OPT_LOCK NUMERIC(10, 0) NOT NULL
END;
When I run this, I get:
Msg 207, Level 16, State 1, Line 3
Invalid column name 'OPT_LOCK'.
on the update command.
Thanks.
In this case you can avoid the problem by adding the column as NOT NULL and setting the values for existing rows in one statement as per my answer here.
More generally the problem is a parse/compile issue. SQL Server tries to compile all statements in the batch before executing any of the statements.
When a statement references a table that doesn't exist at all the statement is subject to deferred compilation. When the table already exists it throws an error if you reference a non existing column. The best way round this is to do the DDL in a different batch from the DML.
If a statement both references a non existing column in an existing table and a non existent table the error may or may not be thrown before compilation is deferred.
You can either submit it in separate batches (e.g. by using the batch separator GO in the client tools) or perform it in a child scope that is compiled separately by using EXEC or EXEC sp_executesql.
The first approach would require you to refactor your code as an IF ... cannot span batches.
IF NOT EXISTS(SELECT *
FROM sys.columns
WHERE Name = 'OPT_LOCK'
AND object_ID = Object_id('REP_DSGN_SEC_GRP_LNK'))
BEGIN
ALTER TABLE REP_DSGN_SEC_GRP_LNK
ADD OPT_LOCK NUMERIC(10, 0)
EXEC('UPDATE REP_DSGN_SEC_GRP_LNK SET OPT_LOCK = 0');
ALTER TABLE REP_DSGN_SEC_GRP_LNK
ALTER COLUMN OPT_LOCK NUMERIC(10, 0) NOT NULL
END;
The root cause of the error is the newly added column name is not reflected in the sys.syscolumns and sys.columns table until you restart SQL Server Management Studio.
For your information,you can replace the IF NOT EXISTS with the COL_LENGTH function. It takes two parameters,
Table Name and
Column you are searching for
If the Column is found then it returns the range of the datatype of the column Ex: Int (4 bytes), when not found then it returns a NULL.
So, you could use this as follows and also combine 3 Statements into one.
IF (SELECT COL_LENGTH('REP_DSGN_SEC_GRP_LNK','OPT_LOCK')) IS NULL
BEGIN
ALTER TABLE REP_DSGN_SEC_GRP_LNK
ADD OPT_LOCK NUMERIC(10, 0) NOT NULL DEFAULT 0
END;
Makes it simpler.

Continue inserting data in tables skipping duplicate data issue

set xact_abort off;
begin tran
DECLARE #error int
declare #SQL nvarchar(max)
set #SQL=N'';
select #SQL=some select query to fetch insert scripts
begin try
exec sp_executesql #SQL
commit
end try
begin catch
select #error=##Error
if #error=2627
begin
continue inserting data
end
if #error<>2627
begin
rollback
end
end catch
I am unable to continue inserting data when any duplicate data comes. Is there any alternative way to continue running SQL queries irrespective of duplicate data? I don not want to alter the index or table.
I am unable to continue inserting data when any duplicate data comes. Is there any alternative way to continue running sql queries irrespective of duplicate data. I dont want to alter the index or table.
What you can do is change the insert scripts as you call them, in this pseudo statement:
select #SQL=some select query to fetch insert scripts
Change the generation script: instead of generating INSERT INTO ... VALUES(...) statements, generate IF NOT EXISTS(...) INSERT INTO ... VALUES(...) statements
These insert statements should first check if a key already exists in the table. If your insert statements are of the form
INSERT INTO some_table(keycol1,...,keycolN,datacol1,...,datacolM)VALUES(keyval1,...,keyvalN,dataval1,...,datavalM);
You can rewrite those as:
IF NOT EXISTS(SELECT 1 FROM some_table WHERE keycol1=keyval1 AND ... AND keycolN=keyvalN)
INSERT INTO some_table(keycol1,...,keycolN,datacol1,...,datacolM)VALUES(keyval1,...,keyvalN,dataval1,...,datavalM);
Change the generation script: instead of generating INSERT INTO ... SELECT ..., generate INSERT INTO ... SELECT ... WHERE NOT EXISTS(...) statements
You can change these statements to only insert if the key does not exist in the table yet. Suppose your insert statements are of the form:
INSERT INTO some_table(keycol1,...,keycolN,datacol1,...,datacolN)
SELECT _keycol1,...,_keycolN,datacol1,...,datacolN
FROM <from_clause>;
You can rewrite those as:
INSERT INTO some_table(keycol1,...,keycolN,datacol1,...,datacolN)
SELECT _keycol1,...,_keycolN,datacol1,...,datacolN
FROM <from_clause>
WHERE NOT EXISTS(SELECT 1 FROM some_table WHERE keycol1=_keycol1 AND ... AND keycolN=_keycolN);
Replace the target table name in #SQL with a temporary table (a so-called staging table), then insert from the temporary table to the target table using WHERE NOT EXISTS(...)
This way you would not have to change the insert generation script. First create a temporary table that has the exact same structure as the target table (not including the primary key). Then replace all instances of the target table name in #SQL with the name of the temporary table. Run the #SQL and afterwards insert from the temporary table to the target table using a WHERE NOT EXISTS(...).
Suppose the target table is named some_table, with key columns key_col1,...,key_colN and data columns datacol1, ..., datacolM.
SELECT * INTO #staging_table FROM some_table WHERE 1=0; -- create staging table with same columns as some_table
SET #SQL=REPLACE(#SQL,'some_table','#staging_table');
EXEC sp_executesql #SQL;
INSERT INTO some_table(keycol1,...,keycolN,datacol1,...,datacolN)
SELECT st.keycol1,...,st.keycolN,st.datacol1,...,st.datacolN
FROM #staging_table AS st
WHERE NOT EXISTS(SELECT 1 FROM some_table WHERE keycol1=st.keycol1 AND ... AND keycolN=st.keycolN);
DROP TABLE #staging_table;

If length of nvarchar is x then alter it

I have been making changes to a database and am creating a script so that it can be run on the original database to inherit all the changes I have made.
I need to alter the length of an NVARCHAR, but want to check its length before I alter the column. I am struggling with the syntax a bit, could anyone help me with this?
SELECT LEN(colName) AS MyLength FROM tblName
IF MyLength = 60
BEGIN
ALTER TABLE tblName
ALTER COLUMN colName nvarchar(140) NOT NULL
END
GO
If I attempt to run this query in SQL Server Management Studio I get an error message that says:
Invalid column name 'MyLength'.
try this:
IF (select max(LEN(colName)) from tblName) = 60
BEGIN
ALTER TABLE tblName
ALTER COLUMN colName nvarchar(140) NOT NULL
END
GO
Following up on podiluska's answer, the complete command you're looking for is:
IF (SELECT [CHARACTER_MAXIMUM_LENGTH] FROM INFORMATION_SCHEMA.COLUMNS WHERE [COLUMN_NAME] = 'ColumnName' AND [TABLE_NAME] = 'TableName') < 140
BEGIN
ALTER TABLE [dbo].[TableName] ALTER COLUMN [ColumnName] NVARCHAR(140) NOT NULL
END
To reiterate, using MAX(LEN()) will only give you the length of the longest string in the column, not the maximum allowed length!
You need to define it as a variable
declare #i int
select #i = len(colname) from table
But this won't work anyway as it returns the length of the data, not the column, which being a varchar, is inherently variable. Try looking at the sysobjects and syscolumns tables or Information_Schema.Columns instead

Test Column exists, Add Column, and Update Column

I'm trying to write a SQL Server database update script. I want to test for the existence of a column in a table, then if it doesn't exist add the column with a default value, and finally update that column based on the current value of a different column in the same table. I want this script to be runnable multiple times, the first time updating the table and on subsequent runs the script should be ignored. My script currently looks like the following:
IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'PurchaseOrder' AND COLUMN_NAME = 'IsDownloadable')
BEGIN
ALTER TABLE [dbo].[PurchaseOrder] ADD [IsDownloadable] bit NOT NULL DEFAULT 0
UPDATE [dbo].[PurchaseOrder] SET [IsDownloadable] = 1 WHERE [Ref] IS NOT NULL
END
SQL Server returns error "Invalid column name 'IsDownloadable'", i.e. I need to commit the DDL before I can update the column. I've tried various permutations but I'm getting nowhere fast.
This script will not run successfully unless the column already exists, which is exactly when you don't need it.
SQL Scripts have to be parsed before they can be executed. If the column doesn't exist at the time the script is parsed, then the parsing will fail. It doesn't matter that your scripts creates the column later on; the parser has no way of knowing that.
You need to put in a GO statement (batch separator) if you want to access a column that you just added. However, once you do that, you can no longer maintain any control flow or variables from the previous batch - it's like running two separate scripts. This makes it tricky to do both DDL and DML, conditionally, at the same time.
The simplest workaround, which I'd probably recommend for you because your DML is not very complex, is to use dynamic SQL, which the parser won't try to parse until "runtime":
IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'PurchaseOrder' AND COLUMN_NAME = 'IsDownloadable')
BEGIN
ALTER TABLE [dbo].[PurchaseOrder] ADD
[IsDownloadable] bit NOT NULL DEFAULT 0
EXEC sp_executesql
N'UPDATE [dbo].[PurchaseOrder] SET [IsDownloadable] = 1 WHERE [Ref] IS NOT NULL'
END
I have often been annoyed by this problem myself, and unfortunately the solution suggested in Aaronaught's answer quickly becomes messy when #parameters and 'strings' are involved. However, I have found a different workaround by exploiting the usage of synonyms:
IF(COL_LENGTH('MyTable', 'NewCol') IS NULL)
BEGIN
ALTER TABLE MyTable ADD NewCol VARCHAR(16) NULL;
CREATE SYNONYM hack FOR MyTable;
UPDATE hack SET NewCol = 'Hello ' + OldCol;
DROP SYNONYM hack;
ALTER TABLE MyTable ALTER COLUMN NewCol VARCHAR(16) NOT NULL;
END
Try adding a "GO" statement after the ALTER TABLE.
It was news to me, but it says here that all statements in a batch (those preceeding the GO) are compiled into one query plan.) Withou no GO in the SQL, the entire plan is effectively one query.
EDIT: Since GO gives a syntax error (which seemed strange to me), I created something similar, and found this worked
declare #doUpdate bit;
SELECT #doUpdate = 0;
IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'PurchaseOrder' AND COLUMN_NAME = 'IsDownloadable')
BEGIN
SELECT #doUpdate=1
END
IF #doUpdate<>0
ALTER TABLE [dbo].[PurchaseOrder] ADD [IsDownloadable] bit NOT NULL DEFAULT 0
IF #doUpdate<>0
UPDATE [dbo].[PurchaseOrder] SET [IsDownloadable] = 1 WHERE [Ref]=0
COMMIT TRAN
Although the accepted answer does work, for a more complicated case, you can use a temp table to persist data past the GO statement. just make sure you remember to clean it up after.
For example:
-- Create a tempTable if it doesn't exist. Use a unique name here
IF OBJECT_ID('tempdb..#tempTable') IS NOT NULL DROP TABLE #tempTable
CREATE TABLE #tempTable (ColumnsCreated bit)
-- Create your new column if it doesn't exist. Also, insert into the tempTable.
IF NOT EXISTS (
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'targetTable' AND COLUMN_NAME = 'newColumn')
BEGIN
INSERT INTO #tempTable VALUES (1)
ALTER TABLE .dbo.targetTable ADD newColumn [SMALLINT] NULL ;
END
GO
-- If the tempTable was inserted into, our new columns were created.
IF (EXISTS(SELECT * FROM #tempTable))
BEGIN
-- Do some data seeding or whatever
END
-- Clean up - delete the tempTable.
IF OBJECT_ID('tempdb..#tempTable') IS NOT NULL DROP TABLE #tempTable
If you're using at least SQL Server 2008, you can specify WITH VALUES at the time of column addition, which will populate existing records with the default value for that attribute.
IF COL_LENGTH('[dbo].[Trucks]', 'Is4WheelDrive') IS NULL
BEGIN
ALTER TABLE [dbo].[Trucks]
ADD [Is4WheelDrive] BIT NULL DEFAULT 1
WITH VALUES;
END
This will add a new column, [Is4WheelDrive], to the table [dbo].[Trucks] if that column doesn't exist. The new column, if added, will populate existing records with the default value, which in this case is a BIT value of 1. If the column already existed, no records would be modified.

Resources