I'm currently trying to create a function in a database that was created in a stored procedure.
Set #sql = 'USE [' + #dbname + '] GO CREATE FUNCTION dbo.functionname(#trajectid int)
RETURNS int
AS
BEGIN
DECLARE #result int;
(SELECT #result = SUM(duur) FROM AgendaItems WHERE trajectid = #trajectid)
RETURN #result
END'
exec(#sql)
What we want to achieve is using the function in the table definitions (also in stored procedures)
gebruikt AS [dbo].functionname([id]),
We tried using Maindatabase.dbo.functionname, which returned an error:
A user-defined function name cannot be prefixed with a database name in this context.
Thanks in advance for any help.
Sorry for being straight but, you simply should not use a stored procedure to create DDL - and in fact, the system is preventing you from doing that, as it's really a bad practice.
There are workarounds, but you should really change the way you are handling the process that you want to create - that would be the only real solution
Related
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.
I have a database with multiple schemas. In every schema I got table called [Logs], so my database tables looks like:
[s1].[Logs]
[s2].[Logs]
[s3].[Logs]
...
[sN].[Logs]
Every day I would like to run stored procedure, which will do same operations on every above table. Is there a way to pass schema name into stored procedure? I am using SQL on Azure.
No, it is not - unless the SP Uses then dynamic SQL to execute some SQL String you constructed in the SP.
This happens via the sp_executesql stored procedure
http://technet.microsoft.com/en-us/library/ms188001.aspx
has more information.
Microsoft has a few undocumented procedures that perform "foreach" operations on tables (sp_msforeachtable) and databases (sp_msforeachdb). Both of these rely on another undocumented proc called sp_msforeachworker which you might be able to exploit to create a foreachschema type of routine. Theres an article (reg required) here that demonstrates this approach.
That said, its unlikely Azure supports anything of these, so you might have to fashion your own using a crude loop:
declare #schemas table (i int identity(1,1), name sysname);
insert into #schemas
select name from sys.schemas where name like 's[0-9]%';
declare #i int, #name sysname, #cmd nvarchar(max);
select #i = min(i) from #schemas;
while #i is not null
begin
select #name = name from #schemas where i = #i;
set #cmd = replace(N'select count(*) from [{0}].[Logs];', '{0}', #name);
print #cmd;
--exec(#cmd);
select #i = min(i) from #schemas where i > #i;
end
I have a stored procedure in which i want to create a user defined function - Split (splits a string separated with delimiters and returns the strings in a table), make use of the function and finally drop the function.
My question is that whether i can create a user defined function inside a stored procedure and drop it finally?
Thank you.
Regards
NLV
Technically...yes you could but that does not mean you should. You would have to be careful about avoiding GO statements (just use Exec for each batch) but you could do something like:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE dbo.Test
AS
Declare #Sql nvarchar(max)
Set #Sql = 'CREATE FUNCTION dbo.Foo
(
)
RETURNS TABLE
AS
RETURN
(
SELECT 0 As Bar
)'
Exec(#Sql)
Select *
From dbo.Foo()
Set #Sql = 'Drop Function dbo.Foo'
Exec(#Sql)
Return
GO
Exec dbo.Test
That said, I would strongly recommend against this sort of solution, especially if the function you want is something that would be useful like a Split function. I would recommend just creating the UDF and using it and leaving it until you might use it again.
CTEs would be good here too, depending on what your UDF is trying to do.
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.
In SQL Server 2005, is there a concept of a one-time-use, or local function declared inside of a SQL script or Stored Procedure? I'd like to abstract away some complexity in a script I'm writing, but it would require being able to declare a function.
Just curious.
You can create temp stored procedures like:
create procedure #mytemp as
begin
select getdate() into #mytemptable;
end
in an SQL script, but not functions. You could have the proc store it's result in a temp table though, then use that information later in the script ..
You can call CREATE Function near the beginning of your script and DROP Function near the end.
Common Table Expressions let you define what are essentially views that last only within the scope of your select, insert, update and delete statements. Depending on what you need to do they can be terribly useful.
I know I might get criticized for suggesting dynamic SQL, but sometimes it's a good solution. Just make sure you understand the security implications before you consider this.
DECLARE #add_a_b_func nvarchar(4000) = N'SELECT #c = #a + #b;';
DECLARE #add_a_b_parm nvarchar(500) = N'#a int, #b int, #c int OUTPUT';
DECLARE #result int;
EXEC sp_executesql #add_a_b_func, #add_a_b_parm, 2, 3, #c = #result OUTPUT;
PRINT CONVERT(varchar, #result); -- prints '5'
The below is what I have used i the past to accomplish the need for a Scalar UDF in MS SQL:
IF OBJECT_ID('tempdb..##fn_Divide') IS NOT NULL DROP PROCEDURE ##fn_Divide
GO
CREATE PROCEDURE ##fn_Divide (#Numerator Real, #Denominator Real) AS
BEGIN
SELECT Division =
CASE WHEN #Denominator != 0 AND #Denominator is NOT NULL AND #Numerator != 0 AND #Numerator is NOT NULL THEN
#Numerator / #Denominator
ELSE
0
END
RETURN
END
GO
Exec ##fn_Divide 6,4
This approach which uses a global variable for the PROCEDURE allows you to make use of the function not only in your scripts, but also in your Dynamic SQL needs.
In scripts you have more options and a better shot at rational decomposition. Look into SQLCMD mode (SSMS -> Query Menu -> SQLCMD mode), specifically the :setvar and :r commands.
Within a stored procedure your options are very limited. You can't create define a function directly with the body of a procedure. The best you can do is something like this, with dynamic SQL:
create proc DoStuff
as begin
declare #sql nvarchar(max)
/*
define function here, within a string
note the underscore prefix, a good convention for user-defined temporary objects
*/
set #sql = '
create function dbo._object_name_twopart (#object_id int)
returns nvarchar(517) as
begin
return
quotename(object_schema_name(#object_id))+N''.''+
quotename(object_name(#object_id))
end
'
/*
create the function by executing the string, with a conditional object drop upfront
*/
if object_id('dbo._object_name_twopart') is not null drop function _object_name_twopart
exec (#sql)
/*
use the function in a query
*/
select object_id, dbo._object_name_twopart(object_id)
from sys.objects
where type = 'U'
/*
clean up
*/
drop function _object_name_twopart
end
go
This approximates a global temporary function, if such a thing existed. It's still visible to other users. You could append the ##SPID of your connection to uniqueify the name, but that would then require the rest of the procedure to use dynamic SQL too.
Just another idea for anyone that's looking this up now. You could always create a permanent function in tempdb. That function would not be prefixed with ## or # to indicate it's a temporary object. It would persist "permanently" until it's dropped or the server is restarted and tempdb is rebuilt without it. The key is that it would eventually disappear once the server is restarted if your own garbage collection fails.
The scope of the function would be within TempDB but it could reference another database on the server with 3 part names. (dbname.schema.objectname) or better yet you can pass in all the parameters that the function needs to do its work so it doesn't need to look at other objects in other databases.