Keeping the design of 2 tables in sync - sql-server

The problem:
I have 2 tables in a database:
TableA TableB
X, Y, Z X, Y, Z
If I add column W to table A I want to copy it automatically to Table B with the same name and data type (without writing it explicitly).
Constraints on deployment:
The tables are updated using update scripts (so must be able to be called / executed in tsql).
There are multiple tables that could be updated (however I can hand code a mapping if needed)
Example update script:
IF NOT EXISTS (SELECT 1 FROM SYS.COLUMNS C INNER JOIN SYS.TABLES T ON C.OBJECT_ID = T.OBJECT_ID
WHERE C.NAME = 'W' AND T.NAME = 'TableA')
BEGIN
ALTER TABLE TableA ADD W [Int] NULL
END
GO
At the end of this script I want to add my ‘SyncMyTables’ SQL
So far from my research I have found 3 possible ways to tackle this:
Call a function at the end of the script which syncs the table designs
Some form of table trigger (but I don’t think triggers are that clever)
Some inline sql that builds up an update string and then runs it against the database.
Option 1 seems the most sensible to me.
What help I need:
Some guidance on how best to tackle this.
An example to point me in the right direction.
Cheers
please note, I don't want to keep the content of the columns in sync, I need to keep the table DESIGN

The column duplication can be achieved with a DDL trigger:
CREATE TRIGGER DDL_TableA_TableB
ON database
FOR ALTER_TABLE
AS
BEGIN
Declare #CommandText nvarchar(1000)
SELECT #CommandText = EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','SYSNAME');
if left(#CommandText, 26) = 'ALTER TABLE dbo.TableA ADD'
begin
set #CommandText = replace( #CommandText, 'TableA', 'TableB')
exec sp_executesql #CommandText
end
end

Related

Is there a way to check if an object is a table or a view in SQL Server?

I have a view that I am changing to a table in SQL Server.
I am dropping the view and then the next part of my code I am establishing the table.
My code works the first time I run it (when the object is a view), but when I need to run it multiple times, I get this error:
Cannot use DROP VIEW with 'engineer.Well' because 'engineer.Well' is a table. Use DROP TABLE.
I've been looking online but cannot find a way to check if an object is a table or a view, and the subsequently drop the object.
Any advice would be greatly appreciated.
Right now it looks like this,
IF OBJECT_ID('engineer.well') IS NOT NULL
BEGIN
DROP TABLE [engineer].[Well]
PRINT '<<< DROPPED TABLE Vendor >>>'
END
I am playing around with a way to check if the object is a table and then drop it, or check if it is a view then drop it.
You can query the system views.
DECLARE #Type varchar(2)
SELECT #Type = type
FROM sys.objects o
JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE o.name = 'well'
AND s.name = 'engineer'
IF #Type = 'U'
BEGIN
DROP TABLE [engineer].[Well]
PRINT '<<< DROPPED TABLE Vendor >>>'
END
IF #Type = 'V'
BEGIN
DROP VIEW [engineer].[Well]
PRINT '<<< DROPPED VIEW Vendor >>>'
END
OBJECTPROPERTY(OBJECT_ID('name'), 'IsView')
https://learn.microsoft.com/en-us/sql/t-sql/functions/objectproperty-transact-sql?view=sql-server-2017
or
SELECT id, type FROM sysobjects where id=OBJECT_ID('objectName')
You can query sys.objects table:
select type_desc, * from sys.objects where object_id = object_id('[dbo].[DimDates]')

How can I detect renaming operations on tables and columns?

I need to detect renaming operation on columns and tables.
I can see alter, drop and create operation in this query:
DECLARE #filename nvarchar(1000);
SELECT #filename = cast(value as nvarchar(1000))
FROM ::fn_trace_getinfo(default)
WHERE traceid = 1 and property = 2;
SELECT *
FROM fn_trace_gettable(#filename, default) AS ftg
INNER JOIN sys.trace_events AS te ON ftg.EventClass = te.trace_event_id
left outer join sys.all_objects o on o.name = ftg.ObjectName
order by EventSequence DESC;
But when someone renames a table or column on MSSQL Management Studio, I cannot detect on this query. Is there another way to do it?
Regards.
You can create a database trigger.
List of available events: [sys].[events] (there are ALTER_COLUMN event)
Example from msdn:
CREATE TRIGGER safety
ON DATABASE
FOR DROP_SYNONYM
AS
RAISERROR ('You must disable Trigger "safety" to drop synonyms!',10, 1)
ROLLBACK
GO
DROP TRIGGER safety
ON DATABASE;
GO
another example: https://www.mssqltips.com/sqlservertip/2085/sql-server-ddl-triggers-to-track-all-database-changes/

SQL Server: How to recreate a computed column *only once*

I need to change the computation logic for an existing computed column in SQL Server.
And I want to keep the column name unchanged.
I wrote a script to do this job. And the script would
Drop the computed column
Recreate the computed column with new computation logic with same name.
The issue is this script may run multiple times in the future, however I don't want this script to drop and create the column every time.
Is there some way to avoid this column to be deleted and created more than once?
One way I could think of is to create a dummy check constraint.
So before drop and recreate the computed column, first check if that dummy check constraint exists or not.
If it does not exists, then create the dummy constraint and drop/create the computed column.
Not sure if there is any other easy way to solve this.
you may rely upon the computed_columns system table:
SELECT t.name
,SCHEMA_NAME(t.schema_id) AS [schema_name]
,c.name
,c.definition
FROM sys.computed_columns c
JOIN sys.tables t ON t.object_id = c.object_id;
I think it will require some trial and error to get the correct code but you should be able to build a check script to verify the current formula and trigger the DROP/CREATE only when needed.
possible issue: if an outdated version of the script is run it may revert the formula to a previous version so a timestamp check must be implemented.
If you don't mind using hardcoded value (not good), this script might help.
You will have to use a hard coded column_id or better figure out a way to retrieve it (count columns for this table maybe?).
CREATE TABLE dbo.Table_1(ID int identity(0, 1), V1 int NULL, V2 int NULL, V3 AS V1*V2) ON [PRIMARY]
select top 10 * from sys.computed_columns where OBJECT_ID('table_1') = object_id
If Exists(Select 1 From sys.computed_columns where OBJECT_ID('table_1') = object_id and name = 'v3' and column_id<=
4)
Begin
Print 'Update Computed Column'
Alter TAble Table_1 Drop column V3;
Alter Table Table_1 Add V3 as V1*V2
End
Else
Begin
Print 'No Update'
End
select top 10 * from sys.computed_columns where OBJECT_ID('table_1') = object_id

Finding Updated Columns from within the Stored Procedures

I am able to find out the columns updated within the trigger of the table. However the trigger is kind of big, I want to reduce its size as much as possible. So now, I want to create a generic stored procedure and find out the updated columns from within the stored procedure.
Here is the SQL query that finds out the updated columns
SELECT #idTable = T.id
FROM sysobjects P JOIN sysobjects T ON P.parent_obj = T.id
WHERE P.id = ##PROCID
---- Get COLUMNS_UPDATED if update
DECLARE #Columns_Updated VARCHAR(50)
SELECT #Columns_Updated = ISNULL(#Columns_Updated + ', ', '') + name
FROM syscolumns
WHERE id = #idTable
AND CONVERT(VARBINARY,REVERSE(COLUMNS_UPDATED())) & POWER(CONVERT(BIGINT, 2), colorder - 1) > 0
Could some one help me out as to what am I suppose to do to achieve my goal
If you want to create an sp that will execute whenever you want and see what was updated database-wide since the last run of this sp, then I don't think it can be done. I would advise to either use the built-in sql server 2008 audit functionality or use triggers as Yuriy Galanter already pointed out.

How do I create and use a stored procedure for SQL-Sever which will delete the data in all tables in a database with VS 2010?

I've been using the entities framework with ASP.NET MVC and I'm looking for an easy and fast way to drop all of the information in the database. It takes quite a while to delete all of the information from the entities object and then save the changes to the database (probably because there are a lot of many-to-many relationships) and I think it should be really fast to just remove all of the information with a stored procedure but I'm not sure how to go about this. How do I create and use a stored procedure for SQL-Sever which will delete the data in all tables in a database with VS 2010? Also if I do this will the command be compatible with other version of SQL-Server? (I'm using 2008 on my testing comptuer, but when I upload it I not sure if my hosting company uses 2008 or 2005).
Thanks!!
This solution will work well in terms of deleting all your data in your database's tables.
You can create this stored proc right within Visual Studio on your SQL Server 2008 development server. It'll work well in any version of SQL Server (2000+).
CREATE PROC NukeMyDatabase
AS
--order is important here. delete data in FK'd tables first.
DELETE Foo
DELETE Bar
TRUNCATE TABLE Baz
I prefer TRUNCATE TABLE, as it's faster. It'll depend on your data model, as you can't issue a TRUNCATE TABLE on a table referenced by a foreign key constraint (i.e. parent tables).
You could then call this stored proc using Entity Framework after adding it to your .edmx:
myContext.NukeMyDatabase();
I recently faced a similar problem in that I had to clear over 200+ tables that were interlinked through many foreign key constraints.
The critical issue, as p.campbell pointed out, is determining the correct order of DELETE statements.
The foreign key constraints between tables essentially represent a hierarchy. If table 3 is dependent on table 2, and table 2 is dependent on table 1, then table 1 is the root and table 3 is the leaf.
In other words, if your going to delete from these three tables, you have to start with the table that has no dependencies and work your way up. That is the intent of this code:
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
;WITH c AS
(
SELECT
parent_object_id AS org_child,
parent_object_id,
referenced_object_id,
1 AS Depth
FROM sys.foreign_keys
UNION ALL
SELECT
c.org_child,
k.parent_object_id,
k.referenced_object_id,
Depth + 1
FROM c
INNER JOIN sys.foreign_keys k
ON c.referenced_object_id = k.parent_object_id
WHERE c.parent_object_id != k.referenced_object_id
),
c2 AS (
SELECT
OBJECT_NAME(org_child) AS ObjectName,
MAX(Depth) AS Depth
FROM c
GROUP BY org_child
UNION ALL
SELECT
OBJECT_NAME(object_id),
0 AS Depth
FROM sys.objects o
LEFT OUTER JOIN c
ON o.object_id = c.org_child
WHERE c.org_child IS NULL
AND o.type = 'U'
)
SELECT #sql = #sql + 'DELETE FROM ' + CAST(ObjectName AS VARCHAR(100))
+ ';' + CHAR(13) + CHAR(10) /** for readability in PRINT statement */
FROM c2
ORDER BY Depth DESC
PRINT #sql
/** EXEC (#sql) **/
exec sp_MSForEachTable 'truncate table ?';
But I would recommend a different approach: take a backup of the empty database and simply restore this backup before each run. Even better, have no database at all and have your application be capable of deploying the database itself, using a schema version upgrade set of scripts.

Resources