Creating Stored Proc to Insert into a View - sql

I have created some new tables that I need to insert to on a semi-regular basis. Due to normalization I decided to build a view on top of the base tables to make reports more logical for myself and EU's. I got the bright idea to try to use a stored procedure to push inserts into the base tables via a different view. I can run the insert statement in SSMS successfully, but when I try to create it into a stored procedure it will run because it appears to think my insert is a function.
Here is the error:
Msg 215, Level 16, State 1, Procedure jedi.p_ForcePush, Line 12
Parameters supplied for object 'jedi.v_midichlorians' which is not a function. If the parameters are intended as a table hint, a WITH keyword is required.
Here is my script:
CREATE PROCEDURE jedi.p_ForcePush
#Field varchar(25) = NULL,
#Value varchar(250) = NULL
AS
BEGIN
SET NOCOUNT ON;
insert jedi.v_midichlorians (#field) values (#value)
END
GO
I have poured out my petition to the googles but haven't found a good solution. I have tried lots of different combo's in my syntax but nothing doing.
Any help is much appreciated! (ps-SQL 2012)

CREATE PROCEDURE jedi.p_ForcePush
#Field varchar(25) = NULL,
#Value varchar(250) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'INSERT INTO jedi.v_midichlorians (' + QUOTENAME(#field)
+ N') values (#value)'
EXECUTE sp_executesql #sql
, N'#Value varchar(250)'
, #Value
END
GO
When you pass #field param as a parameter sql server treats it as a string not an object name, Using QUOTENAME() wraps the passed column name in [] square brackets telling sql server explicitly that it is an object(Table, column) name.
On a side note If your View has only one underlying table on then use view to insert values other wise use the table name.
If you do have more then one underlying table in your View's definition and you want to insert data using view then you will need to create Instead of triggers.
Best option is to do all insert, update delete operation directly to tables, avoid using views and then triggers for view based on more then one underlying tables.

though you mentioned that insert stmts run fine in ssMS, can you confirm that the same insert stmt you ran in SSMS ? becaus ethere is an error in this stmt.
"insert jedi.v_midichlorians (#field)"
syntex is inccoect and column name should dnot have "#" right?
also is this view based on a single table ?

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 use a variable from one stored procedure in a different stored procedure (SQL)

I have written a stored procedure as follows (this is a simplified version - the SP does a lot of other things but these are the key parts):
CREATE PROCEDURE [dbo].[_uspCustomSP]
AS
BEGIN
CREATE TABLE #custno(custno int)
INSERT INTO #custno
EXEC usp_GetCustomerNo
DECLARE #custnumber nvarchar(5)
SET #custnumber = (SELECT custno FROM #custno)
DROP TABLE #custno-- drop table so fresh each time
END
This SP works as I want it to. However, I want to be able to refer to the value of #custnumber in a different stored procedure. Is there any way of persisting the value of #custnumber but without rerunning usp_GetCustomerNo (as every time it is run, the value of #custnumber changes - I want to be able to use the exact number as stored in the variable.)
EDIT: I've had a really helpful response below suggesting I include an output parameters. I have thought about this but I'm not sure how to refer to this output elsewhere (in a different SP) without re-running the entire SP at the same time.
Apologies if I've not included enough information.
Many thanks,
Helen
You can have the stored procedure return the value:
CREATE PROCEDURE [dbo].[_uspCustomSP] (
#custnumber nvarchar(5) OUTPUT
) AS
BEGIN
CREATE TABLE #custno(custno int) ;
INSERT INTO #custno (custno)
EXEC usp_GetCustomerNo;
SELECT #custnumber = custno FROM #custno ;
DROP TABLE #custno-- drop table so fresh each time
END;
Having said that, I have some comments on the stored procedure:
There is no need to drop the temporary table. I prefer table variables, because it is obvious they go out of scope.
I think it is dangerous to return a single value in a table. Why not use a scalar function or OUTPUT parameter for usp_GetCustomerNo?
You should get in the habit of putting semicolons at the end of statements and always using a column list with INSERT.
You would call the stored procedure as:
declare #custnumber nvarchar(5);
exec sp_executesql _uspCustomSP,
N'#custnumber nvarchar(5) output',
#custnumber=#custnumber output;

Set table based on stored procedure parameter

We have a process that updates certain tables based on a parameter passed in, specifically a certain state. I know organizationally this problem would be eliminated by using a single table for this data, but that is not an option -- this isn't my database.
To update these tables, we run a stored procedure. The only issue is that there was a stored procedure for each state, and this made code updates horrible. In order to minimize the amount of code needing to be maintained, we wanted to move towards a single stored procedure that takes in a state parameter, and updates the correct tables. We wanted this without 50 If statements, so the only way I could think to do this was to save the SQL code as text, and then execute the string. IE:
SET #SSQL = 'UPDATE TBL_' + #STATE +' SET BLAH = FOO'
EXEC #SSQL;
I was wondering if there was a way to do this without using strings to update the correct tables based on that parameter. These stored procedures are thousands of lines long.
Thanks all!
Instead save entire script as SQL text and execute it, just update the required table using like code below as where you need and rest continue as it is
EXEC('UPDATE TBL_' + #STATE +' SET BLAH = FOO')
You could, indeed, use dynamic SQL (the exec function) - but with long, complex stored procedures, that can indeed be horrible.
When faced with a similar problem many years ago, we created the stored procedures by running a sort of "mail-merge". We'd write the procedure to work against a single table, then replace the table names with variables and used a PHP script to output a stored procedure for each table by storing the table names in a CSV file.
You could replicate that in any scripting language of your choice - it took about a day to get this to work. It had the added benefit of allowing us to easily store the stored proc templates in source code control.
You can safely use sp_executesql which is fairly more appropriate than a simple EXEC command. To do so, even with input and output parameters :
DECLARE #sql nvarchar(4000),
#tablename nvarchar(4000) = 'YOUR_TABLE_NAME',
#params nvarchar(4000),
#count int
SELECT #sql =
N' UPDATE ' + #tablename +
N' SET Bar = #Foo;' +
N' SELECT #count = ##rowcount'
SELECT #params =
N'#Foo int, ' +
N'#count int OUTPUT'
EXEC sp_executesql #sql, #params, 2, #count OUTPUT
SELECT #count [Row(s) updated]
I encourage you reading the related part of the article mentionned here.

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.

Dynamic Datasource in SQL Server Stored Procudure

I have a SQL Server that houses Several Databases. I have a Main Database that holds several tables with entities and ID numbers. Then, each one of those entities has a correlating database (not a table, but database) with all of its information. For example, if the an entity in the MAIN database has an ID number of 1, there would be an SubDatabase1 Database on the same SQL Server.
Essentially, what I am trying to do is create a stored procedure in the MAIN Database, that collects data from the SUB Database, but the SUB database I collect from should be determined based on the ID number passed to the Proc.
I know this is totally incorrect, but I am wondering if someone can shine some light on this for me.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE GetInstallationCount
-- Add the parameters for the stored procedure here
#installId int=0
AS
BEGIN
SET NOCOUNT ON;
//Trying to make the DatabaseName dynamic here!!
select count(*) from dbo.Installation#installId.Names
END
GO
Thanks - J
Read up on how to create dynamic SQL, particularly sp_executesql. This should get you started:
DECLARE #theSql varchar(1000)
DECLARE #installId int
SET #installId = 1
SET #theSql = 'SELECT COUNT(*) FROM dbo.Installation' + CAST(#installId as nvarchar) + '.Names'
EXEC (#theSql)
You have to use dynamic SQL to do that. Table names and database names cannot be resolved at runtime in any other way.
Here is a good introduction to this technique by Scott Mitchell.
As often, the answer to such a question is dynamic SQL:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE GetInstallationCount
-- Add the parameters for the stored procedure here
#installId int=0
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql nvarchar(MAX)
SET #sql = 'select count(*) from dbo.Installation' + Cast(#installId as nvarchar) + '.Names'
EXECUTE dbo.sp_executesql #sql
END
GO
Definately could be done by building up the select string dynamically and executing but it would be nasty.
You could get very flashy and try create synonyms of the fly, use them in the queries and then drop them but I'm not sure it would be worth it.
Use synonyms. For example this sets synonym dbo.MySpecialTable to point to table dbo.SomeTable in database DB_3.
IF object_id(N'SN', N'dbo.MySpecialTable') IS NOT NULL
DROP SYNONYM dbo.MySpecialTable
CREATE SYNONYM dbo.MySpecialTable FOR [DB_3].[dbo].[SomeTable]
With this in place, write all your queries to use synonyms instead of real table names. Synonyms have DB scope, so manage "target switching" at one place, maybe in a stored procedure.