Alter Table and Update column in single stored procedure? - sql

I'm am trying to create a stored procedure that adds a new column then sets the column to a value:
CREATE PROCEDURE alter_then_update
AS
ALTER TABLE table_1
ADD bundle_type NVARCHAR(10);
UPDATE table_1
SET bundle_type = 'Small'
I keep getting an error that I have an invalid column name of bundle_type. I get that the column isn't created yet. Can't I store this in a stored procedure and have the code execute line by line? Doesn't the semi-colon execute in order? I tried using GO but the query started to execute instead.
How can I put both of these statements into one procedure?

The problem is that the procedure is compiled before it is executed. I think the only way around this in a stored procedure is dynamic SQL:
CREATE PROCEDURE alter_then_update AS
BEGIN
ALTER TABLE table_1 ADD bundle_type NVARCHAR(10);
EXEC sp_executesql 'UPDATE table_1 SET bundle_type = ''Small''';
END;

Related

Select results from stored procedure into a table

I have a stored procedure, usp_region and it has a select statement with 50 columns as the result set. This procedure is called by multiple other stored procedures in our application.
Most of the stored procedure pass a parameter to this procedure and display the result set that it returns. I have one stored procedure, usp_calculatedDisplay, that gets the columns from this stored procedure and inserts the values into a temp table and does some more calculations on the columns.
Here's a part of the code in usp_calculatedDisplay.
Begin Procedure
/* some sql statements */
Declare #tmptable
(
-- all the 50 columns that are returned from the usp_region procedure
)
Insert Into #tmptable
exec usp_region #regionId = #id
Select t.*, /* a few calculated columns here */
From #tmptable t
End of procedure
Every time I add a column to the usp_region procedure, I'll also have to make sure I have to add it to this procedure. Otherwise it breaks. It has become difficult to maintain it since it is highly possible for someone to miss adding a column to the usp_calculatedDisplay procedure when the column is added to the usp_region.
In order to overcome this problem, I decided to do this:
Select *
Into #tmptable
From OPENROWSET('SQLNCLI',
'Server=localhost;Trusted_Connection=yes;',
'EXEC [dbo].[usp_region]')
The problem is 'Ad Hoc Distributed Queries' component is turned off. So I can't use this approach to overcome this issue. I was wondering if there are any other ways of overcoming this problem. I would really appreciate any help. Thank you!
Every time I add a column to the usp_region procedure
SQL Server is a structured database and it does not meant to solve such cases that you need to change your structure every day.
If you add/remove columns so often then you probably did not choose the right type of database, and you better re-design your system.
It has become difficult to maintain it since it is highly possible for someone to miss adding a column to the usp_calculatedDisplay procedure when the column is added to the usp_region.
There are two simple solutions for this (1) using DDL Triggers - very bad idea but simple to implement and working. (2) Using my trick to select from stored procedure
Option 1: using DDL trigger
You can automate the entire procedure and ALTER the stored procedure usp_calculatedDisplay every time that the stored procedure usp_region is changed
https://learn.microsoft.com/en-us/sql/relational-databases/triggers/ddl-triggers
The basic approach is
CREATE OR ALTER TRIGGER NotGoodSolutionTrig ON DATABASE FOR ALTER_PROCEDURE AS BEGIN
DECLARE #var_xml XML = EVENTDATA();
IF(
#var_xml.value('(EVENT_INSTANCE/DatabaseName)[1]', 'sysname') = 'tempdb'
and
#var_xml.value('(EVENT_INSTANCE/SchemaName)[1]', 'sysname') = 'dbo'
and
#var_xml.value('(EVENT_INSTANCE/ObjectName)[1]', 'sysname') = 'usp_region'
)
BEGIN
-- Here you can parse the text of the stored procedure
-- and execute ALTER on the first SP
-- To make it simpler, you can design the procedure usp_region so the columns names will be in specific row or between to comment which will help us to find it
-- The code of the Stored Procedure which you need to parse is in the value of:
-- #var_xml.value('(EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)'))
-- For example we can print it
DECLARE #SP_Code NVARCHAR(MAX)
SET #SP_Code = CONVERT(NVARCHAR(MAX), #var_xml.value('(EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)'))
PRINT #SP_Code
-- In your case, you need to execute ALTER on the usp_calculatedDisplay procedure using the text from usp_region
END
END
Option 2: trick to select from stored procedure using sys.dm_exec_describe_first_result_set
This is simple and direct way to get what you need.
CREATE OR ALTER PROCEDURE usp_calculatedDisplay AS
-- Option: using simple table, so it will exists outsie the scope of the dynamic query
DROP TABLE IF EXISTS MyTable;
DECLARE #sqlCommand NVARCHAR(MAX)
select #sqlCommand = 'CREATE TABLE MyTable(' + STRING_AGG ([name] + ' ' + system_type_name, ',') + ');'
from sys.dm_exec_describe_first_result_set (N'EXEC usp_region', null,0)
PRINT #sqlCommand
EXECUTE sp_executesql #sqlCommand
INSERT MyTable EXECUTE usp_region;
SELECT * FROM MyTable;
GO
Note!!! Both solutions are not recommended in production. My advice is to avoid such needs by redesign your system. If you need to re-write 20 SP so do it and don't be lazy! Your goal should be what best for the database usage.

