Create database using a stored function - database

I am new to PostgreSQL and want to create a database using a stored function.
For ex:
CREATE OR REPLACE FUNCTION mt_test(dbname character varying)
RETURNS integer AS
$BODY$
Create Database $1;
Select 1;
$BODY$
LANGUAGE sql;
When I am trying to execute this function I get a syntax error.
Does Postgres support the CREATE DATABASE statement in stored functions?

This question is old, but for the sake of completeness ...
As has been pointed out in other answers, that's not simply possible because (per documentation):
CREATE DATABASE cannot be executed inside a transaction block.
It has also been reported that the restriction can be bypassed with dblink.
How to use (install) dblink in PostgreSQL?
What was missing so far is a proper function actually doing it:
CREATE OR REPLACE FUNCTION f_create_db(dbname text)
RETURNS integer AS
$func$
BEGIN
IF EXISTS (SELECT 1 FROM pg_database WHERE datname = dbname) THEN
RAISE NOTICE 'Database already exists';
ELSE
PERFORM dblink_exec('dbname=' || current_database() -- current db
, 'CREATE DATABASE ' || quote_ident(dbname));
END IF;
END
$func$ LANGUAGE plpgsql;
Checks if the db already exists in the local cluster. If not, proceed to create it - with a sanitized identifier. We would not want to invite SQL injection.

You can't create a database inside of a function because it's not possible to create a database inside a transaction.
But most probably you don't mean to create databases but schemas, which more closely resemble the MySQL's databases.

I found a tricky solution to this problem, but possible. After looking and reading almost in everywhere I tried something and it worked.
if the error is "CREATE DATABASE cannot be executed from a function or multi-command string" we can force a single command string using dblink. And make it to connect to itself.
Check for dblink installation instructions at dblink
PERFORM replication.dblink_connect('myconn','host=127.0.0.1 port=5432 dbname=mydb user=username password=secret');
PERFORM replication.dblink_exec('myconn', 'CREATE DATABASE "DBFROMUSER'||id||'" TEMPLATE "TEMPL'||type||'";',false);
PERFORM replication.dblink_disconnect('myconn');
In my case using different kinds of template.
Greetings

