Missing ALTER TABLE dependency using SMO DependencyWalker - sql

I am using powershell SMO DependencyWalker and Scripter to find the all dependencies of a table in a SQL Server database and then generating scripts to CREATE and DROP the table and those dependencies. DependencyWalker is successfully finding most of the dependencies, but does not find certain stored procedures that I believe it should.
For example, given these table definitions:
CREATE TABLE MyTable(
[UniqueID] [uniqueidentifier] NOT NULL
)
CREATE TABLE MyOtherTable(
[UniqueID] [uniqueidentifier] NOT NULL
)
ALTER TABLE [dbo].[MyTable]
WITH CHECK ADD CONSTRAINT [FK_MyTable_MyOtherTable] FOREIGN KEY([UniqueID])
REFERENCES [dbo].[MyOtherTable] ([UniqueID])
And the following stored procedures that reference the table:
CREATE PROCEDURE [dbo].[MyTableResumeConstraintChecking]
AS
ALTER TABLE dbo.MyTable CHECK CONSTRAINT FK_MyTable_MyOtherTable
RETURN
GO
CREATE PROCEDURE [dbo].[MyTableSuspendConstraintChecking]
AS
ALTER TABLE dbo.MyTable NOCHECK CONSTRAINT FK_MyTable_MyOtherTable
RETURN
GO
I would expect that the following powershell script would find the stored procedures as dependencies:
$SmoServer = New-Object ('Microsoft.SqlServer.Management.SMO.Server') $server
$dbObject = $SmoServer.Databases["MyDatabase"]
$tables = $dbObject.Tables | where { !$_.IsSystemObject }
$urns = $tables | foreach { $_.Urn }
$walker = New-Object 'Microsoft.SqlServer.Management.SMO.DependencyWalker' $SmoServer
$tree = $walker.DiscoverDependencies($urns, $false)
$depCollection = $walker.WalkDependencies($tree)
$dependencies = $depCollection | foreach { $_.Urn }
This will find all other stored procedures that use MyTable (ones with typical INSERT, UPDATE, etc. calls). However, it will NOT find these *ConstraintChecking procedures that reference the table only via ALTER TABLE.
It should be noted that SSMS does not recognize these procedures as a dependency of the table either (when using View Dependencies).
I have tried to use other mechanisms for finding dependencies such as sp_depends and sys.dm_sql_referenced_entities, but those behave the same as DependencyWalker in this situation.
Using SMO in powershell and given a table URN, how do I find the URN of dependencies such as this?

Related

SQL query not executing as whole