How to alter table and update column values in the same area / stored procedure?

Sorry for the poor title, I'm just not sure how to explain the issue in a single sentence.
I have this bit of code in a stored procedure:
IF COL_LENGTH('dbo.raw_customer', 'LegalName') IS NULL
BEGIN
ALTER TABLE [raw_customer]
ADD [LegalName] varchar(255)
UPDATE [raw_customer]
SET [LegalName] = [Name]
END
When it runs, I get the following error:
Invalid column name 'LegalName'
and the table is not updated.
I tried splitting into two separate commands:
IF COL_LENGTH('dbo.raw_customer', 'LegalName') IS NULL
ALTER TABLE [raw_customer]
ADD [LegalName] varchar(255)
IF COL_LENGTH('dbo.raw_customer', 'LegalName') IS NULL
UPDATE [raw_customer]
SET [LegalName] = [Name]
I get the same error.
If I do them manually separately they work, but I can't seem to get them to work together in the stored procedure. The best I can think is that the ALTER TABLE needs to be committed before the UPDATE can run, but not clear on how to do that. I can't put a GO in the middle of the stored procedure.
To accomplish what you are trying to do you need to defer the compilation of the update statement.
You can do this using exec sp_executesql, try the following:
IF COL_LENGTH('dbo.raw_customer', 'LegalName') IS NULL
BEGIN
exec sp_executesql N'ALTER TABLE [raw_customer] ADD [LegalName] varchar(255)'
exec sp_executesql N'UPDATE [raw_customer] SET [LegalName] = [Name]'
END

EXEC procedure in dynamic SQL statement only does SELECTs and not INSERTs

