patternsqlMinor
Can view adapt to implicitly reference user's default schema?
Viewed 0 times
canreferenceuserviewdefaultimplicitlyschemaadapt
Problem
A bit of background. I have a base application and most clients use it as standard. However some clients have small code and database customisations. Each of these clients has their own branch and maintenance can be tricky.
I want to consolidate all these into a single database structure (not a single database - we aren't doing multi-tenancy) to enable upgrades to be applied in a much more uniform fashion. I'm still at the proof of concept stage, but the route I was going down would be to have the standard objects stay in the schema they currently exist in (mostly dbo) and have the custom objects reside in a schema for each client.
For example, I could have dbo.users and client1.users which has some additional columns. If I set the default schema for the client to be "client1" then the following query
will return data from the client1 schema or the dbo schema depending on which login is connected. This is absolutely perfect for what I'm trying to achieve.
The problem I'm running into is with Views. I have many views which are in the dbo schema and refer to the Users table. No matter which user I connect to the database as, these views always select from dbo.users.
So I'm guessing the question I have is: Can I prefix the tables in the view with some variable like "DEFAULT"? e.g.
If this isn't possible and I'm totally barking up the wrong tree, do you have any suggestions as to how I can achieve what I'm setting out to do?
Many thanks.
I want to consolidate all these into a single database structure (not a single database - we aren't doing multi-tenancy) to enable upgrades to be applied in a much more uniform fashion. I'm still at the proof of concept stage, but the route I was going down would be to have the standard objects stay in the schema they currently exist in (mostly dbo) and have the custom objects reside in a schema for each client.
For example, I could have dbo.users and client1.users which has some additional columns. If I set the default schema for the client to be "client1" then the following query
SELECT * FROM userswill return data from the client1 schema or the dbo schema depending on which login is connected. This is absolutely perfect for what I'm trying to achieve.
The problem I'm running into is with Views. I have many views which are in the dbo schema and refer to the Users table. No matter which user I connect to the database as, these views always select from dbo.users.
So I'm guessing the question I have is: Can I prefix the tables in the view with some variable like "DEFAULT"? e.g.
SELECT u.username, u.email, a.level
FROM DEFAULT.users u INNER JOIN DEFAULT.accessLevels a ON u.accessID = a.accessIDIf this isn't possible and I'm totally barking up the wrong tree, do you have any suggestions as to how I can achieve what I'm setting out to do?
Many thanks.
Solution
Create multiple views and/or table-valued functions. You can't override the schema without specifying it explicitly unless you build dynamic SQL to do so for you, but you can't do that in a view or a function - only a stored procedure.
With that said, you could do that with something like this, however it will fall back to dbo (or some other schema you hard-code) if a match is not found or if you are using Windows groups for example (which prior to SQL Server 2012 do not support default schema):
You can test this:
And then execute it as yourself and also as
Results:
But like I said, this is potentially fragile, and I would lean toward either creating a view per schema, or changing the design altogether.
With that said, you could do that with something like this, however it will fall back to dbo (or some other schema you hard-code) if a match is not found or if you are using Windows groups for example (which prior to SQL Server 2012 do not support default schema):
CREATE PROCEDURE dbo.whatever_procedure
AS
BEGIN
SET NOCOUNT ON;
DECLARE @sql NVARCHAR(MAX), @s SYSNAME;
SELECT @s = d.default_schema_name
FROM sys.database_principals AS d
LEFT OUTER JOIN sys.server_principals AS s
ON d.[sid] = s.[sid]
WHERE SUSER_SNAME() IN (d.name, s.name);
SELECT @s = COALESCE(@s, N'dbo');
SET @sql = N'SELECT x FROM ' + QUOTENAME(@s)
+ '.whatever_view_name;';
EXEC sp_executesql @sql;
END
GOYou can test this:
USE tempdb;
GO
CREATE SCHEMA foo;
GO
CREATE LOGIN flubat WITH PASSWORD='flubat',CHECK_POLICY = OFF;
GO
CREATE USER flubat FROM LOGIN flubat WITH DEFAULT_SCHEMA = foo;
GO
GRANT SELECT ON SCHEMA::foo TO flubat;
GO
CREATE VIEW dbo.whatever_view_name AS SELECT x = 'dbo';
GO
CREATE VIEW foo.whatever_view_name AS SELECT x = 'foo';
GO
-- create the above procedure
GO
GRANT EXEC ON dbo.whatever_procedure TO flubat;
GOAnd then execute it as yourself and also as
flubat:EXEC dbo.whatever_procedure;
GO
EXECUTE AS USER = N'flubat';
GO
EXEC dbo.whatever_procedure;
GO
REVERT;Results:
x
----
dbo
x
----
fooBut like I said, this is potentially fragile, and I would lean toward either creating a view per schema, or changing the design altogether.
Code Snippets
CREATE PROCEDURE dbo.whatever_procedure
AS
BEGIN
SET NOCOUNT ON;
DECLARE @sql NVARCHAR(MAX), @s SYSNAME;
SELECT @s = d.default_schema_name
FROM sys.database_principals AS d
LEFT OUTER JOIN sys.server_principals AS s
ON d.[sid] = s.[sid]
WHERE SUSER_SNAME() IN (d.name, s.name);
SELECT @s = COALESCE(@s, N'dbo');
SET @sql = N'SELECT x FROM ' + QUOTENAME(@s)
+ '.whatever_view_name;';
EXEC sp_executesql @sql;
END
GOUSE tempdb;
GO
CREATE SCHEMA foo;
GO
CREATE LOGIN flubat WITH PASSWORD='flubat',CHECK_POLICY = OFF;
GO
CREATE USER flubat FROM LOGIN flubat WITH DEFAULT_SCHEMA = foo;
GO
GRANT SELECT ON SCHEMA::foo TO flubat;
GO
CREATE VIEW dbo.whatever_view_name AS SELECT x = 'dbo';
GO
CREATE VIEW foo.whatever_view_name AS SELECT x = 'foo';
GO
-- create the above procedure
GO
GRANT EXEC ON dbo.whatever_procedure TO flubat;
GOEXEC dbo.whatever_procedure;
GO
EXECUTE AS USER = N'flubat';
GO
EXEC dbo.whatever_procedure;
GO
REVERT;x
----
dbo
x
----
fooContext
StackExchange Database Administrators Q#40851, answer score: 2
Revisions (0)
No revisions yet.