So I have a query that should add Primary Key to the Id field:
IF NOT EXISTS(SELECT *
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' AND TABLE_NAME = 'CAL')
BEGIN
DROP INDEX IF EXISTS CAL$01 ON dbo.CAL;
ALTER TABLE BTS.dbo.CAL
ALTER COLUMN Intern INT NOT NULL;
ALTER TABLE BTS.dbo.CAL
ADD CONSTRAINT PK_CAL_Intern PRIMARY KEY (Intern);
CREATE INDEX CAL$01
ON CAL (Intern);
END
The problem is that when I chose all this code and execute (F5), I get this error:
Whereas when I'm choosing every statement one by one it works as expected:
I am sure that the IF works as expected
I tried to use GO between statements, it's not allowed.
I should execute this code on a large number of tables
Maybe I don't know something about how SQL Server Management Studio executes statements
Before a query is run it is parsed. This is why what you're doing is failing. SQL server is checking the details of the Intern before the script is run. At the point you start to try to run the script, Intern in the table BTS.dbo.CAL is NULLable, and so the script fails.
You can get around this by running the statement to create the primary key cosntraint in a separate scope:
IF NOT EXISTS(
SELECT *
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' AND TABLE_NAME = 'CAL'
)
BEGIN
DROP INDEX IF EXISTS CAL$01 ON dbo.CAL;
ALTER TABLE
BTS.dbo.CAL
ALTER COLUMN
Intern
INT NOT NULL;
EXEC sys.sp_executesql N'ALTER TABLE BTS.dbo.CAL ADD CONSTRAINT PK_CAL_Intern PRIMARY KEY (Intern);';
CREATE INDEX [CAL$01]
ON CAL (Intern);
END'
Even though question is answer, I just wanted to add one more option.
You can separate statments into two separate batches, so that your change is available to the subsequent batch
CREATE TABLE #test(a int null);
-- DDL Changes
if exists(SELECT 1)
BEGIN
ALTER TABLE #test ALTER COLUMN a int not null;
END
GO
-- Index changes
if exists(SELECT 1)
BEGIN
ALTER TABLE #test ADD CONSTRAINT PK_test PRIMARY KEY(a)
END
GO

Define relationship to tables as a whole

I need to define a table which basically will contain an Id for a user, and a second column which will list names of tables to which the user has access. I can't think of anyway to define any relationships here in case the original table names change. All the logic will be at the application level. However, I would like to be able to define some sort of constraints. How can I do this? Also, I am open to advice regarding any other way to do this.
I am really confused. Doesn't the grant command do exactly what you want? This assumes that the operations you want are database operations.
If you have a more customized set of operations, then you can keep track of table name changes via DDL triggers.
Here's a detailed code example of how to achieve this using RLS
USE tempdb;
GO
CREATE TABLE dbo.OKTable
(
OKTableID int IDENTITY(1,1) NOT NULL
CONSTRAINT PK_dbo_OKTable PRIMARY KEY,
SecuredInfo varchar(100)
);
GO
INSERT dbo.OKTable (SecuredInfo)
VALUES ('Very'), ('Secret'), ('Stuff');
GO
CREATE TABLE dbo.NotOKTable
(
NotOKTableID int IDENTITY(1,1) NOT NULL
CONSTRAINT PK_dbo_NotOKTable PRIMARY KEY,
SecuredInfo varchar(100)
);
GO
INSERT dbo.NotOKTable (SecuredInfo)
VALUES ('Other'), ('Important'), ('Things');
GO
CREATE SCHEMA [Security] AUTHORIZATION dbo;
GO
CREATE TABLE [Security].PermittedTableUsers
(
PermittedTableUsers int IDENTITY(1,1) NOT NULL
CONSTRAINT PK_Security_PermittedTableUsers
PRIMARY KEY,
UserName sysname,
SchemaName sysname,
TableName sysname
);
GO
INSERT [Security].PermittedTableUsers (UserName, SchemaName, TableName)
VALUES (N'dbo', N'dbo', 'OKTable');
GO
ALTER FUNCTION [Security].CheckUserAccess
(
#SchemaName AS sysname,
#TableName AS sysname
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS CheckUserAccessOutcome
WHERE EXISTS (SELECT 1 FROM [Security].PermittedTableUsers AS ptu
WHERE ptu.UserName = USER_NAME()
AND ptu.SchemaName = #SchemaName
AND ptu.TableName = #TableName);
GO
CREATE SECURITY POLICY OKTableAccessFilter
ADD FILTER PREDICATE [Security].CheckUserAccess (N'dbo', N'OKTable')
ON dbo.OKTable
WITH (STATE = ON);
GO
CREATE SECURITY POLICY NotOKTableAccessFilter
ADD FILTER PREDICATE [Security].CheckUserAccess (N'dbo', N'NotOKTable')
ON dbo.NotOKTable
WITH (STATE = ON);
GO
SELECT * FROM dbo.OKTable;
SELECT * FROM dbo.NotOKTable;
GO
It's described more fully in this link:
https://blog.greglow.com/2019/10/10/sql-how-to-control-access-to-sql-server-tables-by-entries-in-another-table/
Since users are NOT SQL Server Logins, therefore, I guess you can use the DDL trigger to monitor table rename where you can change the table name in your custom security table. But I don't know if you can throw exception within this trigger to prevent table rename (simulating some type of constraint). Also it would be better if you store the table name in each line rather saving comma separated table names in 1 field.
If you can utilize SQL Logins then the Gordan's solution is also applicable, but sometimes you cannot create SQL Logins, in-case if you have different application databases along with hundreds of thousands of users.

How to force Microsoft Database Project generate ALTER statement for primary key constraint instead of creating temp table?

I have following script for LoginLogo table:
CREATE TABLE [LoginLogo] (
[LoginLogoId] INT IDENTITY (1, 1) NOT NULL,
[LoginId] INT NOT NULL,
[LogoNm] NVARCHAR(255) NULL,
CONSTRAINT [PK_LoginLogo_LoginLogoId] PRIMARY KEY CLUSTERED ([LoginId] ASC),
CONSTRAINT [FK_LoginLogo_LoginId] FOREIGN KEY ([LoginId])
REFERENCES [Login] ([LoginId])
);
GO
CREATE NONCLUSTERED INDEX [IF_LoginLogo_LoginId]
ON [LoginLogo]([LoginId] ASC)
ON [INDX];
I need to change Primary Key Constraint, so I've just changed one line, please see below the change:
CONSTRAINT [PK_LoginLogo_LoginLogoId] PRIMARY KEY CLUSTERED ([LoginLogoId] ASC),
Database project perfectly build changed code, but when it generates database update statement it generates temp table instead of simple ALTER statement. See below generated script:
CREATE TABLE [tmp_ms_xx_LoginLogo] (
[LoginLogoId] INT IDENTITY (1, 1) NOT NULL,
[LoginId] INT NOT NULL,
[LogoNm] NVARCHAR (255) NULL,
CONSTRAINT [tmp_ms_xx_constraint_PK_LoginLogo_LoginLogoId1]
PRIMARY KEY CLUSTERED ([LoginLogoId] ASC)
);
IF EXISTS (SELECT TOP 1 1
FROM [apps].[LoginLogo])
BEGIN
SET IDENTITY_INSERT [apps].[tmp_ms_xx_LoginLogo] ON;
INSERT INTO [apps].[tmp_ms_xx_LoginLogo] ([LoginLogoId], [LoginId], [LogoNm])
SELECT [LoginLogoId],
[LoginId],
[LogoNm],
FROM [LoginLogo]
ORDER BY [LoginLogoId] ASC;
SET IDENTITY_INSERT [tmp_ms_xx_LoginLogo] OFF;
END
DROP TABLE [LoginLogo];
EXECUTE sp_rename N'[tmp_ms_xx_LoginLogo]', N'LoginLogo';
EXECUTE sp_rename N'[tmp_ms_xx_constraint_PK_LoginLogo_LoginLogoId1]',
N'PK_LoginLogo_LoginLogoId', N'OBJECT';
Is it possible to tell Database project to generate ALTER statement instead of creating temp table? How can I force Microsoft Database Project to do that?
Bearing in mind that if you change the clustered index of a table, the table will be rebuilt regardless of whether the script does ALTER TABLE or the SSDT-generated stuff with temp tables, the usual way to solve these problems is to do the ALTER ahead of time
Meaning, you need a script, often referred to as a pre-pre-deploy script (pre-deploy won't work, as it is run post-comparison) that makes the expensive change, so that when the comparison is run the change has already occurred, and hence doesn't get repeated by the dacpac deployment.
This script needs to be run as part of your deployment, before you do any of the sqlpackage stuff. You can specify the change as alter table in this script.
In this particular instance, where the table is going to be rebuilt either way, I can't see it making a great deal of difference to the overall deployment time.

unresolved reference warning - ON statement

When added a constraint in SQL, I am receiving an unresolved reference warning on the 'ON FG_LOGGING' line. Does it need to be declared as I'm not sure on this. This is an existing code I'm looking at that someone else built and cannot see when I search the file, any other mention of FG_LOGGING.
CREATE table Holidays.J2H.PriceCheckWorkList
(
PriceCheckWorkListId INT IDENTITY(1,1),
HotelID int,
HotelRoomID int,
PerPersonCost money,
CONSTRAINT [PK_PriceCheckWorkListId] PRIMARY KEY CLUSTERED (PriceCheckWorkListId)
) ON FG_LOGGING
The error message you are likely getting is:
Invalid filegroup 'FG_LOGGING' specified.
What the error effectively means is that the script was originally intended for a server which had FILEGROUPS set up to store tables in specific drives and folders on the server. Seemingly, the database on which you are now trying to create doesn't have these File Groups - apparantly the full database creation script hasn't been run.
You can either change the file group to run on the default file group (or remove it entirely), e.g.
CREATE table Holidays.J2H.PriceCheckWorkList
(
PriceCheckWorkListId INT IDENTITY(1,1),
HotelID int,
HotelRoomID int,
PerPersonCost money,
CONSTRAINT [PK_PriceCheckWorkListId] PRIMARY KEY CLUSTERED (PriceCheckWorkListId)
) ON [PRIMARY];
Alternatively, you (or your DBA) can recreate the filegroup and associated files on your server (replace the folder with an appropriate path for your server):
ALTER DATABASE Holidays
ADD FILEGROUP FG_Logging;
ALTER DATABASE Holidays
ADD FILE
(
NAME = FG_Logging,
FILENAME = 'c:\temp\FG_Logging.ndf')
TO FILEGROUP FG_Logging;

Defining a schema inside a procedure

I'm working on creating a model data base design for a retail store. I'm trying to create a single procedure which will initialize the database schema.
What I'm trying to achieve is to create a new schema from inside the procedure. My code is as follows:
begin trans
create procedure Retail_Fill
as
create schema Retail_Test;
go
create table Retail_Test.customer(
cust_id int,
cust_name varchar(30),
cust_phone int,
cust_add varchar(50),
constraint pk_customer primary key (cust_id)
);
Here the create schema statement works fine by itself. But inside the procedure it gives an error:
Invaid Syntax!CREATE SCHEMA must be the only statement in the batch
I want to know if it is at all possible to achieve this. If yes then what am I doing wrong or where is the error?
CREATE SCHEMA has to be executed as a separate batch. Batches in SQL Server Management Studio is separated by GO. That is not the case in a stored procedure. You can do what you want by using EXECUTE for the statements that needs to be in a batch of its own like CREATE SCHEMA or CREATE PROCEDURE.
create procedure Retail_Fill
as
exec('create schema Retail_Test');
create table Retail_Test.customer(
cust_id int,
cust_name varchar(30),
cust_phone int,
cust_add varchar(50),
constraint pk_customer primary key (cust_id)
);
As Mikael Eriksson said above creating DB Schema should be in a single script file or may be you can configure some deployment that will create your schema. So it will be good to avoid creating schema within procedures.
You can have these inside procedure when you have to design the schema on fly for each customer (for example).