How many tables altered by EXEC in SQL Server? - sql-server

I am changing the data type of columns in a few tables. and I have created a dynamic query for the same. Now I want to know the count of the tables which got altered actually by this query. I want to compare the expected and actual value if it matches then good else I need to run the rollback, Is there a way to do this?
Query:
USE FXecute
GO
DECLARE #ExpectedCounter INT=0, #ActualCounter AS INT=0
DROP TABLE IF EXISTS #List
CREATE TABLE #List (Command varchar(max), OrderBy INT IDENTITY(1,1))
INSERT INTO #List
SELECT
'ALTER TABLE ['+TABLE_SCHEMA+'].['+TABLE_NAME+'] ALTER COLUMN ['+COLUMN_NAME+'] DECIMAL(22,6)' AS 'Queries'
FROM FXecute.INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE = 'DECIMAL' and (column_name LIKE '%amount%' or column_name LIKE '%amt%' OR column_name LIKE '%total%'
OR column_name LIKE '%USD%') and TABLE_NAME NOT LIKE 'syncobj%'
SET #ActualCounter = ##ROWCOUNT;
PRINT 'Expected tables to be altered: ' + + CAST(#ActualCounter AS NVARCHAR(10))
DECLARE #sqlcmd VARCHAR(MAX);
SET #sqlcmd = (
SELECT STRING_AGG(Command,';' + CHAR(10)) WITHIN GROUP (ORDER BY [OrderBy]) as cmd
FROM #List
)
PRINT #sqlcmd
EXEC(#sqlcmd);
IF (#ExpectedCounter = #ActualCounter)
BEGIN
PRINT 'All Good'
END
ELSE
BEGIN
PRINT 'Something wrong'
--Rollback Script to be run
END
GO
Edit:
ALTER TABLE [dbo].[Table1] ALTER COLUMN [Column1] DECIMAL(22,6);
ALTER TABLE [dbo].[Table2] ALTER COLUMN [Column2] DECIMAL(22,6);
ALTER TABLE [dbo].[Table2] ALTER COLUMN [Column3] DECIMAL(22,6);
ALTER TABLE [dbo].[Table3] ALTER COLUMN [Column4] DECIMAL(22,6);

Related

Procedure to fix table builds cci to ci

All our platform team has asked our team to refrain from using unnecessary Clustered Columnstore
I'm trying to create a proc that we can use
pass in the table
and the index column.
I'm getting an error when trying to create
"Parse error at line: 25, column: 14: Incorrect syntax near '#A'"
I don't see an issue; what am I missing?
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROC [lab03].[Sproc_TableCCItoCI] #Table [NVARCHAR](150),#TheColumn [NVARCHAR](150)
AS
-- exec lab03.Sproc_TableCCItoCI #param1 = '', #param2 = ''
-- ==========================================================================================================================================================================
-- File Name:
--
-- Purpose: Fix tables built using clustered - columnstore
--
-- Version Date Changed by Description
-- ------- ---------- ------------------- -------------------------------------------
-- 1.0 2021-07-22 Sxxx Move to prod
--
-- ==========================================================================================================================================================================
-- converting Clustered Columnstore(CCI) index to Clustered Index (CI)
DECLARE #A Varchar(6)
SET #A = 'lab16.'
CREATE TABLE #A + #Table + '_convert'
WITH (
DISTRIBUTION = HASH (#TheColumn),
CLUSTERED INDEX (#TheColumn)
)
AS
SELECT
*
FROM #A + #Table
--save current table just in case, you’ll drop this as soon as process is complete
RENAME OBJECT #A + #Table TO #Table + '_Hold'
--renames new table to the existing name
RENAME OBJECT #A + #Table + '_convert' TO #Table;
--validate if desired then drop the hold table
DROP TABLE #A + #Table +'_Hold';
GO
I think you need to understand dynamic SQL better and I can highly recommend Erland Sommarskog's excellent article 'The Curse and Blessings of Dynamic SQL'.
I've adapted an example from our Synapse db which is doing something similar and shows a method of parameterising dynamic sql plus doing some error checking in Synapse which is worthwhile when things go wrong. Dedicated SQL Pools do not currently support the RETURN statement so just kind of ploughs on when errors occur so that's why it's good to collect as much info as possible through the error messages. See what you think of this:
IF OBJECT_ID('dbo.usp_convertTableToCI') IS NOT NULL
DROP PROC dbo.usp_convertTableToCI;
GO
CREATE PROC dbo.usp_convertTableToCI
#schemaName SYSNAME,
#tableName SYSNAME,
#columnName SYSNAME,
#debug_yn BIT
AS
DECLARE #sql NVARCHAR(MAX);
SET NOCOUNT ON;
-- Check schema exists
IF NOT EXISTS ( SELECT * FROM sys.schemas WHERE [name] = #schemaName )
RAISERROR( 'Source schema [%s] does not exist.', 16, 1, #schemaName );
-- Check table exists
IF NOT EXISTS ( SELECT * FROM sys.tables WHERE SCHEMA_NAME(schema_id) = #schemaName AND [name] = #tableName )
RAISERROR( 'Source table [%s].[%s] does not exist.', 16, 1, #schemaName, #tableName );
-- Check column exists
IF NOT EXISTS ( SELECT * FROM sys.columns WHERE OBJECT_SCHEMA_NAME(object_id) = #schemaName AND OBJECT_NAME(object_id) = #tableName AND [name] = #columnName )
RAISERROR( 'Column [%s] does not exist in table [%s].[%s].', 16, 1, #columnName, #schemaName, #tableName );
-- Assemble the dynamic SQL to swap the table over to clustered index
SET #sql = 'CREATE TABLE #schemaName.#tableName_convert
WITH (
DISTRIBUTION = HASH ( #columnName ),
CLUSTERED INDEX ( #columnName )
)
AS
SELECT *
FROM #schemaName.#tableName;
-- Save current table just in case, you’ll drop this as soon as process is complete
RENAME OBJECT #schemaName.#tableName TO #tableName_Hold
-- Renames new table to the existing name
RENAME OBJECT #schemaName.#tableName_convert TO #tableName;
-- Validate if desired then drop the hold table
--DROP TABLE #schemaName.#tableName_Hold;'
-- Replace the variable names
SET #sql = REPLACE(
REPLACE(
REPLACE( #sql, '#schemaName', #schemaName ),
'#tableName', #tableName ),
'#columnName', #columnName )
IF #debug_yn = 1
PRINT #sql;
ELSE
BEGIN
PRINT #sql;
EXEC(#sql);
END
GO
I would strongly suggest giving it some thorough tests for your scenarios.

"INSERT EXEC statement cannot be nested" with dynamic query

BACKGROUND
I have a procedure that has INSERT INTO ... EXEC (#sql) from dynamic SQL.
This procedure's results is INSERTed INTO table outside of procedure. When I attempt this, I get get an error:
[S0001][8164] An INSERT EXEC statement cannot be nested.
This error is discussed in other questions, but for inner procedures' calls instead of dynamic SQL:
Errors: "INSERT EXEC statement cannot be nested." and "Cannot use the ROLLBACK statement within an INSERT-EXEC statement." How to solve this?
An insert exec statement cannot be nested
Example with error:
-- =================================
-- table with test data
-- =================================
CREATE TABLE dbo.TestInsertIntoDynamicData1
(
data nvarchar(max)
)
INSERT INTO dbo.TestInsertIntoDynamicData1
VALUES ('one1'), ('two1'), ('three1')
GO
-- =================================
-- another table with test data
-- =================================
CREATE TABLE dbo.TestInsertIntoDynamicData2
(
data nvarchar(max)
)
INSERT INTO dbo.TestInsertIntoDynamicData2
VALUES ('one2'), ('two2'), ('three2')
GO
-- =================================
-- procedure with dynamic query
-- =================================
CREATE PROCEDURE dbo.TestInsertIntoDynamicProc
#TableName nvarchar(100)
AS
BEGIN
DECLARE #Results table(
data nvarchar(max)
)
DECLARE #sql nvarchar(max)
SET #sql = '
SELECT data
FROM dbo.' + #TableName + ';
'
-- FIRST INSERT INTO ... EXEC ...
INSERT INTO #Results -- this INSERT is required for example
EXEC (#sql)
SELECT *
FROM #Results;
END
GO
-- =================================
-- CALL
-- =================================
DECLARE #ResultsOfProc table(
data nvarchar(max)
)
-- SECOND INSERT INTO ... EXEC ...
INSERT INTO #ResultsOfProc (data)
EXEC dbo.TestInsertIntoDynamicProc #TableName = 'TestInsertIntoDynamicData2'
SELECT *
FROM #ResultsOfProc;
GO
DROP TABLE dbo.TestInsertIntoDynamicData1
DROP TABLE dbo.TestInsertIntoDynamicData2
DROP PROCEDURE dbo.TestInsertIntoDynamicProc
https://stackoverflow.com/a/2917775/7573844
QUESTION
How can we get around this error?
Move INSERT INTO to dynamic query.
Refactor table variable to temp table in order to use it in dynamic query.
Fixed procedure:
CREATE PROCEDURE dbo.TestInsertIntoDynamicProcFixed
#TableName nvarchar(100)
AS
BEGIN
CREATE TABLE #Results ( -- refactor to temp table
data nvarchar(max)
)
DECLARE #sql nvarchar(max)
SET #sql = '
INSERT INTO #Results -- move INSERT to here
SELECT data
FROM dbo.' + #TableName + ';
'
-- FIRST INSERT INTO ... EXEC ...
EXEC (#sql)
SELECT *
FROM #Results;
END
GO

Dynamically add columns to temp table based on rows in another temp table

I am trying to build a temp table with a dynamic number of columns based on the number of rows from another temp table. Say I have 89 rows in #table1, in #table2 I would like to use the row count and take that corresponding row value as the column name. I've been fiddling with this for a while but I keep getting errors. Here's my query which will be turned into a proc later.
My table looks like this (all columns are varchar with null allowed in case the imported date has no data for that CVE Number - CVEId relates to FK constraint CVEID on CVENumber table):
CVEId D20160901 D20160902 D20160903 D20160904 D20160905
1 6182 6473 5879 NULL NULL
2 72862 76583 NULL NULL 74772
CVENumber Table:
CVEID CVENumber
1 CVE-781-2016
2 CVE-006-2016
What I'm hoping for is to get the date of the column or perhaps use an injected date as the first row - run a query against this data where I can specify 09-01-2016 TO 09-03-2016. And return all rows from the table with the CVENumber referred to in the CVENumber table. What I want my result to look like:
CVE Number 09-01-2016 09-02-2016 09-03-2016
CVE-781-2016 6182 6473 8579
CVE-006-2016 72682 76583 0
I hope this clarifies what I am trying to do.
My current query using STUFF() which takes the rows from #FixedDates and turns those into columns. I want to take those columns returned to #cols to be added as columns to #query_results
Set nocount on
Insert #tmp
EXEC sp_columns #table_name = N'CVECountsByDate'
-- Using collate to force the DB to only look at Uppercase values
DECLARE #cols varchar(max), #query varchar(max), #cols2 varchar(MAX)
INSERT #FixedDays
SELECT Replace(COLUMN_NAME, 'D' collate Latin1_General_CS_AS, '' collate Latin1_General_CS_AS) from #Tmp
WHERE COLUMN_NAME LIKE 'D%' collate Latin1_General_CS_AS OR COLUMN_NAME = 'CVEId' ORDER BY COLUMN_NAME DESC
SET #cols = STUFF((SELECT ',' + QUOTENAME(QT.COLUMN_NAME) + ' varchar(100)'
FROM #FixedDays QT
GROUP BY QT.COLUMN_NAME
ORDER BY QT.COLUMN_NAME
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
,1,1,'')
SET #cols2 = N'CREATE TABLE #query_results (' + #cols + ') '
--EXEC(#cols2)
SELECT #cols2
DROP TABLE #FixedDays
DROP TABLE #Tmp
This is what I ended up doing...
CREATE TABLE #Tmp
(TABLE_QUALIFIER varchar(40),
TABLE_OWNER varchar(20),
TABLE_NAME varchar(40),
COLUMN_NAME varchar(40),
DATA_TYPE int,
TYPE_NAME varchar(20),
PREC int, LENGTH int,
SCALE int, RADIX int,
NULLABLE char(4),
REMARKS varchar(128),
COLUMN_DEF varchar(40),
SQL_DATA_TYPE int,
SQL_DATETIME_SUB int,
CHAR_OCTET_LENGTH int,
ORDINAL_POSITION int,
IS_NULLABLE char(4),
SS_DATA_TYPE int)
CREATE TABLE #FixedDays
(COLUMN_NAME varchar(40))
CREATE TABLE #query_results
(CVENumber varchar(100) null)
Set nocount on
Insert #tmp
EXEC sp_columns #table_name = N'CVECountsByDate'
INSERT #FixedDays
SELECT Replace(COLUMN_NAME, 'D' collate Latin1_General_CS_AS, '' collate Latin1_General_CS_AS) from #Tmp
WHERE COLUMN_NAME LIKE 'D%' collate Latin1_General_CS_AS OR COLUMN_NAME = 'CVEId'
DECLARE #listStr VARCHAR(MAX)
DECLARE #FixedList VARCHAR(MAX)
SELECT #FixedList = COALESCE(#FixedList,'[') + COLUMN_NAME + '] VARCHAR(MAX) NULL, [' FROM #FixedDays
SELECT #FixedList = substring(#FixedList, 1, len(#FixedList) -1)
SET #FixedList = LEFT(#FixedList, len(#FixedList)-1) -- Altered Column List
EXEC(N'ALTER TABLE #query_results ADD ' + #FixedList + '')
INSERT INTO #query_results
SELECT CVEDetails.CVENumber, CVECountsByDate.* FROM CVECountsByDate INNER JOIN CVEDetails ON CVECountsByDate.CVEId = CVEDetails.CVEID
ALTER TABLE #query_results DROP column CVEId
DROP TABLE #query_results
DROP TABLE #Tmp
DROP TABLE #FixedDays

Drop multiple columns using subquery within alter table

I need to drop all but few columns from a table while I don't know number and names of these columns in advance. I want to get column names from sys.columns and then drop all columns except few. I need something like this:
alter table Table
drop column (
select a.name as ColumnName
from sys.all_columns a
INNER JOIN sys.tables b
on a.object_id = b.object_id
where b.name = 'Table'
where a.name NOT IN (
RowID,
RemoteUserId,
Email,
FirstName,
)
)
First You need to Create a Function to Split the Comma Separated Strings to Rows
CREATE FUNCTION [dbo].[FnSplit]
(
#List nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Value nvarchar(100)
)
AS
BEGIN
While (Charindex(#SplitOn,#List)>0)
Begin
Insert Into #RtnValue (value)
Select
Value = ltrim(rtrim(Substring(#List,1,Charindex(#SplitOn,#List)-1)))
Set #List = Substring(#List,Charindex(#SplitOn,#List)+len(#SplitOn),len(#List))
End
Insert Into #RtnValue (Value)
Select Value = ltrim(rtrim(#List))
Return
END
GO
Then Create Below Stored Procedure
Create Procedure DropColumnExceptGivn
#table varchar(50),
#Column Varchar(max)
AS
BEGIN
SET NOCOUNT ON;
declare #variables AS TABLE (cols varchar(max))
declare #sql varchar(max) = ''
declare #sql1 varchar(max) = ''
declare #cols nvarchar(max) =''
set #sql1 = 'SELECT Stuff((select '',''+ Column_name '+'from INFORMATION_SCHEMA.COLUMNS where
table_name = ' +''''+#table+''''+
' and column_name not in (SELECT Value FROM dbo.FnSplit('+''''+#column+''''+','',''))
for xml path('''')),1,1,'''')'
print #sql1
insert into #variables
exec (#sql1)
select #cols = cols from #variables
select #cols
set #sql= 'alter table '+ #table + ' drop column ' + #cols
print #sql
exec (#sql)
end
go
--Creating a Sample Table
Create table pets1 (petid int, PetTypeID int, PetName Varchar(20),OwnerId int)
go
Then Execute the below Stored Procedure.
The Parameters are : 1. Table Name 2. The Columns need to be Left (Comma Seperated values)
exec DropColumnExceptGivn 'pets1','PetID, PetTypeID'
go
select *from pets1
Hope This will helps

Dynamic SQL results into temp table in SQL Stored procedure

The code is as follows:
ALTER PROCEDURE dbo.pdpd_DynamicCall
#SQLString varchar(4096) = null
AS
Begin
create TABLE #T1 ( column_1 varchar(10) , column_2 varchar(100) )
insert into #T1
execute ('execute ' + #SQLString )
select * from #T1
End
The problem is that I want to call different procedures that can give back different columns.
Therefore I would have to define the table #T1 generically.
But I don't know how.
Can anyone help me on this problem?
Try:
SELECT into #T1 execute ('execute ' + #SQLString )
And this smells real bad like an sql injection vulnerability.
correction (per #CarpeDiem's comment):
INSERT into #T1 execute ('execute ' + #SQLString )
also, omit the 'execute' if the sql string is something other than a procedure
You can define a table dynamically just as you are inserting into it dynamically, but the problem is with the scope of temp tables. For example, this code:
DECLARE #sql varchar(max)
SET #sql = 'CREATE TABLE #T1 (Col1 varchar(20))'
EXEC(#sql)
INSERT INTO #T1 (Col1) VALUES ('This will not work.')
SELECT * FROM #T1
will return with the error "Invalid object name '#T1'." This is because the temp table #T1 is created at a "lower level" than the block of executing code. In order to fix, use a global temp table:
DECLARE #sql varchar(max)
SET #sql = 'CREATE TABLE ##T1 (Col1 varchar(20))'
EXEC(#sql)
INSERT INTO ##T1 (Col1) VALUES ('This will work.')
SELECT * FROM ##T1
Hope this helps,
Jesse
Be careful of a global temp table solution as this may fail if two users use the same routine at the same time as a global temp table can be seen by all users...
create a global temp table with a GUID in the name dynamically. Then you can work with it in your code, via dyn sql, without worry that another process calling same sproc will use it. This is useful when you dont know what to expect from the underlying selected table each time it runs so you cannot created a temp table explicitly beforehand. ie - you need to use SELECT * INTO syntax
DECLARE #TmpGlobalTable varchar(255) = 'SomeText_' + convert(varchar(36),NEWID())
-- select #TmpGlobalTable
-- build query
SET #Sql =
'SELECT * INTO [##' + #TmpGlobalTable + '] FROM SomeTable'
EXEC (#Sql)
EXEC ('SELECT * FROM [##' + #TmpGlobalTable + '] ')
EXEC ('DROP TABLE [##' + #TmpGlobalTable + ']')
PRINT 'Dropped Table ' + #TmpGlobalTable
INSERT INTO #TempTable
EXEC(#SelectStatement)
Try Below code for creating temp table dynamically from Stored Procedure Output using T-SQL
declare #ExecutionName varchar(1000) = 'exec [spname] param1,param2 '
declare #sqlStr varchar(max) = ''
declare #tempTableDef nvarchar(max) =
(
SELECT distinct
STUFF(
(
SELECT ','+a.[name]+' '+[system_type_name]
+'
' AS [text()]
FROM sys.dm_exec_describe_first_result_set (#ExecutionName, null, 0) a
ORDER BY a.column_ordinal
FOR XML PATH ('')
), 1, 1, '') tempTableDef
FROM sys.dm_exec_describe_first_result_set (#ExecutionName, null, 0) b
)
IF ISNULL(#tempTableDef ,'') = '' RAISERROR( 'Invalid SP Configuration. At least one column is required in Select list of SP output.',16,1) ;
set #tempTableDef='CREATE TABLE #ResultDef
(
' + REPLACE(#tempTableDef,'
','') +'
)
INSERT INTO #ResultDef
' + #ExecutionName
Select #sqlStr = #tempTableDef +' Select * from #ResultDef '
exec(#sqlStr)
DECLARE #EmpGroup INT =3 ,
#IsActive BIT=1
DECLARE #tblEmpMaster AS TABLE
(EmpCode VARCHAR(20),EmpName VARCHAR(50),EmpAddress VARCHAR(500))
INSERT INTO #tblEmpMaster EXECUTE SPGetEmpList #EmpGroup,#IsActive
SELECT * FROM #tblEmpMaster
CREATE PROCEDURE dbo.pdpd_DynamicCall
AS
DECLARE #SQLString_2 NVARCHAR(4000)
SET NOCOUNT ON
Begin
--- Create global temp table
CREATE TABLE ##T1 ( column_1 varchar(10) , column_2 varchar(100) )
SELECT #SQLString_2 = 'INSERT INTO ##T1( column_1, column_2) SELECT column_1 = "123", column_2 = "MUHAMMAD IMRON"'
SELECT #SQLString_2 = REPLACE(#SQLString_2, '"', '''')
EXEC SP_EXECUTESQL #SQLString_2
--- Test Display records
SELECT * FROM ##T1
--- Drop global temp table
IF OBJECT_ID('tempdb..##T1','u') IS NOT NULL
DROP TABLE ##T1
End
Not sure if I understand well, but maybe you could form the CREATE statement inside a string, then execute that String? That way you could add as many columns as you want.

Resources