SQL Server : error on column add - sql

I am trying to copy rows from one column to another. Execution flow is simple.
Check if column is exist.
If not - add column
Define cursor and populate rows for new column
Set column to NOT NULL.
Unfortunately I am getting this type of error. It says that column does not exist. But I've created it and committed transaction. What could be a reason? Thanks!
Msg 207, Level 16, State 1, Line 29
Invalid column name 'Field_Name'.
Code:
begin transaction;
IF NOT EXISTS(SELECT * FROM sys.columns
WHERE Name = N'Field_Name' and Object_ID = Object_ID(N'dbo.Table_Name'))
BEGIN
ALTER TABLE [dbo].[Table_Name]
ADD [Field_Name] VARCHAR(255)
END
commit;
DECLARE
#var1 int,
#var2 varchar(255)
DECLARE copy_cursor CURSOR FOR
SELECT id, Name
FROM Table_Name
OPEN copy_cursor
FETCH NEXT FROM copy_cursor
INTO #var1, #var2
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE Table_Name
SET Field_Name = #var2
WHERE id = #var1
FETCH NEXT FROM copy_cursor
INTO #var1, #var2
END
CLOSE copy_cursor;
DEALLOCATE copy_cursor;
ALTER TABLE Table_Name ALTER COLUMN Field_name varchar(255) NOT NULL

The server tries to compile an entire batch of SQL before it runs any of it. So, before referencing the column in a query, you need to create it (if necessary) in a separate batch. Insert a GO keyword to instruct your tool (SQLCMD, OSQL, SSMS) to send separate batches:
begin transaction;
IF NOT EXISTS(SELECT * FROM sys.columns
WHERE Name = N'Field_Name' and Object_ID = Object_ID(N'dbo.Table_Name'))
BEGIN
ALTER TABLE [dbo].[Table_Name]
ADD [Field_Name] VARCHAR(255)
END
commit;
GO
I think you may also need to wrap the ALTER as follows, to deal with the case where the column does exist:
EXEC sp_executesql 'ALTER TABLE [dbo].[Table_Name]
ADD [Field_Name] VARCHAR(255)'

First check if the table exists, start and commit the transaction, and it should work.
BEGIN TRANSACTION
IF (Object_ID(N'dbo.Table_Name') > 0)
BEGIN
IF NOT EXISTS(SELECT * FROM sys.columns
WHERE Name = N'Field_Name' and Object_ID = Object_ID(N'dbo.Table_Name'))
BEGIN
ALTER TABLE [dbo].[Table_Name]
ADD [Field_Name] VARCHAR(255)
END
END
COMMIT
GO

Related

How to prevent the trigger from sending an error if the table does not exist?

