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
Related
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
I have gone thorough these previous questions Q1, Q2, Q3. Using this I can catch my exact constraint name. But it is not enough for me.
For example I have done somthing
ALTER TABLE dbo.Documents ADD ShowOnHandset BIT NOT NULL DEFAULT 'FALSE'
Here automatically my constrant named by the machine was DF__Documents__ShowO__7AB2122C on my machine. But I have run the same script in multiple PC, on those PC those constraint are almost same except the last hashed value. DF__Documents__ShowO__54A20B0D DF__Documents__ShowO__5D5216D7
I have seen that the last 8 bit hashed value is not similar. But I need to remove this constraint from all table and I want to replace them with
ALTER TABLE dbo.Documents ADD ShowOnHandset BIT NOT NULL DEFAULT ((1))
But I can't identify the the exact constraint name, so how can I drop it using a single script?
I have found hard-coded solution by those mentioned questions. But I need a single script to do it. Please help me to solve this.
I can catch the constraint name using this code
select name from sys.objects where name like 'DF__Documents__ShowO%'
I know the way how to delete it. Here it is.
ALTER TABLE dbo.AppSystems DROP constraint [constraint_name]
But I am unable to do it. Because I couldn't put the value constraint_name even if I can caught it. So how could I put this name here to drop it.
Update Modified the Question.
Run this and output as text, then copy the result and run in another query window
Fritz with the code below to ADD the updated constraint:
;With cte As (select object_id From sys.objects where name like 'DF__Documents__ShowO%')
Select 'Alter Table ' + Object_Name(df.object_id) + N' Add Constraint Default (1) For ShowOnHandset'
From sys.default_constraints As df
Join cte As c
On c.object_id = df.object_id
This deletes the constraints
;With cte As (select object_id From sys.objects where name like 'DF__Documents__ShowO%')
Select 'Alter Table ' + Object_Name(df.object_id) + N' Drop Constraint [' + df.Name + ']'
From sys.default_constraints As df
Join cte As c
On c.object_id = df.object_id
In MS SQL you can specify constraint name explicit:
ALTER TABLE dbo.Documents ADD ShowOnHandset BIT NOT NULL CONSTRAINT DF_Documents_ShowOnHandset DEFAULT 'FALSE'
Try dynamic query:
Declare #MyVariable varchar(max)
Declare #Variable varchar(max)
Set #MyVariable=(Select name from sys.objects
where parent_object_id=Object_ID('Documents','U') and name like 'DF__Documents__Show%')
Set #variable='ALTER TABLE dbo.AppSystems DROP constraint' +#MyVariable
Exec(#variable)
I'm creating CRUD procedures that duplicate a legacy program that generates a unique ID based on a 'Next ID' field in a separate table. Rather than duplicate the use of a separate table I have written a stored procedure that reads the number of rows in the table.
CREATE PROCEDURE [TLA_CreateItem]
#SiteReference varchar(50)
,#ItemID varchar(4)
,#NewUniqueID varchar(68) OUTPUT
AS
BEGIN
DECLARE #Rows varchar(12)
SET #Rows = (CONVERT(varchar(12), (SELECT Count(UniqueID) FROM [TLA_Items]) + 1))
SET #NewUniqueID = #ItemID + #SiteReference + #Rows
INSERT INTO [TLA_Items] ([ItemID], [UniqueID])
VALUES (#ItemID, #NewUniqueID)
SELECT #NewUniqueID
END
I've simplified the code above but what's not shown is that the TLA_Items table also has an IDENTITY column and that it needs to work with SQL Server 2008.
The UniqueID field has to match the pattern of the legacy program: ItemID + SiteReference + (integer representing number of previous records)
However when testing this I've found a flaw in my logic. If rows are deleted then it's possible to create a unique Id which matches an existing row. This doesn't happen in the legacy system as rows are rarely deleted and the separate table stores the next number in the sequence.
Other than store the next ID value in a separate table, is there a better technique, to create a unique ID that matches the legacy pattern?
You could have your procedure store only the prefix (#ItemID + #SiteReference) into UniqueID and use a FOR INSERT trigger to append the IDENTITY value as the rows component immediately after the row is inserted, something like this:
CREATE TRIGGER TLA_Items_Adjust
ON dbo.TLA_Items
FOR INSERT
AS
BEGIN
UPDATE t
SET t.UniqueID = i.UniqueID + CAST(t.IdentityColumn AS varchar(10))
FROM dbo.TLA_Items AS t
INNER JOIN inserted AS i
ON t.IdentityColumn = i.IdentityColumn
;
END
To read and return the newly generated UniqueID value as the OUTPUT parameter as well as a row, you could use a table variable and the OUTPUT clause in the INSERT statement, like this:
CREATE PROCEDURE [TLA_CreateItem]
#SiteReference varchar(50)
,#ItemID varchar(4)
,#NewUniqueID varchar(68) OUTPUT
AS
BEGIN
DECLARE #GeneratedUniqueID TABLE (UniqueID varchar(68));
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueID])
OUTPUT inserted.UniqueID INTO #GeneratedUniqueID (UniqueID)
VALUES (#ItemID, #ItemID + #SiteReference);
SELECT #NewUniqueID = UniqueID FROM #GeneratedUniqueID;
SELECT #NewUniqueID;
END
Although instead of using OUTPUT you could probably just read the value from the row matching the SCOPE_IDENTITY() result:
CREATE PROCEDURE [TLA_CreateItem]
#SiteReference varchar(50)
,#ItemID varchar(4)
,#NewUniqueID varchar(68) OUTPUT
AS
BEGIN
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueID])
VALUES (#ItemID, #ItemID + #SiteReference);
SELECT #NewUniqueID = UniqueID
FROM dbo.TLA_Items
WHERE IdentityColumn = SCOPE_IDENTITY();
SELECT #NewUniqueID;
END
Here is another option, but please bear in mind that it would affect existing UniqueID values.
If you can afford a slight change to the table schema, you could add a column called something like UniqueIDPrefix:
ALTER TABLE dbo.TLA_Items
ADD UniqueIDPrefix varchar(56) NOT NULL;
and redefine the UniqueID column to be a computed column:
ALTER TABLE dbo.TLA_Items
DROP COLUMN UniqueID;
GO
ALTER TABLE dbo.TLA_Items
ADD UniqueID AS UniqueIDPrefix + CAST(IdentiyColumn AS varchar(12));
In your stored procedure, you would then need to populate UniqueIDPrefix instead of UniqueID (with just the result of #ItemID + #SiteReference)
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueIDPrefix])
VALUES (#ItemID, #ItemID + #SiteReference);
and read the value of UniqueID using either OUTPUT or SCOPE_IDENTITY(), as in my other answer.
It sounds like you are on SQL 2008, but if you were on 2012, you could use a sequence to store an incrementing value.
How about never delete? You could add a flag to the table for logical deletes.
I tried below in sql server management, in a single query.
alter table add column amount2
update table set amount2=amount
I am getting column amount2 not found.
Can anyone tell me why this error?
That is not valid syntax (misses table name and column datatype) but in management studio use the batch separator GO between adding a column to an existing table and statements referencing the new column anyway.
Or alternatively you can use EXEC to execute it in a child batch.
SQL Server tries to compile all statements in the batch before execution and this will fail when it encounters the statement using this column.
There's a couple things wrong here.
The correct syntax for adding a column is MSDN - ALTER TABLE
ALTER TABLE [TableName] ADD [ColumnNAME] [DataType]
'Table' is a Reserved Keyword in SQL Server, although it is possible to have a table named 'Table'. You need to include brackets when referencing it.
SELECT * FROM [Table]
All together, you need
ALTER TABLE [Table] ADD [Amount2] INT
GO -- See Martin's answer for reason why 'GO' is needed here
UPDATE [Table] SET [Amount2] = [Amount]
You can get around this problem like this:
-- Alter the table and add new column "NewColumn"
ALTER TABLE [MyTable] ADD [NewColumn] CHAR(1) NULL;
-- Set the value of NewColumn
EXEC ('UPDATE [MyTable] SET [NewColumn] = ''A'' ');
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.