Add columns from one to another table dynamically in SQL Server - sql

I have 2 tables, A and B. Table A has columns Q1, Q2, Q3, Q4 and Table B has columns Q1 and Q2 along with others. In Table A, columns will get changed in future with Q5, Q6, Q7 etc. I want to write dynamic SQL to alter the table B (adding new columns) whenever new columns are added to Table A. Columns which will get added always be like Q1, Q2,Q3...Q40.
example of my tables:
create table tableA
(
[sid] [varchar](50) not null,
[rid] [varchar](50) not null,
[Q_URL] [varchar](500) null,
[Q1] int null,
[Q2] int null,
[Q3] int null,
[Q4] int null
);
create table tableB
(
[s_id] [varchar](50) not null,
[rid] [varchar](50) not null,
[Q_URL] [varchar](500) null,
[Q1] int null,
[Q2] int null
);

if object_id('tempdb..#source_col') is not null
drop table #source_col
Select distinct column_name as source_r_column from hr.INFORMATION_SCHEMA.COLUMNS
where table_name = 'tableA'
and column_name like 'Q%'
and column_name <> 'Q_URL';
if object_id('tempdb..#destination_col') is not null
drop table #source_col
Select distinct column_name as source_r_column from hr.INFORMATION_SCHEMA.COLUMNS
where table_name = 'tableB'
and column_name like 'Q%'
and column_name <> 'Q_URL';
if object_id('tempdb..#newcols') is not null
drop table #newcols
Select row_number() over(order by t1.source_q_columns) as colnum,t1.source_q_columns, 'int' as datatype
into #newcols
from #tableA t1
left join #tableB t2 on t1.source_q_columns= t2.destination_q_columns
where t2.destination_q_columns is null;
Declare #sql nvarchar(max)
Declare #column varchar(10)
Declare #datatype varchar(10)
Declare #max_colnum int
set #max_colnum = (select max(colnum) from #newcols)
While(#max_colnum != 0)
begin
set #column = (select source_q_columns from #newcols where colnum = #max_colnum)
print #column
select #sql = 'Alter Table destination Add '+ #column + ' int null'
print #sql
EXEC (#sql)
set #max_colnum = #max_colnum -1
end
This is what i came up. I believe this will work for any case. :)

You can create something called a DDL Trigger for Alter Table event. The Trigger is created on the database level so it will fire for any Alter table commands executed on the database.
A sample of DDL trigger for Alter table event on the database level for TableA would look something like this.
CREATE TRIGGER tr_Replicate_DDL_For_TableA
ON DATABASE
FOR ALTER_TABLE
AS
BEGIN
SET NOCOUNT ON;
Declare #data xml = EVENTDATA()
,#TableName SYSNAME
,#Sql NVARCHAR(MAX);
SET #TableName = #data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'SYSNAME');
SET #Sql = #data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(max)');
IF (#TableName = 'TableA') --<-- only replicate changes if TableA's schema changed.
BEGIN
SET #sql = REPLACE(#sql , 'TableA' , 'TableB');
Exec sp_executesql #sql;
END
END
GO

Related

SQL Server : drop constraint if exists

I have been struggling with this for about 2 hours now. I am trying to create a column to tables using a stored procedure. That said, I must check if that column exists, and if it does I must drop it.
The Issue is that I am not able to drop the constraint, I am getting an error:
invalid column name 'RowNumber'
Code:
DECLARE #tableName NVARCHAR(30)
DECLARE #newColumnName NVARCHAR(100)
DECLARE #newDefaultValue NVARCHAR(100)
DECLARE #newColumnType NVARCHAR(100)
DECLARE #constraintName NVARCHAR (30)
SET #tableName='TestTable'
SET #newDefaultValue = 'James'
SET #newColumnType ='NVARCHAR(100)'
SET #newColumnName = 'James'
CREATE TABLE #cons (constraintName NVARCHAR(100), RowNumber int)
INSERT INTO #cons(constraintName, RowNumber)
SELECT
t.*
FROM
(SELECT
name,
ROW_NUMBER() OVER(ORDER BY name) AS RowNumber
FROM
sys.objects
WHERE
type_desc LIKE '%CONSTRAINT'
AND name like '%JAMES%'
AND name like '%JAMES%') AS t
SELECT * FROM #cons
IF EXISTS(SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '[dbo].[TestTable]'
AND COLUMN_NAME = 'James')
BEGIN
DECLARE #i int
DECLARE #numberOfRows int
SET #i = 1
SET #numberOfRows = (SELECT COUNT(*) FROM #cons)
WHILE(#i <= #numberOfRows)
BEGIN
SET #constraintName = (SELECT constraintName
FROM #cons
WHERE RowNumber = #i)
DECLARE #dropStatement NVARCHAR(500)
SET #dropStatement = 'ALTER TABLE [dbo].[TestTable] DROP CONSTRAINT ' + #constraintName
EXEC(#dropStatement)
SET #i = #i + 1
END
ALTER TABLE [dbo].[TestTable]
DROP COLUMN [James]
END
--ALTER TABLE [dbo].[TestTable] ADD James NVARCHAR(50)
DROP TABLE #cons
How can I get this to drop the column?
ALTER TABLE
DROP CONSTRAINT #constraintname
Before the drop colum

SQL MERGE Table with tableName

I would like have a generic procedure to Update my look Up Table.
CREATE TYPE S_Reference.[ReferenceType] as TABLE (
[Id] SMALLINT NOT NULL PRIMARY KEY,
[Code] VARCHAR(16) UNIQUE NOT NULL,
[Rank] SMALLINT NOT NULL CHECK([Rank]>0),
[Description] VARCHAR(128) NOT NULL,
[Base] BIT NOT NULL DEFAULT 0);
GO
CREATE PROCEDURE [S_Reference].[P_B_ReferenceMerge]
#Values [ReferenceType] READONLY,
#TableName NVARCHAR(50)
AS
DECLARE #SQLQuery NVARCHAR(200)
SET #SQLQuery = 'SELECT * FROM ' + #TableName
MERGE INTO #SQLQuery Ori
USING
#Values New
ON (Ori.[Id] = New.[Id])
WHEN MATCHED THEN
UPDATE
SET Ori.[Code] = New.[Code],
Ori.[Rank] = New.[Rank],
Ori.[Description] = New.[Description],
Ori.[Base] = New.[Base]
WHEN NOT MATCHED THEN
INSERT (Ori.[Id] , Ori.[Code], Ori.[Rank],Ori.[Description] ,Ori.[Base])
Values (New.[Id] , New.[Code], New.[Rank],New.[Description] ,New.[Base]);
RETURN 0
But I don't know how to use the "tableName" ?
I get an error on Ori.[Id], I think the problem comes from
SET #SQLQuery = 'SELECT * FROM ' + #TableName
MERGE INTO #SQLQuery Ori
If can help this is how I did. By this way You can manage easily the values in your look up (reference or enum in c#).
First the procedure:
CREATE TYPE S_Reference.[ReferenceType] AS TABLE (
[Id] SMALLINT NOT NULL PRIMARY KEY,
[Code] VARCHAR(40) UNIQUE NOT NULL,
[Rank] SMALLINT UNIQUE NOT NULL CHECK([Rank]>0),
[Description] VARCHAR(128) NOT NULL,
[Base] BIT NOT NULL DEFAULT 0);
GO
CREATE PROCEDURE [S_Reference].[P_M_ReferenceMerge]
#Values S_Reference.[ReferenceType] READONLY,
#TableName NVARCHAR(50)
AS
CREATE TABLE #NewValues (
[Id] SMALLINT NOT NULL PRIMARY KEY,
[Code] VARCHAR(40) UNIQUE NOT NULL,
[Rank] SMALLINT UNIQUE NOT NULL CHECK([Rank]>0),
[Description] VARCHAR(128) NOT NULL,
[Base] BIT NOT NULL DEFAULT 0);
Insert INTO #NewValues
select * From #Values
DECLARE #SQLQuery NVARCHAR(MAX)
SET #SQLQuery =
'DELETE
FROM ' + #TableName + '
WHERE [Id] IN (SELECT [Id]
FROM ' + #TableName + '
EXCEPT
SELECT [Id]
FROM #NewValues)
MERGE INTO ' + #TableName + ' Ori
USING #NewValues New
ON (Ori.[Id] = New.[Id])
WHEN MATCHED THEN
UPDATE
SET Ori.[Code] = New.[Code],
Ori.[Rank] = New.[Rank],
Ori.[Description] = New.[Description],
Ori.[Base] = New.[Base]
WHEN NOT MATCHED THEN
INSERT ([Id] , [Code], [Rank], [Description] ,[Base])
Values (New.[Id] , New.[Code], New.[Rank], New.[Description] ,New.[Base]);'
EXEC sp_executesql #SQLQuery
RETURN 0
Usage
INSERT INTO
#VALUES([Id],[Code],[Rank],[Description] ,[Base])
Values
(1,'EUR',1,'EUR',0),
(2,'GBP',2,'GBP',0),
(3,'USD',3,'USD',0)
EXEC [S_Reference].[P_M_ReferenceMerge]
#Values = #VALUES,
#TableName = 'S_Reference.T_R_Currency'
DELETE FROM #VALUES

SQL INDENTITY DOESN'T RECEIVE PARAMETERS

I'm trying to set IDENTITY seed parameter while creating table, getting it from a var. Something like this
DECLARE #MaxID INTEGER
SET #MaxID = (SELECT TOP 1 ID FROM dbo.ProductQuotes ORDER BY ID DESC) + 1;
CREATE TABLE [dbo].[Z](
[ID] int PRIMARY KEY not null IDENTITY(#MaxID,1),
[Number] int NULL,
[Name] nvarchar(50) COLLATE Cyrillic_General_CI_AS NULL
) ON [PRIMARY]
GO
Error is "incorrect syntax near '#MaxID' (in this row [ID] int PRIMARY KEY not null IDENTITY(#MaxID,1) )
But I'm not sure it's about syntax at all. Can somebody explain me where am I wrong here? :)
This cannot be parameterised.
You would need to use dynamic SQL as below.
DECLARE #MaxID INTEGER
SELECT #MaxID = 1 + ISNULL(MAX(ID),0) FROM dbo.ProductQuotes
DECLARE #Script NVARCHAR(MAX) =
N'
CREATE TABLE [dbo].[Z](
[ID] int PRIMARY KEY not null IDENTITY(' + CAST(#MaxID AS NVARCHAR(10)) + ',1),
[Number] int NULL,
[Name] nvarchar(50) COLLATE Cyrillic_General_CI_AS NULL
) ON [PRIMARY]
'
EXEC (#Script);
I assume you will take precautions to ensure that ProductQuotes cannot be subject to any inserts during the import process.
Something like
DECLARE #MaxID INTEGER
DECLARE #SQL varChar(4000)
SET #MaxID = (SELECT TOP 1 ID FROM dbo.ProductQuotes ORDER BY ID DESC) + 1;
Set #SQL = 'CREATE TABLE [dbo].[Z]([ID] int PRIMARY KEY not null IDENTITY(' +
Convert(VarChar(8),#MaxID) +
',1), [Number] int NULL, [Name] nvarchar(50) COLLATE Cyrillic_General_CI_AS NULL) ON [PRIMARY]
Exec(#sql)
Build the sql statement using the teh value of #MaxID then execute it.

Selecting not null column

I have a table with varbinary(max) column and nvarchar(max) column. One of them is null and the other has a value.
I would like to return the column that has the value as a varbinary(max) column. So far I have tried this, that does not work:
SELECT
A =
CASE A
WHEN NULL THEN B
ELSE A
END
FROM Table
SELECT COALESCE(A, CAST(B As varbinary(max)))
UPDATE: In response to comments (thanks) and assuming B is the nvarchar(max) column, I have moved the CAST inside the COALESCE
Try SELECT ISNULL(A, cast(B AS varbinary(max))) FROM TABLE
Your case statement evaluates to the dreaded A = NULL:
CASE A WHEN NULL THEN B ELSE A END
Is the same as:
CASE WHEN A = NULL then B ELSE A END
One way to fix this is to use A IS NULL, like:
CASE WHEN A IS NULL THEN B ELSE A END
Or even simpler:
COALESCE(A,B)
Both the when and the coalesce will assume the data type of the first argument. To cast the result to varbinary, you can place the varbinary column first, or explicitly cast:
COALESCE(CAST(A AS VARBINARY(MAX)),B)
here is the full code of create table and insert value and apply my code and only retrieve not null value
CREATE TABLE [dbo].[SUPPLIER](
[ID] [int] IDENTITY(1,1) NOT NULL,
[SUPPLIER_NAME] [varchar](100) NOT NULL,
[ADDRESS] [varchar](150) NULL,
[CREATE_DATE] [datetime] NULL,)
INSERT INTO [MyPayrol].[dbo].[SUPPLIER]
([SUPPLIER_NAME]
,[CREATE_DATE])
VALUES
('Khaled Nabil'
,GETDATE())
declare #inumberofcolumn int
select #inumberofcolumn= count(*)
from sys.columns where OBJECT_NAME(object_id) = 'supplier'
declare #nameofcolumn varchar(100)
set #nameofcolumn =''
declare #counter int
set #counter=1
declare #colname varchar(100)
declare #statment varchar(100)
declare #value varchar(100)
while #counter <=#inumberofcolumn
begin
select #colname= COL_NAME(object_id('[dbo].[SUPPLIER]'),#counter)
declare #data table ([value] varchar(100))
--set #statment = 'select '+#colname+' from [dbo].[SUPPLIER]'
insert #data exec ('SELECT top 1 '+ #colname +' from [dbo].[SUPPLIER]')
select #value = [value] from #data
if #value is not null
begin
if #counter = 1
begin
set #nameofcolumn = #nameofcolumn + #colname
end
else
begin
set #nameofcolumn = #nameofcolumn + ','+ #colname
end
end
set #counter = #counter+1
end
execute ('select '+#nameofcolumn+' from [dbo].[SUPPLIER]')

How can I retrieve the values from a generic audit table reconstituted as a rowset with meaningful column names in SQL?

So I've got this audit table, looks like this:
USE [DatabaseName]
GO
/****** Object: Table [ERAUser].[Audit] Script Date: 05/20/2009 17:07:11 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [ERAUser].[Audit](
[AuditID] [int] IDENTITY(1,1) NOT NULL,
[Type] [char](1) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[TableName] [varchar](128) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[PrimaryKeyField] [varchar](1000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[PrimaryKeyValue] [varchar](1000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[FieldName] [varchar](128) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[OldValue] [varchar](1000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[NewValue] [varchar](1000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[UpdateDate] [datetime] NULL DEFAULT (getdate()),
[UserName] [varchar](128) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
The problem is, for reasons out of my control, I need to return data to users (other developers that are the users of this system) as rowsets that replicate the source table. How can I turn this schema on its side and get the values in FieldName as the column headings for a rowset? I'm using SQL 2005. I will know the tablename and the UpdateDate.
This can be done using the PIVOT function. I'll try to work out an example:
SELECT *
FROM ERAUser.Audit
PIVOT (max(NewValue) FOR FieldName in (Field1, Field2, Field3)) as PivotTable
The max() is necessary to tell Sql Server what to do if it finds multiple rows with the same FieldName. You can use a WHERE statement to limit it to the right rows; if you ensure it finds only one, max(NewValue) is equal to NewValue.
You can generate the SQL for this, if you have a long list of columns:
declare #columnlist nvarchar(4000)
select #columnlist = IsNull(#columnlist + ', ', '') + FieldName
from (
select distinct FieldName from ERAUser.Audit
) sub
declare #query nvarchar(4000)
select #query = 'select *
from ERAUser.Audit
PIVOT (max(newValue) FOR FieldName in (' + #columnlist + ')) as PivotTable'
exec sp_executesql #query
Here's a basic example of PIVOT, to get the general idea:
create table #normalized (
colname varchar(12),
value varchar(12)
)
insert into #normalized values ('value1','A')
insert into #normalized values ('value2','B')
insert into #normalized values ('value3','C')
select *
from #normalized
PIVOT (max(value) FOR ColName in (value1,value2,value3)) as Y
This will result in:
value1 value2 value3
A B C
It will be extremely difficult, not least because the type of the values for one combination of table name and column name will be different from other types. To create a whole record corresponding to one row of the table, you are going to have to combine multiple audit records. So, your rowset would have to be tailored to one table at a time. It isn't clear whether you record the unchanged columns - you probably don't - so dealing with a rowset containing the entire record will be tough, at best.
If you know that you only need to return the rows from one table (e.g. "Foo") and assuming all fields to create are varchar(50):
1) Create a temp table with the structure you need
DECLARE #Sql VARCHAR(8000)
DECLARE #ColName VARCHAR(128)
DECLARE #Comma VARCHAR(2)
SET #CreateSql = "CREATE TABLE #Temp ("
SET #Comma = ''
DECLARE columnCur CURSOR FOR
SELECT DISTINCT FieldName
FROM Audit
WHERE TableName = 'Foo'
OPEN columnCur
FETCH NEXT FROM columnCur INTO #ColName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #CreateSql = #CreateSql+#Comma+#ColName+' VARCHAR(128) NULL'
SET #Comma = ', '
FETCH NEXT FROM columnCur INTO #ColName
END
CLOSE columnCur
DEALLOCATE columnCur
SET #CreateSql = #CreateSql + ")"
EXECUTE sp_executesql #CreateSql
2) Fill in the table primary keys:
--Assuming a single PK column, otherwise you need another cursor
DECLARE #pk VARCHAR(1000)
SELECT #pk=PrimaryKeyField
FROM Audit
WHERE TableName = 'Foo'
DECLARE #PkSql VARCHAR(2000)
SET #PkSql = 'INSERT INTO #Temp ('+#pk+') VALUES SELECT DISTINCT PrimaryKeyValue FROM Audit WHERE TableName = ''Foo'''
EXECUTE sp_executesql #PkSql
3) Create a cursor that creates dynamic sql that updates the rows in the temp table with the correct values (similar style as above)
4) SELECT from #Temp
5) DROP #Temp
Note: This should work, but it's totally untested as I don't have a SQL Server available right now.