I wrote trigger after deleting a row (user) to drop the table that has the name composed from ID of this row (but not every ID has a table), I don't like to throw exception in code if the table does not exist!
CREATE TRIGGER AfterDeleteUser
ON usersProject
FOR DELETE
AS DECLARE #IDres VARCHAR(50),
#tablename VARCHAR(50)
SELECT #IDres = ins.IDressource FROM DELETED ins;
set #tablename = concat('MSG_', #IDres);
SET NOCOUNT ON;
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = N'DROP TABLE '+QUOTENAME(#tablename)
EXECUTE sp_executesql #Sql;
go
I get this error when the table does not exist!
Msg 3701, Niveau 11, État 5, Ligne 7
Cannot drop the table 'MSG_', because it does not exist or you do not have permission.
From SQL Server 2016 on, you could use the syntax:
DROP TABLE IF EXISTS [Tablename]
For older versions you could do:
IF OBJECT_ID('tablename', 'U') IS NOT NULL
DROP TABLE tablename;

If statement not working properly in sql server query

I have a table where I want to run following query
IF COL_LENGTH('table_name','IsDeleted') IS NOT NULL
BEGIN
IF COL_LENGTH('table_name','IsActive') IS NOT NULL
BEGIN
UPDATE table_name
SET IsActive = ~IsDeleted
END
ELSE
BEGIN
EXEC sp_RENAME 'table_name.IsDeleted', 'IsActive', 'COLUMN'
UPDATE table_name
SET IsActive = ~IsActive
END
ALTER TABLE table_name
DROP CONSTRAINT DF_table_name_IsDeleted
ALTER TABLE table_name DROP COLUMN IsDeleted
END
ELSE
BEGIN
--IsDeleted column does not exist
END
In my table there is no column with name "IsDeleted", so it shouldn't enter in the first if statement. But somehow it enters the first if statement in gives me error message:
Invalid column name 'IsDeleted'."
at line SET IsActive = ~IsDeleted
What is wrong in my query? Why control is not going in else part. Is there any syntax error?
IsActive and IsDeleted are of type BIT
This is because SQL Server parses and validates the statement regardless of the IF statements, meaning it parses and validates the whole batch. So when parsing the statement:
UPDATE table_name SET IsActive = ~IsDeleted
it errors out saying:
Invalid column name IsDeleted
See this example:
IF 1 = 1 BEGIN
CREATE TABLE #temp(col INT)
END
ELSE BEGIN
CREATE TABLE #temp(col INT)
END
It produces an error:
There is already an object named '#temp' in the database.
The workaround is to use Dynamic SQL.
DECLARE #sql NVARCHAR(MAX)
IF COL_LENGTH('table_name','IsDeleted') IS NOT NULL BEGIN
IF COL_LENGTH('table_name','IsActive') IS NOT NULL BEGIN
SET #sql = 'UPDATE table_name SET IsActive = ~IsDeleted'
EXEC sp_executesql #sql
END
ELSE BEGIN
EXEC sp_RENAME 'table_name.IsDeleted', 'IsActive', 'COLUMN'
SET #sql = 'UPDATE table_name SET IsActive = ~IsActive'
EXEC sp_executesql #sql
END
....
END

Unable to change my custom type in sql server

I have a custom type that I create by using the following:
IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = 'LineItemType')
BEGIN
DROP TYPE dbo.LineItemType
END
GO
CREATE TYPE dbo.LineItemType
AS TABLE
(
Id UNIQUEIDENTIFIER,
Invoice_Id UNIQUEIDENTIFIER,
Cost INT,
Quantity INT,
Total INT,
[Description] NVARCHAR(250)
);
GO
This type is used as a parameter of one of my stored procedures like this:
IF EXISTS (SELECT 1 FROM sys.procedures WHERE name = 'AddSomething' AND SCHEMA_NAME(schema_id) = 'dbo')
BEGIN
DROP PROCEDURE dbo.AddSomething
END
GO
CREATE PROCEDURE AddSomething
...
#LineItems AS dbo.LineItemType READONLY
AS
BEGIN
...
My problem is I have since decided to add some columns to my type, I updated my script above adding the column, and expected it to simply drop and re-create when I ran it but instead I got this error:
Msg 3732, Level 16, State 1, Line 4 Cannot drop type
'dbo.LineItemType' because it is being referenced by object
'AddSomething'. There may be other objects that reference this type.
Msg 219, Level 16, State 1, Line 8 The type 'dbo.LineItemType' already
exists, or you do not have permission to create it.
What am I missing? How can I drop my type and recreate it at any time?
Thanks.
I think you have to do this in the following sequence.
DROP ALL Procedures that use the LineItemType Type.
DROP The LineItemType Type.
CREATE The New LineItemType Type.
CREATE ALL the Procedures that use the LineItemType Type.
In short, generate the CREATE and DROP Scripts for all the Stored Procedures that use the LineItemType Type and then follow the above sequence.
I think you have to do this in the following sequence.
Rename the LineItemType Type.
CREATE the New LineItemType Type
3.execute aLL Procedures that use the LineItemType Type.
DROP the LineItemType Type.(Renamed)
Using SSDT will solve the problem of having to drop and re-create the referencing procedures manually, but this script should help otherwise:
create table #tmp(name nvarchar(200), module nvarchar(max))
insert into #tmp (name, module)
select distinct '['+ps.name+'].['+p.name+']', m.[definition] from sys.procedures p
join sys.schemas ps on ps.schema_id=p.schema_id
join sys.parameters r on r.object_id=p.object_id
join sys.types t on t.user_type_id=r.user_type_id
join sys.schemas ts on ts.schema_id=t.schema_id
join sys.sql_modules m on m.object_id=p.object_id
where ts.name='dbo' and t.name='ttCustom' --### pay attention to this line ###
select * from #tmp
declare procs scroll cursor for select * from #tmp
open procs
declare #name nvarchar(200), #body nvarchar(max)
while 1=1 begin
fetch next from procs into #name, #body
if ##FETCH_STATUS <> 0 break
set #body = 'drop procedure '+#name
exec sp_sqlexec #body
end
--### re-create the type here ###
DROP TYPE [dbo].[ttCustom]
CREATE TYPE [dbo].[ttCustom] AS TABLE (whatever nvarchar(30))
fetch first from procs into #name, #body
while ##FETCH_STATUS = 0 begin
exec sp_sqlexec #body
fetch next from procs into #name, #body
end
close procs
deallocate procs
drop table #tmp
I haven't tested it against procedures that are called by other procedures, but I don't think they'll be a problem.

