Using sqlcmd to execute deploy scripts generated by DAC in a transaction - sql-server-data-tools

We have a DACPAC (sqlproj) solution which has some tables and a post-deployment script which runs some DML queries.
If the DML query fails (I'm raising an error with severity=20), I would like to rollback all changes - including the DDL changes done by the dacpac and the post-deployment file changes. This is especially useful when I would be upgrading an existing target database.
I'm striving for an atomic DB upgrade when the DACPAC is published - all DDL changes mentioned in the DACPAC solution should be published only when everything in the post deployment script is successful.
Since DACPAC DDL changes are committed before it invokes post-deployment script, I thought generating all the DAC changes as a single script file using DacServices.GenerateDeployScript will help. Doesnt look so straight forward.
Has anyone tried something like this (and failed/passed)?
I'm facing many challenges like...
Create/alter Database shouldnt be in a transaction.
Rollback not happening at all.
[Edit 10Nov]: Pasting the deploy script generated by the dacpac here, so that I can explain my issue better (hopefully)
/*
Deployment script for 9Nov
This code was generated by a tool.
Changes to this file may cause incorrect behavior and will be lost if
the code is regenerated.
*/
GO
SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
GO
:setvar INSTALL_DIR "D:\EDW_9Nov\"
:setvar DatabaseName "9Nov"
:setvar DefaultFilePrefix "9Nov"
:setvar DefaultDataPath "C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\"
:setvar DefaultLogPath "C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\"
GO
:on error exit
GO
/*
Detect SQLCMD mode and disable script execution if SQLCMD mode is not supported.
To re-enable the script after enabling SQLCMD mode, execute the following:
SET NOEXEC OFF;
*/
:setvar __IsSqlCmdEnabled "True"
GO
IF N'$(__IsSqlCmdEnabled)' NOT LIKE N'True'
BEGIN
PRINT N'SQLCMD mode must be enabled to successfully execute this script.';
SET NOEXEC ON;
END
GO
USE [master];
GO
IF (DB_ID(N'$(DatabaseName)') IS NOT NULL)
BEGIN
ALTER DATABASE [$(DatabaseName)]
SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [$(DatabaseName)];
END
GO
PRINT N'Creating $(DatabaseName)...'
GO
CREATE DATABASE [$(DatabaseName)]
ON
PRIMARY(NAME = [$(DatabaseName)], FILENAME = N'$(DefaultDataPath)$(DefaultFilePrefix)_Primary.mdf')
LOG ON (NAME = [$(DatabaseName)_log], FILENAME = N'$(DefaultLogPath)$(DefaultFilePrefix)_Primary.ldf') COLLATE SQL_Latin1_General_CP1_CI_AS
GO
IF EXISTS (SELECT 1
FROM [master].[dbo].[sysdatabases]
WHERE [name] = N'$(DatabaseName)')
BEGIN
ALTER DATABASE [$(DatabaseName)]
SET ANSI_NULLS ON,
ANSI_PADDING ON,
ANSI_WARNINGS ON,
ARITHABORT ON,
CONCAT_NULL_YIELDS_NULL ON,
NUMERIC_ROUNDABORT OFF,
QUOTED_IDENTIFIER ON,
ANSI_NULL_DEFAULT ON,
CURSOR_DEFAULT LOCAL,
RECOVERY SIMPLE,
CURSOR_CLOSE_ON_COMMIT OFF,
AUTO_CREATE_STATISTICS ON,
AUTO_SHRINK OFF,
AUTO_UPDATE_STATISTICS ON,
RECURSIVE_TRIGGERS OFF
WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [$(DatabaseName)]
SET AUTO_CLOSE OFF
WITH ROLLBACK IMMEDIATE;
END
GO
IF EXISTS (SELECT 1
FROM [master].[dbo].[sysdatabases]
WHERE [name] = N'$(DatabaseName)')
BEGIN
ALTER DATABASE [$(DatabaseName)]
SET ALLOW_SNAPSHOT_ISOLATION OFF;
END
GO
IF EXISTS (SELECT 1
FROM [master].[dbo].[sysdatabases]
WHERE [name] = N'$(DatabaseName)')
BEGIN
ALTER DATABASE [$(DatabaseName)]
SET READ_COMMITTED_SNAPSHOT OFF
WITH ROLLBACK IMMEDIATE;
END
GO
IF EXISTS (SELECT 1
FROM [master].[dbo].[sysdatabases]
WHERE [name] = N'$(DatabaseName)')
BEGIN
ALTER DATABASE [$(DatabaseName)]
SET AUTO_UPDATE_STATISTICS_ASYNC OFF,
PAGE_VERIFY NONE,
DATE_CORRELATION_OPTIMIZATION OFF,
DISABLE_BROKER,
PARAMETERIZATION SIMPLE,
SUPPLEMENTAL_LOGGING OFF
WITH ROLLBACK IMMEDIATE;
END
GO
IF IS_SRVROLEMEMBER(N'sysadmin') = 1
BEGIN
IF EXISTS (SELECT 1
FROM [master].[dbo].[sysdatabases]
WHERE [name] = N'$(DatabaseName)')
BEGIN
EXECUTE sp_executesql N'ALTER DATABASE [$(DatabaseName)]
SET TRUSTWORTHY OFF,
DB_CHAINING OFF
WITH ROLLBACK IMMEDIATE';
END
END
ELSE
BEGIN
PRINT N'The database settings cannot be modified. You must be a SysAdmin to apply these settings.';
END
GO
IF IS_SRVROLEMEMBER(N'sysadmin') = 1
BEGIN
IF EXISTS (SELECT 1
FROM [master].[dbo].[sysdatabases]
WHERE [name] = N'$(DatabaseName)')
BEGIN
EXECUTE sp_executesql N'ALTER DATABASE [$(DatabaseName)]
SET HONOR_BROKER_PRIORITY OFF
WITH ROLLBACK IMMEDIATE';
END
END
ELSE
BEGIN
PRINT N'The database settings cannot be modified. You must be a SysAdmin to apply these settings.';
END
GO
ALTER DATABASE [$(DatabaseName)]
SET TARGET_RECOVERY_TIME = 0 SECONDS
WITH ROLLBACK IMMEDIATE;
GO
IF EXISTS (SELECT 1
FROM [master].[dbo].[sysdatabases]
WHERE [name] = N'$(DatabaseName)')
BEGIN
ALTER DATABASE [$(DatabaseName)]
SET FILESTREAM(NON_TRANSACTED_ACCESS = OFF),
CONTAINMENT = NONE
WITH ROLLBACK IMMEDIATE;
END
GO
IF EXISTS (SELECT 1
FROM [master].[dbo].[sysdatabases]
WHERE [name] = N'$(DatabaseName)')
BEGIN
ALTER DATABASE [$(DatabaseName)]
SET AUTO_CREATE_STATISTICS ON(INCREMENTAL = OFF),
MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT = OFF,
DELAYED_DURABILITY = DISABLED
WITH ROLLBACK IMMEDIATE;
END
GO
USE [$(DatabaseName)];
GO
IF fulltextserviceproperty(N'IsFulltextInstalled') = 1
EXECUTE sp_fulltext_database 'enable';
GO
PRINT N'Creating [EDW_INTERNAL]...';
GO
CREATE SCHEMA [EDW_INTERNAL]
AUTHORIZATION [dbo];
GO
PRINT N'Creating [EDW_INTERNAL].[DB_VERSIONS]...';
GO
CREATE TABLE [EDW_INTERNAL].[DB_VERSIONS] (
[ID] BIGINT IDENTITY (1, 1) NOT NULL,
[MODULE] VARCHAR (30) NOT NULL,
[FROM_VERSION] VARCHAR (20) NOT NULL,
[TO_VERSION] VARCHAR (20) NOT NULL,
[UPGRADE_DML_APPLIED_YN] VARCHAR (1) NOT NULL,
CONSTRAINT [PK_DB_VERSIONS] PRIMARY KEY CLUSTERED ([MODULE] ASC, [FROM_VERSION] ASC, [TO_VERSION] ASC)
);
GO
PRINT N'Creating [EDW_INTERNAL].[DML_UPGR_SCRIPT_MASTER]...';
GO
CREATE TABLE [EDW_INTERNAL].[DML_UPGR_SCRIPT_MASTER] (
[MODULE] VARCHAR (30) NOT NULL,
[FROM_VERSION] VARCHAR (20) NOT NULL,
[TO_VERSION] VARCHAR (20) NOT NULL,
[APPLY_ORDER] INT NOT NULL,
[UPGR_SCRIPT_FILEPATH] VARCHAR (1024) NOT NULL,
CONSTRAINT [PK_DML_UPGR_SCRIPT_MASTER] PRIMARY KEY CLUSTERED ([APPLY_ORDER] ASC, [TO_VERSION] ASC, [FROM_VERSION] ASC, [MODULE] ASC)
);
GO
PRINT N'Creating unnamed constraint on [EDW_INTERNAL].[DB_VERSIONS]...';
GO
ALTER TABLE [EDW_INTERNAL].[DB_VERSIONS]
ADD DEFAULT 'N' FOR [UPGRADE_DML_APPLIED_YN];
GO
PRINT N'Creating [EDW_INTERNAL].[UPGRADE_DML]...';
GO
CREATE PROCEDURE EDW_INTERNAL.UPGRADE_DML
#Module VARCHAR(30)
AS
BEGIN
DECLARE #Failure bit = 1;
IF #Failure = 1
BEGIN
RAISERROR
(N'One or more database upgrade query statements have failed. Please check the DML Upgrade Log table for details.',
20, -- Severity.
1 -- State
) WITH LOG;
END
END
GO
/*
Post-Deployment Script Template
--------------------------------------------------------------------------------------
This file contains SQL statements that will be appended to the build script.
Use SQLCMD syntax to include a file in the post-deployment script.
Example: :r .\myfile.sql
Use SQLCMD syntax to reference a variable in the post-deployment script.
Example: :setvar TableName MyTable
SELECT * FROM [$(TableName)]
--------------------------------------------------------------------------------------
*/
-- Reference to load the Version Upgrade tables with rows
--:r VersionUpgradeRowsPopulate.sql
-- execute
EXEC [EDW_INTERNAL].[UPGRADE_DML] #MODULE = 'Test_Common'
GO
GO
DECLARE #VarDecimalSupported AS BIT;
SELECT #VarDecimalSupported = 0;
IF ((ServerProperty(N'EngineEdition') = 3)
AND (((##microsoftversion / power(2, 24) = 9)
AND (##microsoftversion & 0xffff >= 3024))
OR ((##microsoftversion / power(2, 24) = 10)
AND (##microsoftversion & 0xffff >= 1600))))
SELECT #VarDecimalSupported = 1;
IF (#VarDecimalSupported > 0)
BEGIN
EXECUTE sp_db_vardecimal_storage_format N'$(DatabaseName)', 'ON';
END
GO
PRINT N'Update complete.';
GO

Related

Execute two Insert Statements in one SQL Server stored procedure?

I am trying to implement two insert statements in one SQL Server stored procedure. I did something like this, but the second insert statement does not work. Any ideas? Thanks.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[InsertWrite]
#Die nvarchar(8),
#TSO int,
#TSOStatus nvarchar(50);
as
BEGIN
SET NOCOUNT OFF;
INSERT INTO Table1 (TSO, TSOStatus)
VALUES (#TSO, #TSOStatus)
if ##rowcount > 0
BEGIN
INSERT INTO Table2 (TSO, Die)
VALUES (#TSO, #Die);
COMMIT
END
ELSE
ROLLBACK
END
You don't need that extra commit and rollback since I don't see anywhere you started the transaction. Not sure exactly what you are trying but the below code works just fine
CREATE PROCEDURE [dbo].[InsertWrite]
#Die varchar(8),
#TSO varchar(10),
#TSOStatus varchar(50)
as
BEGIN
SET NOCOUNT OFF;
INSERT INTO Tab1 (TSO, TSOStatus) Values (#TSO,#TSOStatus);
if ##rowcount > 0
INSERT INTO Tab2 (TSO,Die) Values (#TSO, #Die);
END
You can simplify this considerably:
create procedure dbo.InsertWrite
#Die nvarchar(8) ,
#TSO int ,
#TSOStatus nvarchar(50)
as
set nocount on
begin transaction
begin try
insert Table1 ( TSO , TSOStatus ) values ( #TSO , #TSOStatus )
insert Table2 ( TSO , Die ) values ( #TSO , #Die )
commit transaction
end try
begin catch
rollback transaction
throw
end catch
return 0
go

SQL Server triggers aren't working with Linq to SQL on ASP.NET

My work colleague is making the ASP.NET Web Forms application collecting data. I'm administrating SQL Server database of it. Based on databse he makes objects to Web Forms using Linq to SQL. He wanted me to make recodrds in Osoby to change dataDodania with date of generation the object and dataModyfikacji with date of last update. Having experience in PL/SQL I made simple triggers for this. The problem is that triggers work when I run SQL statements in SQL Server Management Studio 2008 nicely, but when used in application - they are omitted, not making changes needed. Here is triggers SQL code:
CREATE TRIGGER [dbo].[DodanieOsoby]
ON [dbo].[Osoby]
INSTEAD OF INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
INSERT INTO Osoby(dataDodania, dataModyfikacji, loginId, rola, imie, imieDrugie, nazwisko, plec, wiek,pESEL,wyksztalcenie,opieka,ulica, nrDom, nrLokal, miejscowosc, obszar, kodPoczty, telefonKontakt, telefonStacjo, email, zatrudnienie, stanowisko, przedsiebiorstwo)
SELECT GETDATE(), GETDATE(), loginId, rola, imie, imieDrugie, nazwisko, plec, wiek, pESEL, wyksztalcenie,opieka,ulica, nrDom, nrLokal, miejscowosc, obszar, kodPoczty, telefonKontakt, telefonStacjo, email, zatrudnienie, stanowisko, przedsiebiorstwo
FROM inserted
END
And for UPDATE of Osoby...
CREATE TRIGGER [dbo].[AktualizacjaOsoby]
ON [dbo].[Osoby]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE Osoby
SET dataModyfikacji = GETDATE()
WHERE id in
(SELECT DISTINCT id from Inserted)
END
Possible this be helpful for you (if dbo.Osoby is view) -
ALTER TRIGGER dbo.trg_IOIU_vw_WorkOut
ON dbo.vw_WorkOut
INSTEAD OF INSERT, UPDATE
AS BEGIN
SET NOCOUNT ON
SET XACT_ABORT ON
DECLARE
#WorkOutID BIGINT
, #DateOut DATETIME
, #EmployeeID INT
DECLARE workout CURSOR LOCAL READ_ONLY FAST_FORWARD FOR
SELECT
WorkOutID
, DateOut
, EmployeeID
FROM INSERTED
OPEN workout
FETCH NEXT FROM workout INTO
#WorkOutID
, #DateOut
, #EmployeeID
WHILE ##FETCH_STATUS = 0 BEGIN
IF NOT EXISTS(
SELECT 1
FROM dbo.WorkOut
WHERE WorkOutID = #WorkOutID
)
BEGIN
INSERT INTO dbo.WorkOut
(
EmployeeID
, DateOut
)
SELECT
#EmployeeID
, #DateOut
SELECT SCOPE_IDENTITY() -- if you use LINQ need return new ID to client
END
ELSE BEGIN
UPDATE dbo.WorkOut
SET
EmployeeID = #EmployeeID
, DateOut = #DateOut
WHERE WorkOutID = #WorkOutID
END
FETCH NEXT FROM workout INTO
#WorkOutID
, #DateOut
, #EmployeeID
END
CLOSE workout
DEALLOCATE workout
END

SQL Server stored procedure returning 'fail'

I have a stored procedure in SQL Server 2008 that needs to delete a few rows of data. But when I run it, it returns a fail and a value of -6.
ALTER procedure [dbo].[p_CaseFiles_Exhibits_DeleteExhibits]
#ExhibitID int
, #Message nvarchar(50) output
as
declare #FileID int
set #FileID = (select FileID from CaseFileExhibits where ExhibitID = #ExhibitID)
begin transaction
begin try
delete from CaseFileExhibitMovementTracking where ExhibitID = #ExhibitID
delete from CaseFileExhibitAttachments where CaseFileExhibitID = #ExhibitID
delete from CaseFileExhibits where ExhibitID = #ExhibitID
delete from CaseFileExhibitPropertyLink where ExhibitID = #ExhibitID
update CaseFileQuickStats set ExhibitCount = ExhibitCount -1 where CaseFileID = #FileID
commit transaction
end try
begin catch
set #Message='Fail'
rollback transaction
end catch
I can't seem to find what's wrong.
You're able to check out the messages yourself, add this to your CATCH block:
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_SEVERITY() AS ErrorSeverity
,ERROR_STATE() AS ErrorState
,ERROR_PROCEDURE() AS ErrorProcedure
,ERROR_LINE() AS ErrorLine
,ERROR_MESSAGE() AS ErrorMessage;
You may want to change that SELECT to PRINT, and then you'll be able to see the results in the 'Messages' tab when running the SP within SSMS.
I suspect it's a problem with a Foreign Key or a possible trigger.

How to drop multiple databases based on a prefix string

example_ or e_
I want to drop all databases that match the prefix e_, so that e_database1, e_database2 and so forth are dropped.
Commands that do not work:
mysql drop database e_%
mysql drop database e_*
I'm not looking for all the tables in a given database, but all the databases in a given MySQL server.
You could do this with a stored proc like this:
/* Start stored proc */
DELIMITER //
DROP PROCEDURE IF EXISTS db_clean_up //
CREATE PROCEDURE db_clean_up
(
)
BEGIN
declare done bit default false;
declare deleted varchar(255);
-- Drop DBs
DECLARE cur1 CURSOR FOR SELECT
SCHEMA_NAME
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME LIKE 'db_prefix%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
createLoop: LOOP
FETCH cur1 INTO deleted;
IF done THEN
LEAVE createLoop;
END IF;
SET #query = CONCAT('DROP DATABASE `', deleted, '`;');
PREPARE stmt1 FROM #query;
EXECUTE stmt1;
END LOOP createLoop;
CLOSE cur1;
END //
delimiter ;
/* End stored proc */

Preferred Method to Catch Specific OleDB Error

Ok - I have a situation in which I must execute a dynamically built stored procedure against tables that may, or may not be in the database. The data retrieved is then shunted to a VB.Net backed ASP based report page. By design, if the tables are not present in the database, the relevant data is automatically hidden on the report page. Currently, I'm doing this by checking for the inevitable error, and hiding the div in the catch block. A bit kludgy, but it worked.
I can't include the VB code-behind, but the relevant stored procedure is included below.
However, a problem with this method was recently brought to my attention when, for no apparent reason, the div was being hidden even though the proper data was available. As it turned out, the user trying to select the table in the dynamic SQL call didn't have the proper select permissions, an easy enough fix once I could track it down.
So, two fold question. First and foremost - is there a better way to check for a missing table than through catching the error in the VB.Net codebehind? All things considered, I'd rather save the error checking for an actual error. Secondly, is there a preferred method to squirrel out a particular OLE DB error out of the general object caught by the try->catch block other than just checking the actual stack trace string?
SQL Query - The main gist of the code is that, due to the design of the database, I have to determine the name of the actual table being targeted manually. The database records jobs in a single table, but each job also gets its own table for processing data on the items processed in that job, and it's data from those tables I have to retrieve. Absolutely nothing I can do about this setup, unfortunately.
DECLARE #sql NVarChar(Max),
#params NVarChar(Max),
#where NVarChar(Max)
-- Retained for live testing of stored procedure.
-- DECLARE #Table NvarChar(255) SET #Table = N'tblMSGExportMessage_10000'
-- DECLARE #AcctID Integer SET #AcctID = 10000
-- DECLARE #Type Integer SET #Type = 0 -- 0 = Errors only, 1 = All Messages
-- DECLARE #Count Integer
-- Sets our parameters for our two dynamic SQL calls.
SELECT #params = N'#MsgExportAccount INT, #cnt INT OUTPUT'
-- Sets our where clause dependent upon whether we want all results or just errors.
IF #Type = 0
BEGIN
SELECT #where =
N' AND ( mem.[MSGExportStatus_OPT_CD] IN ( 11100, 11102 ) ' +
N' OR mem.[IngestionStatus_OPT_CD] IN ( 11800, 11802, 11803 ) ' +
N' OR mem.[ShortcutStatus_OPT_CD] IN ( 11500, 11502 ) ) '
END
ELSE
BEGIN
SELECT #where = N' '
END
-- Retrieves a count of messages.
SELECT #sql =
N'SELECT #cnt = Count( * ) FROM dbo.' + QuoteName( #Table ) + N' AS mem ' +
N'WHERE mem.[MSGExportAccount_ID] = #MsgExportAccount ' + #where
EXEC sp_executesql #sql, #params, #AcctID, #cnt = #Count OUTPUT
To avoid an error you could query the sysobjects table to find out if the table exists. Here's the SQL (replace YourTableNameHere). If it returns > 0 then the table exists. Create a stores procedure on the server that runs this query.
select count(*)
from sysobjects a with(nolock)
where a.xtype = 'U'
and a.name = 'YourTableNameHere'

Resources