postgres=> create or replace function mt_test(dbname text)
returns void language plpgsql as $$
postgres$> begin
postgres$> execute 'create database '||$1;
postgres$> end;$$;
CREATE FUNCTION
postgres=> select work.mt_test('dummy_db');
ERROR: CREATE DATABASE cannot be executed from a function or multi-command string
CONTEXT: SQL statement "create database dummy_db"
PL/pgSQL function "mt_test" line 2 at EXECUTE statement
postgres=>
note the error message: CREATE DATABASE cannot be executed from a function or multi-command string
so the answer to the question:
Does postgresql support creating statement in stored function
is "no" (at least on 8.4 - you don't specify your version)

Related

Does MSSQL support an equivalent form of the MySQL statement `CREATE DATABASE IF NOT EXISTS {db}`?

I'm using SQLAlchemy to execute SQL queries against different database engines.
For MySQL, I use the following query to create a database, if it doesn't already exist:
CREATE DATABASE IF NOT EXISTS {db} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
Does MSSQL support an equivalent form of the MySQL statement CREATE DATABASE IF NOT EXISTS {db}? to create a database, if it doesn't already exist?
SQL Server doesn't have CREATE {object} IF NOT EXISTS syntax. It does, however, have CREATE OR ALTER syntax, but only for objects where a CREATE/ALTER must be the only statement in the batch (such as PROCEDUREs and VIEWs), and DROP IF EXISTS.
If you explicitly want to check if an object exists before you create it, and it doesn't support the CREATE OR ALTER syntax, you would need to use a statement prior to check for its existence:
IF NOT EXISTS (SELECT 1 FROM sys.databases WHERE [name] = N'testing')
CREATE DATABASE Testing;
If, for some reason, a LOGIN does not have access to see the databases in sys.databases you could wrap the attempt in a TRY...CATCH:
BEGIN TRY
CREATE DATABASE Testing;
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 1801
PRINT N'Database already exists.';
ELSE
THROW;
END CATCH

drop SQL Server schema

I understand that you cannot simply drop an SQL server schema, you must first of all drop all the objects contained therein. I found this stored proc that performs the task of dropping all objects and then the schema itself.
Is there really no simpler way to drop a schema? Ideally, I'd like to find a way to do this without using a stored proc.
Also, it seems like the stored proc will cause errors if the schema name provided does not exist. I would like it to simply do nothing instead. I guess this is simply a matter of putting this pseudocode at the top of the script
IF #SchemaName NOT EXISTS
QUIT
Can someone convert this into language that SQL Server will understand?
The following at the top of the script should help:
IF SCHEMA_ID(#SchemaName) IS NULL
RETURN
SCHEMA_ID returns the schema ID associated with a schema name, and RETURN exits unconditionally from a query or procedure.
You have to remove all objects in the schame before dropping it or migrate all objects to a new schema. There is no "wildcard" option for either
To exit a stored procedure before any further processing...
IF SCHEMA_ID(#SchemaName) IS NULL
RETURN
if exists(select * from sys.schemas where name = #SchemaName)
begin
-- Your work
end
You must drop all objects before dropping the schema.
To check if a schema exists:
IF NOT EXISTS (select SCHEMA_NAME from INFORMATION_SCHEMA.SCHEMATA WHERE CATALOG_NAME='YOUR DB NAME HERE' and SCHEMA_NAME=#SchemaName)
BEGIN
-- Do some processing...
return
END

drop and create SQL Server procedure

I'm trying to drop and create a procedure in a single script. I tried this:
IF EXISTS (SELECT * FROM sys.objects WHERE type = 'P' AND name = 'Foo')
DROP PROCEDURE Foo
GO
CREATE PROCEDURE dbo.Foo
-- procedure body here
But I get an error:
Incorrect syntax near 'GO'.
If I remove GO, I get an error:
'CREATE/ALTER PROCEDURE' must be the first statement in a query batch
Update
These scripts are being executed by a Java build tool (Maven)
GO is not actually a valid term in T-SQL, it's merely the separator that the Microsoft management tools use to delimit query batches.
What are you using to run the script? If you're trying to do it in code then you'll need to split it into two statements, perhaps using a regex to split on ^GO$
Try
IF OBJECT_ID ('idhere') IS NOT NULL
DROP PROCEDURE idhere
GO
CREATE PROCEDURE idhere
#paramsHere PARAMTYPE
AS
BEGIN
//...code here...
END
GO
This is how I do it, I'm not sure what version of SQL SERVER my work uses, I believe its 2005.
The easiest way I've found for executing a large scripts outside SSMS from a tool is to use the SQLCMD. (iSQL pre sql 2005) This will work with any environment that allows you to run a shell command.
From the MSDN article
The sqlcmd utility lets you enter
Transact-SQL statements, system
procedures, and script files at the
command prompt, in Query Editor in
SQLCMD mode, in a Windows script file
or in an operating system (Cmd.exe)
job step of a SQL Server Agent job.
This utility uses OLE DB to execute
Transact-SQL batches.
It would be better to use this syntax for the existence check:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[foo]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[foo]
GO
As written, if there was a foo sproc in any schema it would try to drop it. Not sure if that will make your problem go away though. If you use SSMS, there is an option to script a stored procedure as DROP and CREATE; that syntax should work.
Check Jon Galloway's post: Handling "GO" Separators in SQL Scripts - the easy way
It might have the answer you are seeking.

Errors: "INSERT EXEC statement cannot be nested." and "Cannot use the ROLLBACK statement within an INSERT-EXEC statement." How to solve this?

I have three stored procedures Sp1, Sp2 and Sp3.
The first one (Sp1) will execute the second one (Sp2) and save returned data into #tempTB1 and the second one will execute the third one (Sp3) and save data into #tempTB2.
If I execute the Sp2 it will work and it will return me all my data from the Sp3, but the problem is in the Sp1, when I execute it it will display this error:
INSERT EXEC statement cannot be nested
I tried to change the place of execute Sp2 and it display me another error:
Cannot use the ROLLBACK statement
within an INSERT-EXEC statement.
This is a common issue when attempting to 'bubble' up data from a chain of stored procedures. A restriction in SQL Server is you can only have one INSERT-EXEC active at a time. I recommend looking at How to Share Data Between Stored Procedures which is a very thorough article on patterns to work around this type of problem.
For example a work around could be to turn Sp3 into a Table-valued function.
This is the only "simple" way to do this in SQL Server without some giant convoluted created function or executed sql string call, both of which are terrible solutions:
create a temp table
openrowset your stored procedure data into it
EXAMPLE:
INSERT INTO #YOUR_TEMP_TABLE
SELECT * FROM OPENROWSET ('SQLOLEDB','Server=(local);TRUSTED_CONNECTION=YES;','set fmtonly off EXEC [ServerName].dbo.[StoredProcedureName] 1,2,3')
Note: You MUST use 'set fmtonly off', AND you CANNOT add dynamic sql to this either inside the openrowset call, either for the string containing your stored procedure parameters or for the table name. Thats why you have to use a temp table rather than table variables, which would have been better, as it out performs temp table in most cases.
OK, encouraged by jimhark here is an example of the old single hash table approach: -
CREATE PROCEDURE SP3 as
BEGIN
SELECT 1, 'Data1'
UNION ALL
SELECT 2, 'Data2'
END
go
CREATE PROCEDURE SP2 as
BEGIN
if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
INSERT INTO #tmp1
EXEC SP3
else
EXEC SP3
END
go
CREATE PROCEDURE SP1 as
BEGIN
EXEC SP2
END
GO
/*
--I want some data back from SP3
-- Just run the SP1
EXEC SP1
*/
/*
--I want some data back from SP3 into a table to do something useful
--Try run this - get an error - can't nest Execs
if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
DROP TABLE #tmp1
CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))
INSERT INTO #tmp1
EXEC SP1
*/
/*
--I want some data back from SP3 into a table to do something useful
--However, if we run this single hash temp table it is in scope anyway so
--no need for the exec insert
if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
DROP TABLE #tmp1
CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))
EXEC SP1
SELECT * FROM #tmp1
*/
My work around for this problem has always been to use the principle that single hash temp tables are in scope to any called procs. So, I have an option switch in the proc parameters (default set to off). If this is switched on, the called proc will insert the results into the temp table created in the calling proc. I think in the past I have taken it a step further and put some code in the called proc to check if the single hash table exists in scope, if it does then insert the code, otherwise return the result set. Seems to work well - best way of passing large data sets between procs.
This trick works for me.
You don't have this problem on remote server, because on remote server, the last insert command waits for the result of previous command to execute. It's not the case on same server.
Profit that situation for a workaround.
If you have the right permission to create a Linked Server, do it.
Create the same server as linked server.
in SSMS, log into your server
go to "Server Object
Right Click on "Linked Servers", then "New Linked Server"
on the dialog, give any name of your linked server : eg: THISSERVER
server type is "Other data source"
Provider : Microsoft OLE DB Provider for SQL server
Data source: your IP, it can be also just a dot (.), because it's localhost
Go to the tab "Security" and choose the 3rd one "Be made using the login's current security context"
You can edit the server options (3rd tab) if you want
Press OK, your linked server is created
now your Sql command in the SP1 is
insert into #myTempTable
exec THISSERVER.MY_DATABASE_NAME.MY_SCHEMA.SP2
Believe me, it works even you have dynamic insert in SP2
I found a work around is to convert one of the prods into a table valued function. I realize that is not always possible, and introduces its own limitations. However, I have been able to always find at least one of the procedures a good candidate for this. I like this solution, because it doesn't introduce any "hacks" to the solution.
I encountered this issue when trying to import the results of a Stored Proc into a temp table, and that Stored Proc inserted into a temp table as part of its own operation. The issue being that SQL Server does not allow the same process to write to two different temp tables at the same time.
The accepted OPENROWSET answer works fine, but I needed to avoid using any Dynamic SQL or an external OLE provider in my process, so I went a different route.
One easy workaround I found was to change the temporary table in my stored procedure to a table variable. It works exactly the same as it did with a temp table, but no longer conflicts with my other temp table insert.
Just to head off the comment I know that a few of you are about to write, warning me off Table Variables as performance killers... All I can say to you is that in 2020 it pays dividends not to be afraid of Table Variables. If this was 2008 and my Database was hosted on a server with 16GB RAM and running off 5400RPM HDDs, I might agree with you. But it's 2020 and I have an SSD array as my primary storage and hundreds of gigs of RAM. I could load my entire company's database to a table variable and still have plenty of RAM to spare.
Table Variables are back on the menu!
I recommend to read this entire article. Below is the most relevant section of that article that addresses your question:
Rollback and Error Handling is Difficult
In my articles on Error and Transaction Handling in SQL Server, I suggest that you should always have an error handler like
BEGIN CATCH
IF ##trancount > 0 ROLLBACK TRANSACTION
EXEC error_handler_sp
RETURN 55555
END CATCH
The idea is that even if you do not start a transaction in the procedure, you should always include a ROLLBACK, because if you were not able to fulfil your contract, the transaction is not valid.
Unfortunately, this does not work well with INSERT-EXEC. If the called procedure executes a ROLLBACK statement, this happens:
Msg 3915, Level 16, State 0, Procedure SalesByStore, Line 9 Cannot use the ROLLBACK statement within an INSERT-EXEC statement.
The execution of the stored procedure is aborted. If there is no CATCH handler anywhere, the entire batch is aborted, and the transaction is rolled back. If the INSERT-EXEC is inside TRY-CATCH, that CATCH handler will fire, but the transaction is doomed, that is, you must roll it back. The net effect is that the rollback is achieved as requested, but the original error message that triggered the rollback is lost. That may seem like a small thing, but it makes troubleshooting much more difficult, because when you see this error, all you know is that something went wrong, but you don't know what.
I had the same issue and concern over duplicate code in two or more sprocs. I ended up adding an additional attribute for "mode". This allowed common code to exist inside one sproc and the mode directed flow and result set of the sproc.
what about just store the output to the static table ? Like
-- SubProcedure: subProcedureName
---------------------------------
-- Save the value
DELETE lastValue_subProcedureName
INSERT INTO lastValue_subProcedureName (Value)
SELECT #Value
-- Return the value
SELECT #Value
-- Procedure
--------------------------------------------
-- get last value of subProcedureName
SELECT Value FROM lastValue_subProcedureName
its not ideal, but its so simple and you don't need to rewrite everything.
UPDATE:
the previous solution does not work well with parallel queries (async and multiuser accessing) therefore now Iam using temp tables
-- A local temporary table created in a stored procedure is dropped automatically when the stored procedure is finished.
-- The table can be referenced by any nested stored procedures executed by the stored procedure that created the table.
-- The table cannot be referenced by the process that called the stored procedure that created the table.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NULL
CREATE TABLE #lastValue_spGetData (Value INT)
-- trigger stored procedure with special silent parameter
EXEC dbo.spGetData 1 --silent mode parameter
nested spGetData stored procedure content
-- Save the output if temporary table exists.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NOT NULL
BEGIN
DELETE #lastValue_spGetData
INSERT INTO #lastValue_spGetData(Value)
SELECT Col1 FROM dbo.Table1
END
-- stored procedure return
IF #silentMode = 0
SELECT Col1 FROM dbo.Table1
Declare an output cursor variable to the inner sp :
#c CURSOR VARYING OUTPUT
Then declare a cursor c to the select you want to return.
Then open the cursor.
Then set the reference:
DECLARE c CURSOR LOCAL FAST_FORWARD READ_ONLY FOR
SELECT ...
OPEN c
SET #c = c
DO NOT close or reallocate.
Now call the inner sp from the outer one supplying a cursor parameter like:
exec sp_abc a,b,c,, #cOUT OUTPUT
Once the inner sp executes, your #cOUT is ready to fetch. Loop and then close and deallocate.
If you are able to use other associated technologies such as C#, I suggest using the built in SQL command with Transaction parameter.
var sqlCommand = new SqlCommand(commandText, null, transaction);
I've created a simple Console App that demonstrates this ability which can be found here:
https://github.com/hecked12/SQL-Transaction-Using-C-Sharp
In short, C# allows you to overcome this limitation where you can inspect the output of each stored procedure and use that output however you like, for example you can feed it to another stored procedure. If the output is ok, you can commit the transaction, otherwise, you can revert the changes using rollback.
On SQL Server 2008 R2, I had a mismatch in table columns that caused the Rollback error. It went away when I fixed my sqlcmd table variable populated by the insert-exec statement to match that returned by the stored proc. It was missing org_code. In a windows cmd file, it loads result of stored procedure and selects it.
set SQLTXT= declare #resets as table (org_id nvarchar(9), org_code char(4), ^
tin(char9), old_strt_dt char(10), strt_dt char(10)); ^
insert #resets exec rsp_reset; ^
select * from #resets;
sqlcmd -U user -P pass -d database -S server -Q "%SQLTXT%" -o "OrgReport.txt"

Programmatically retrieve SQL Server stored procedure source that is identical to the source returned by the SQL Server Management Studio gui?

Any pointers on how I can programmatically get exactly the identical stored procedure source from SQL Server 2005, as when I right-click on that stored procedure in SQL Server Management Studio and select modify?
I'm trying using SMO, but there are some textual differences. The procedure always has CREATE, not ALTER, and there are some differences in the header, such as missing GOs in the version I'm getting programmatically. I can fix these up, but perhaps there is a better way?
Again, I'm in SQL Server 2005, using SMSE. Using SMO via Visual Studio 8 2008.
Update: Gotten some answers that tell the basics of how to retrieve the stored procedure. What I'm looking for is retrieving the text identical (or nearly identical) to what the GUI generates.
Example: for sp_mysp, right-click in Management Studio, select modify. This generates:
USE [MY_DB]
GO
/****** Object: StoredProcedure [dbo].[sp_mysp] Script Date: 01/21/2009 17:43:18 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:
-- Create date:
-- Description:
-- =============================================
ALTER PROCEDURE [dbo].[sp_mysp]
I'd like to programmatically get the same thing (notice the GOs in the header, and the fact that it's an ALTER PROCEDURE. Ideally, I'd like to get this with minimal programmatic fixing up of the source retrieved.
I'd be happy to only get something that differed in the Script Date details . . .
EXEC sp_helptext 'your procedure name';
This avoids the problem with INFORMATION_SCHEMA approach wherein the stored procedure gets cut off if it is too long.
Update: David writes that this isn't identical to his sproc...perhaps because it returns the lines as 'records' to preserve formatting? If you want to see the results in a more 'natural' format, you can use Ctrl-T first (output as text) and it should print it out exactly as you've entered it. If you are doing this in code, it is trivial to do a foreach to put together your results in exactly the same way.
Update 2: This will provide the source with a "CREATE PROCEDURE" rather than an "ALTER PROCEDURE" but I know of no way to make it use "ALTER" instead. Kind of a trivial thing, though, isn't it?
Update 3: See the comments for some more insight on how to maintain your SQL DDL (database structure) in a source control system. That is really the key to this question.
You will have to hand code it, SQL Profiler reveals the following.
SMSE executes quite a long string of queries when it generates the statement.
The following query (or something along its lines) is used to extract the text:
SELECT
NULL AS [Text],
ISNULL(smsp.definition, ssmsp.definition) AS [Definition]
FROM
sys.all_objects AS sp
LEFT OUTER JOIN sys.sql_modules AS smsp ON smsp.object_id = sp.object_id
LEFT OUTER JOIN sys.system_sql_modules AS ssmsp ON ssmsp.object_id = sp.object_id
WHERE
(sp.type = N'P' OR sp.type = N'RF' OR sp.type='PC')and(sp.name=N'#test___________________________________________________________________________________________________________________00003EE1' and SCHEMA_NAME(sp.schema_id)=N'dbo')
It returns the pure CREATE which is then substituted with ALTER in code somewhere.
The SET ANSI NULL stuff and the GO statements and dates are all prepended to this.
Go with sp_helptext, its simpler ...
You said programmatically, right? I hope C# is ok. I know you said that you tried SMO and it didn't quite do what you wanted, so this probably won't be perfect for your request, but it will programmatically read out legit SQL statements that you could run to recreate the stored procedure. If it doesn't have the GO statements that you want, you can probably assume that each of the strings in the StringCollection could have a GO after it. You may not get that comment with the date and time in it, but in my similar sounding project (big-ass deployment tool that has to back up everything individually), this has done rather nicely. If you have a prior base that you wanted to work from, and you still have the original database to run this on, I'd consider tossing the initial effort and restandardizing on this output.
using System.Data.SqlClient;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
…
string connectionString = … /* some connection string */;
ServerConnection sc = new ServerConnection(connectionString);
Server s = new Server(connection);
Database db = new Database(s, … /* database name */);
StoredProcedure sp = new StoredProcedure(db, … /* stored procedure name */);
StringCollection statements = sp.Script;
Use the following select statement to get the whole definition:
select ROUTINE_DEFINITION
from INFORMATION_SCHEMA.ROUTINES
where ROUTINE_NAME = 'someprocname'
I guess that SSMS and other tools read this out and make changes where necessary, such as changing CREATE to ALTER. As far as I know, SQL stores not other representations of the procedure.
I agree with Mark. I set the output to text mode and then sp_HelpText 'sproc'. I have this binded to Crtl-F1 to make it easy.
The Databse Publishing Wizard can dump the schema (and other objects) from the command line.
I just want to note that instead of using find and replace to change create procedure to alter procedure, you are just as well to use a drop, you can put it right at the top and it does require text searching.
IF exists (SELECT * FROM sys.objects
WHERE object_id = OBJECT_ID(N'sp_name')
and type in ('P','V') --procedure or view
)
DROP sp_name
GO
If you are sure it's there, I guess you could just drop it too, but I wouldn't recommend that. Don't forget the go, since create procedure must be the first and only statement in a batch.
Or the lazy approach:
IF OBJECT_ID(N'sp_name') is not null
DROP sp_name
GO
I saw a article via link.
There are four methods, I just did a short summary here for helping other programmers.
EXEC sp_helptext 'sp_name';
SELECT OBJECT_ID('sp_name')
SELECT OBJECT_DEFINITION(
OBJECT_ID('sp_name')
) AS [Definition];
SELECT * FROM sys.sql_modules WHERE object_id = object_id('sp_name');
To alter a stored procedure, here's the C# code:
SqlConnection con = new SqlConnection("your connection string");
con.Open();
cmd.CommandType = System.Data.CommandType.Text;
string sql = File.ReadAllText(YUOR_SP_SCRIPT_FILENAME);
cmd.CommandText = sql;
cmd.Connection = con;
cmd.ExecuteNonQuery();
con.Close();
Things to note:
Make sure the USER in the connection string have the right to alter SP
Remove all the GO,SET ANSI_NULLS XX,SET QUOTED_IDENTIFIER statements from the script file. (If you don't, the SqlCommand will throw an error).

Resources