SQL Server - Select columns that meet certain conditions?

My COLUMNS can contain only three values or var chars - economy, basic, luxury. I want to select a ROW and display only those COLUMNS which contain luxury. The problem is that there are many such columns - about 50. I don't want to type the names of all those columns in my select query. Is there a shorter and simpler alternative to this ? Which query should I use ?
I am thinking of something like this (this is a FAKE query) -
#declare Column_Name varchar(30)
select Column_Name where Column_Value = 'luxury'
from ATable
where rowId = 'row 5';
Table structure -
rowId | Column1 | Column2 | Column3.....
I've created a stored procedure for you.
This procedure examines the MSSQL meta to build a dynamic SQL string that returns a result containing column names N and their values V, and the corresponding row key K from which that value was retrieved, for a specified table.
When this is executed, the results stored in a global temporary table called ##ColumnsByValue, which can then be queried directly.
Create the GetColumnsByValue stored procedure, by executing this script:
-- =============================================
-- Author: Ben Roberts (sepster#internode.on.net)
-- Create date: 22 Mar 2013
-- Description: Returns the names of columns that contain the specified value, for a given row
-- =============================================
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF OBJECT_ID ( 'dbo.GetColumnsByValue', 'P' ) IS NOT NULL
DROP PROCEDURE dbo.GetColumnsByValue;
GO
CREATE PROCEDURE dbo.GetColumnsByValue
-- Add the parameters for the stored procedure here
#idColumn sysname,
#valueToFind nvarchar(255),
#dbName sysname,
#tableName sysname,
#schemaName sysname,
#debugMode int = 0
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #SQL nvarchar(max);
DECLARE #SQLUnion nvarchar(max);
DECLARE #colName sysname;
DECLARE #dbContext nvarchar(256);
DECLARE #Union nvarchar(10);
SELECT #dbContext = #dbName + '.' + #schemaName + '.sp_executeSQL';
SELECT #SQLUnion = '';
SELECT #Union = '';
IF OBJECT_ID ( 'tempdb..##GetColumnsByValueIgnoreList') IS NULL -- no columns to ingore have been specified, need to create an empty list.
BEGIN
CREATE TABLE ##GetColumnsByValueIgnoreList (column_name nvarchar(255));
END
DECLARE DBcursor CURSOR FOR
SELECT
COLUMN_NAME
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = #tableName
AND
TABLE_SCHEMA = #schemaName;
OPEN DBcursor;
FETCH DBcursor INTO #colName;
WHILE (##FETCH_STATUS = 0)
BEGIN
IF (
#colName != #idColumn
AND
#colName NOT IN (SELECT column_name FROM ##GetColumnsByValueIgnoreList)
)
BEGIN
SELECT #SQL = 'SELECT '+#idColumn+' as K, '''+#colName+''' as N, ' +#colName+ ' as V FROM ' + #dbName + '.' + #schemaName + '.' + #tableName;
--PRINT #SQL;
SELECT #SQLUnion = #SQL + #Union + #SQLUnion;
SELECT #Union = ' UNION ';
END
FETCH DBcursor INTO #colName;
END; -- while
CLOSE DBcursor; DEALLOCATE DBcursor;
IF (#debugMode != 0)
BEGIN
PRINT #SQLUnion;
PRINT #dbContext;
END
ELSE
BEGIN
-- Delete the temp table if it has already been created.
IF OBJECT_ID ('tempdb..##ColumnsByValue') IS NOT NULL
BEGIN
DROP TABLE ##ColumnsByValue
END
-- Create a new temp table
CREATE TABLE ##ColumnsByValue (
K nvarchar(255), -- Key
N nvarchar(255), -- Column Name
V nvarchar(255) -- Column Value
)
-- Populate it with the results from our dynamically generated SQL.
INSERT INTO ##ColumnsByValue EXEC #dbContext #SQLUnion;
END
END
GO
The SP takes several inputs as parameters, these are explained in the following code.
Note also I've provided a mechanism to add an "ignore list" as an input:
This allows you to list any column names that should not be included
in the results.
You do NOT need to add the columnn that you're using as your key, ie the row_id from your example structure.
You MUST include other columns that are not varchar as
these will cause an error (as the SP just does a varchar comparison
on all columns it looks at).
This is done via a temp table that you must create/populate
Your example table structure suggests
the table contains only columns of interest, so this may not apply to
you.
I've included example code for how to do this (but only do this if you need to):
IF OBJECT_ID ( 'tempdb..##GetColumnsByValueIgnoreList') IS NOT NULL
BEGIN
DROP TABLE ##GetColumnsByValueIgnoreList;
END
CREATE TABLE ##GetColumnsByValueIgnoreList (column_name nvarchar(255));
INSERT INTO ##GetColumnsByValueIgnoreList VALUES ('a_column');
INSERT INTO ##GetColumnsByValueIgnoreList VALUES ('another_column');
INSERT INTO ##GetColumnsByValueIgnoreList VALUES ('yet_another_column');
Now, to fire off the procedure that build your temp table of results, use the following code (and modify as appropriate, of course).
-- Build the ##ColumnsByValue table
EXEC dbo.GetColumnsByValue
#idColumn = 'row_id', -- The name of the column that contains your row ID (eg probably your PK column)
#dbName = 'your_db_name',
#tableName = 'your_table_name',
#schemaName = 'dbo',
#debugMode = 0 -- Set this to 1 if you just want a print out of the SQL used to build the temp table, to 0 if you want the temp table populated
This leaves you with ##ColumnsByValue, on which you can perform whatever search you need, eg:
select * from ##ColumnsByValue WHERE v = 'luxury' and k = 5 --some_row_id
You'd need to re-execute the stored procedure (and if relevant, create/modify the ignore list table prior to it) for each table you want to examine.
A concern with this approach is the nvarchar length might get exceeded in your case. You'd prob. need to use different datatype, reduce the column name lengths etc. Or break it up into sub-steps and union the results together to get the resultset you're after.
Another concern I have is that this is complete overkill for your particular scenario, where a one-off script-to-query-window will give you the basis of what you need, then some clever text editing in eg Notepad++ will get you all the way there... and hence this problem will likely (and quite reasonably) put you off doing it this way! But it is a good general-case question, and so deserves an answer for anyone interested in future ;-)

How can I reset an MySQL AutoIncrement using a MAX value from another table?

I know this won't work. I tried it in various forms and failed all times. What is the simplest way to achieve the following result?
ALTER TABLE XYZ AUTO_INCREMENT = (select max(ID) from ABC);
This is great for automation projects.
SELECT #max := (max(ID)+1) from ABC; -> This works!
select ID from ABC where ID = (#max-1); -> This works!
ALTER TABLE XYZ AUTO_INCREMENT = (#max+1); -> This fails :( Why?
Use a prepared statement:
SELECT #max := MAX(ID)+ 1 FROM ABC;
PREPARE stmt FROM 'ALTER TABLE ABC AUTO_INCREMENT = ?';
EXECUTE stmt USING #max;
DEALLOCATE PREPARE stmt;
Following the MySQL documentation, this worked for me in MySQL 5.7:
SET #m = (SELECT MAX(id) + 1 FROM ABC);
SET #s = CONCAT('ALTER TABLE XYZ AUTO_INCREMENT=', #m);
PREPARE stmt1 FROM #s;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
Whoever is having a problem with PREPARE stmt FROM 'ALTER TABLE XYZ AUTO_INCREMENT = ?' can use:
CREATE PROCEDURE reset_xyz_autoincrement
BEGIN
SELECT #max := MAX(ID)+ 1 FROM ABC;
set #alter_statement = concat('ALTER TABLE temp_job_version AUTO_INCREMENT = ', #max);
PREPARE stmt FROM #alter_statement;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
I'm creating an automated database transformation script for a new version of my application.
In one table, I needed to change the primary auto-increment field to a different field. Since this page came up first many times while I googled a solution for it, here's a solution that eventually worked for me:
-- Build a new ID field from entry_id, make it primary and fix the auto_increment for it:
ALTER TABLE `entries` ADD `id` INT UNSIGNED NOT NULL FIRST;
UPDATE entries SET id = entry_id;
ALTER TABLE `entries` ADD PRIMARY KEY ( `id` );
-- ...the tricky part of it:
select #ai := (select max(entry_id)+1 from entries);
set #qry = concat('alter table entries auto_increment=',#ai);
prepare stmt from #qry; execute stmt;
-- ...And now it's possible to switch on the auto_increment:
ALTER TABLE `entries` CHANGE `id` `id` INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT;
Reset Auto Increment IDs.
Reset Auto Increment IDs
Update all auto increment columns in a database to the smallest possible value based on current values in the databases. We needed to do this after cleaning out a database.
Use a prepared statement within a stored procedure:
drop PROCEDURE if exists reset_autoincrement;
DELIMITER //
CREATE PROCEDURE reset_autoincrement (IN schemaName varchar(255))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE o_name VARCHAR(255);
DECLARE o_table VARCHAR(255);
DECLARE cur1 CURSOR FOR SELECT COLUMN_NAME, TABLE_NAME FROM information_schema.`COLUMNS` WHERE extra LIKE '%auto_increment%' and table_schema=schemaName;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO o_name, o_table;
IF done THEN
LEAVE read_loop;
END IF;
set #qry1 = concat('SELECT MAX(`',o_name,'`) + 1 as autoincrement FROM `',o_table,'` INTO #ai');
PREPARE stmt1 FROM #qry1;
EXECUTE stmt1;
IF #ai IS NOT NULL THEN
SELECT o_name, o_table;
select #qry1;
select #ai;
set #qry2 = concat('ALTER TABLE `',o_table,'` AUTO_INCREMENT = ', #ai);
select #qry2;
PREPARE stmt2 FROM #qry2;
EXECUTE stmt2;
END IF;
END LOOP;
CLOSE cur1;
END //
DELIMITER ;
call reset_autoincrement('my_schema_name');
Personally I'd probably use either a shell script or a little C#/C++ application or PHP/Ruby/Perl script to do this in 2 queries:
Grab the value you want SELECT MAX(ID) FROM ABC;
Alter the table using the value ALTER TABLE XYZ AUTO_INCREMENT = <insert value retrieved from first query here>
Obviously being careful that the new auto increment won't cause any key clashes with existing data in the XYZ table.
Ok guys. I have come up with a not so intuitive solution. The best part is that it works!
SELECT #max := max(ID) from ABC;
ALTER TABLE XYZ AUTO_INCREMENT = 1;
ALTER TABLE XYZ ADD column ID INTEGER primary key auto_increment;
UPDATE XYZ SET ContactID = (ContactID + #max);
If you really want to do this in MySQL alone, you can just dump the dynamically built alter command to a file on disk and then execute it.
Like so:
select concat('ALTER TABLE XYZ AUTO_INCREMENT = ',max(ID)+1,';') as alter_stmt
into outfile '/tmp/alter_xyz_auto_increment.sql'
from ABC;
\. /tmp/alter_xyz_auto_increment.sql