I have a stored procedure, let's call it stored procedure 'B'. Stored procedure 'B' calls stored procedure 'A' which returns a resultset that needs to be inserted into a temp table within stored procedure 'B', in order to do further mutations. Because of nested inserts, I have used OPENROWSET (and tried OPENQUERY too).
Now, it seems to work great! However, next to returning a resultset, stored procedure 'A' also does INSERTS in a table. The weird thing is, when stored procedure 'A' is executed from within stored procedure 'B', stored procedure 'A' only returns the resultset, and does NO insert at all. It just seems to skip the entire INSERT INTO statement. I have tried putting dummy SELECT 'test' breakpoints before and after the INSERT, and they are executed fine! How is this possible?
This query looks like this (I changed data and columns up a bit):
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = 'INSERT INTO #Temp (1,2,3)
SELECT * FROM OPENROWSET (
''SQLOLEDB'',
''Server=(local);TRUSTED_CONNECTION=yes;'',
''SET FMTONLY OFF EXECUTE StoredProcedureA
#Parameter1 = '''''+#InputValue1+'''''
,#Parameter_2 = '''''+#InputValue2+'''''
''
)'
EXEC(#SQL)
No errors are returned. The resultset (SELECT statement from procedure A) is correctly loaded into #Temp within procedure B. But the INSERT that is done within procedure A is not executed.
Does openquery/openrowset not allow INSERTS and only execute SELECT outputs? I thought, maybe its a security/rights issue? Is there any other way to workaround this issue?
Thanks in advance guys!
It is because you are using a temporary table denoted by #.
The scope of this table is ends when your nested stored procedure ends and the temporary table is dropped.
So the insert happens, the table just doesn't exist anymore.
If you create the table before starting your nested procedure you can solve this problem. You can just drop the table in you Procedure B if you really want it gone.

Adding column to table and then setting value gives error

I am pretty new to SQL. I am working with SQL Server 2012. I need to do the following: add a column to an existing table and fill all rows in that column with the same value. To do this, I have come up with the following based on searching online:
ALTER TABLE myTable ADD myNewColumn VARCHAR(50) NULL
UPDATE myTable SET myNewColumn = 'test'
The problem is that in SQL server, I get the following error for the second statement:
Invalid column name 'myNewColumn
So, my guess is that a new column called myNewColumn wasn't created by the first statement.
You need to perform the update in a separate batch. Otherwise SQL Server tries to parse and validate that the column exists before ever trying to run the ALTER that creates it. You get the invalid column name at parse time, not at run time.
One workaround is to use GO between the two batches:
ALTER TABLE dbo.myTable ADD myNewColumn VARCHAR(50) NULL;
GO
UPDATE dbo.myTable SET myNewColumn = 'test';
(Always use schema prefixes to reference objects and always terminate statements with semi-colons.)
However this only works in Management Studio and other certain client applications, because it is not actually part of the T-SQL language; these client tools see it as a batch separator and it tells them to submit and evaluate these two batches separately. It will NOT work in code blocks submitted to SQL Server in other ways and as a single batch, e.g. in the body of a stored procedure:
CREATE PROCEDURE dbo.foo
AS
BEGIN
SET NOCOUNT ON;
SELECT 1;
GO
SELECT 2;
END
GO
This yields the following errors, because it actually splits the stored procedure into two separate batches:
Msg 102, Level 15, State 1, Procedure foo, Line 8
Incorrect syntax near ';'.
Msg 102, Level 15, State 1, Line 11
Incorrect syntax near 'END'.
What you can do as a different workaround is force the update into its own batch by executing it in dynamic SQL.
DECLARE #sql NVARCHAR(MAX), #value VARCHAR(50) = 'test';
ALTER TABLE dbo.myTable ADD myNewColumn VARCHAR(50) NULL;
SET #sql = N'UPDATE dbo.myTable SET myNewColumn = #value;';
EXEC sp_executesql #sql, N'#value VARCHAR(50)', #value;
(Why you should use EXEC sp_executesql vs. EXEC(#sql).)
Another workaround; perform the add and the update in one step:
ALTER TABLE dbo.myTable ADD myNewColumn VARCHAR(50) DEFAULT 'test' WITH VALUES;
(You can later drop the default constraint if you don't actually want any future rows to inherit that value in circumstances that would cause that behavior.)
Place the word GO after your alter statement
Alter and update cannot be executed at the same time. You need to segregate it using a built-in stored procedure to execute the update statement as under:
ALTER TABLE myTable ADD myNewColumn VARCHAR(50) NULL
Exec sp_executesql N'UPDATE myTable SET myNewColumn = ''test'''
This should definitely solve this problem.
try something like:
ALTER TABLE myTable ADD myNewColumn VARCHAR(50) NULL DEFAULT("Something")
The problem with your code is that the column does not exist until after the query completes. So you can reference it until then.

How to execute a stored procedure after it is created?

I'm trying to execute a stored procedure directly after its creation however it is not getting called. It looks like the stored procedure is not yet created during the execution call.
Here is how the script looks like:
CREATE PROCEDURE sp_Transfer_RegionData
AS
BEGIN
INSERT INTO Region (regionName)
SELECT column1
FROM openquery(ITDB, 'select * from db.table1')
END
EXEC sp_Transfer_RegionData
The script runs fine however the needed table is not populated. After replacing the execution part with:
IF OBJECT_ID('sp_Transfer_RegionData') IS NOT NULL
begin
exec [dbo].[sp_Transfer_RegionData]
print 'tada'
end
I could see that the stored procedure does not exist when it has to be executed. Couldn't find a solution for this in the internet...
So how to make the SQL script run sync so that the stored procedure would already exist during the execution part?
You need a GO after you created the SP otherwise you have created a recursive SP that calls itself "indefinitely" which is 32 times in SQL Server.
Maximum stored procedure, function, trigger, or view nesting level
exceeded (limit 32).
Try this:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE sp_Transfer_RegionData
AS
BEGIN
INSERT INTO Region (regionName)
SELECT column1
FROM openquery(ITDB, 'select * from db.table1')
END
GO
EXEC sp_Transfer_RegionData