How does SQL Server lookup object names in stored procedures? - sql-server

I have a stored procedure in SQL Server 2008 such as:
CREATE PROCEDURE [dbo].[test]
AS
BEGIN
SET NOCOUNT ON;
SELECT user_name();
SELECT SCHEMA_NAME()
SELECT * FROM MyView
END
I have a view named testuser.MyView. I then call the SP using:
exec as user = 'testuser' exec test
This shows the user_name and SCHEMA_NAME are both set to testuser
However I also get a Invalid object name 'MyView'. message, as the SP is still looking up the view name in the dbo schema.
Is there anyway to change how the SP is executed so the MyView object references testuser.MyView without having to use a fully qualified name?
I am trying to use a single set of many stored procedures, on identical table structures in different schemas. I really want to avoid rewriting all the SPs using dynamic SQL, or to create copies of all the SPs each using qualified names.

Unqualified object names are resolved using the module owner's default schema. This behavior can't be changed so you'll need to resort to the other methods you mentioned.
Since you already create separate tables and views for each user with the same structure, why not create stored procedures in the user's schema at the same time?

Related

Stored procedure for verifying backup

I am trying to write a stored procedure for verifying backups. This is what I have done so far.
I have used master to store the procedure.
USE [master]
GO
CREATE PROCEDURE verifyData #filename nvarchar(225)
AS
RESTORE VERIFYONLY FROM DISK = #filename;
GO
GO
But after I save the procedure and check it again the file changes to
USE [master]
GO
/****** Object: StoredProcedure [dbo].[verifyData] Script Date: 05/10/2021 3:49:53 pm ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[verifyData] #filename nvarchar(225)
AS
RESTORE VERIFYONLY FROM DISK = #filename;
Where a red squiggly line is shown [dbo].[verifyData] here
Also when I try to execute the procedure my intellisense is not able to pick the procedure out
Any insights would be helpful
But after I save the procedure and check it again the file changes to [dbo].[verifyData]... Also when I try to execute the procedure my intellisense is not able to pick the procedure out
SQL Server DATABASE contains multiple objects such as tables, views, stored procedures, functions, indexes, triggers. These objects owned by a logical entities named SCHEMA.
Unless specified otherwise, all Transact-SQL references to specific object can be a four-part name in the following format: [server_name].[database_name].[schema_name].object_name. This format called Four Parts Name and you can think about it like a full logical path to the object
A four-part name is in a way equivalent to a full path to a file for example: C:\Program Files\Microsoft SQL Server\MSSQL15.SQL2019\MSSQL\DATA\AdventureWorks2019.mdf
To uniquely identify an object within multi-servers environment, you need all four parts: Server Name (1), Database Name (2), Schema Name (3) and Object Name (4).
But when we are working inside a specific database, then we usually uses a two-part name (or two parts "path" if you want), which include the SHEMA and the name of the OBHECT. For example: CREATE PROCEDURE MySchemaName.MySP, or SELECT ColA FROM MySchemaName.MyTableName
A two-part name is in a way equivalent to a relative path to a file. for example if we are in the folder: C:\Program Files\Microsoft SQL Server\MSSQL15.SQL2019\MSSQL\ Then as can use only the relative path to the file DATA\AdventureWorks2019.mdf
Unfortunately most people are lazy (or do not know well) and therefore they are using the name of the object without the name of the SCHEMA, just you did when you created the Stored Procedure. This is not well formatted code!
When you create an object without explicitly using the SCHEMA name (as you did), then SQL Server uses the default schema as the owner of that object, which is [dbo]
But after I save the procedure and check it again the file changes to [dbo].[verifyData]
Off-topic by HIGHLY important! (1) We do not save object but CREATE objects. (2) This is NOT a file but a Stored Procedure.
Note! you must start use the correct phrases if you want to understand.
The answer is simple:
The server uses the right format which is two-part name [dbo].[verifyData] - Name of the default schema, which own the stored procedure + The name of the stored procedure
when I try to execute the procedure my intellisense is not able to pick the procedure
This happens since you did not started with the SCHEMA name. Start to type the name of the SCHEMA and the intellisense should find the SCHEMA name. Next you add dot . and start to type the stored procedure name and the intellisense should find that stored procedure under the SCHEMA
In summary
(1) You should always use at least two-part name (even if most us do not do it)
(2) You can and you should CREATE new SCHEMA and group your objects in the database in a logical groups. To create new SCHEMA you can use: CREATE SCHEMA [Customer]. For more information and option when you CREATE a SCHEMA, check this link.

SQL Server stored procedure returns different with query result

I am new to SQL Server. I wrote a simple stored procedure that returns rows with data condition.
Here is my code:
CREATE PROCEDURE test.newArtists
#LastUpdated smalldatetime
AS
BEGIN
SELECT *
FROM ARTIST
WHERE GENERATED > #LastUpdated OR MODIFIED > #LastUpdated;
END
When I execute this stored procedure, it returns 0 rows. like ...
DECLARE #temp DATETIME;
SET #temp = CONVERT (DATETIME, '2016/12/05');
EXEC test.newArtists #LastUpdated = #temp;
However, when I execute the query without using procedure, it returns about 5,000 rows.
DECLARE #temp DATETIME;
SET #temp = CONVERT (DATETIME, '2016/12/05');
SELECT *
FROM ARTIST
WHERE GENERATED > #temp OR MODIFIED > #temp;
I just do not understand whey those two returns different results.
Thanks for explanations!
===================================================
I find the problem. Thanks.
I'am using test schema.
So I connected to test. However, When I use SELECT * FROM ARTIST it does not search test.ARTIST, but ARTIST table which belongs to dbo.
Summary.
The basic problem here is I got 2 tables with name ARTIST.
However, I still do not understand why it automatically look for dbo schema.
I got little experience with MYSQL, but when I connect to a certain schema, it only find objects inside of the schema. Is it normal or should I do some work to set prefix?
Thanks for answers though b
It is always good practice to refer to database objects by a schema
name and the object name, separated by a period (.).
object referred to without an explicit schema name ... will be located by searching the default schema first, followed by the dbo schema
Source: SQL Server Best Practices – Implementation of Database Object Schemas
Dbo is the default schema, it will always go to that one first if the schema is not specified. It is a good practice to always specify the schema in the query especially if you are using more than one. That way it won't waste time looking in the wrong schema first.

Schema lookup for stored procedure different to tables (bug?)

If you run the following in sql server...
CREATE SCHEMA [cp]
GO
CREATE TABLE [cp].[TestIt](
[ID] [int] NULL
) ON [PRIMARY]
GO
CREATE PROCEDURE cp.ProcSub
AS
BEGIN
Print 'Proc Sub'
END
GO
CREATE PROCEDURE cp.ProcMain
AS
BEGIN
Print 'Proc Main'
EXEC ProcSub
END
GO
CREATE PROCEDURE cp.ProcMain2
AS
BEGIN
Print 'Proc Main2'
SELECT * FROM TestIt
END
GO
exec cp.ProcMain2
GO
exec cp.ProcMain
GO
You get the error
Could not find stored procedure 'ProcSub'
Which makes it impossible to compartmentalize procedures into a schema without hard coding the schema into the execution call. Is this by design or a bug as doing a select on tables looks in the procedures schema first.
If anyone has a work around I'd be interested to hear it, although the idea is that I can give a developer two stored procedures which call each other and can put into whatever schema they like in 'their' database that they can run for the purpose of being a utility that looks at the objects of another given schema in the same database.
I have looked to see if I might be able to get round it with Synonyms but they seem to have the same problem associated with them.
This is by design, because the default schema is not set for the user calling the procedure.
When schema is not specified in the query, sql server will try the default schema first and then the dbo (if different) schema. So you need to set the default schema or make your procedure creation using fully qualified names.
Check out:
Beginning with SQL Server 2005, each user has a default schema. The
default schema can be set and changed by using the DEFAULT_SCHEMA
option of CREATE USER or ALTER USER. If DEFAULT_SCHEMA is left
undefined, the database user will have dbo as its default schema.
https://technet.microsoft.com/en-us/library/ms190387%28v=sql.105%29.aspx

Database name as variable

I have a database with stored procedures which selects data from another database
select * from Users in AnotherDatabase.dbo.Users
I have a couple of versions of the other database, i.e.
AnotherDatabaseProduction, AnotherDatabaseDevelopment, AnotherDatabaseStage
I remember a while back that I created some kind of global variable like AnotherDatabase_Pointer which I could set by running some stored procedure like SetAnotherDatabaseToStage:
AnotherDatabase_Pointer = 'AnotherDatabaseStage' //pseudo-code
Which I could then use in my stored procedures.
But I cannot seem to remember how it was done. All I can find is how to do this with string replacing/concatenating: variable database name :(
Is this possible? MS SQL.
You are probably thinking of a Synonym.
CREATE SYNONYM dbo.whatever FOR dbname.dbo.procedure_name;
Now, if you want to change it to some other database, you just drop and re-create the synonym:
DROP SYNONYM dbo.whatever;
GO
CREATE SYNONYM dbo.whatever FOR other_dbname.dbo.procedure_name;

Can I set a default schema for within a stored procedure?

I'm working on the next update for StackQL.
One thing I want to do is have the ability to query over several releases. So when I loaded the October data, for example, I didn't delete the old September database. It's still out there. In fact, you can even still query it by including the database name like this:
select top 10 * from SO_Sept09..Posts
This will be even more important as they start providing data for ServerFault and SuperUser.
But I don't like having a whole bunch of databases out there to support this. I'd much rather put all the data in the same database and separate each distinct set into it's own schema. But to make this possible, I need to be able to set a default schema as part of the stored procedure that runs the query, based on a parameter passed to the stored procedure that tells it which database the user selected from a future drop down list to appear in the tool bar.
Queries at StackQL are eventually just passed to the exec() function like this:
exec(#QueryText)
Is there anything I can do either in the stored procedure or prepend to the QueryText string (ala USE [DatabaseName]) to set the default schema used in a query?
There are pieces of how to do this in various places here, but not altogether. The way to do this is:
Make a unique login & user for each schema
Make these users the owners of each different schema.
Set each such user's default schema to be the schema that they own.
Use the syntax EXECUTE ('sql commands') AS USER = 'schema-owner' to execute your SQL commands in the context of that default schema.
The following script demonstrates this:
--====== Create the Login for the User:
CREATE LOGIN [UserTest1] WITH PASSWORD='whatever', DEFAULT_DATABASE=[TestUsers], DEFAULT_LANGUAGE=[us_english]
GO
--====== Make a User for the Login:
CREATE USER [UserTest1] FOR LOGIN [UserTest1]
GO
--====== Make a Schema owned by the User and default to it:
-- (I assume that you already have the schemas)
CREATE SCHEMA [UserTest1] AUTHORIZATION [UserTest1]
GO
ALTER USER [UserTest1] WITH DEFAULT_SCHEMA=[UserTest1]
GO
--====== Make a sProc in dbo
CREATE PROCEDURE [dbo].[TestSchema_Exec] AS
SELECT 'executing in schema [dbo]'
GO
--====== Make a similar sProc in New Schema
CREATE PROCEDURE [UserTest1].[TestSchema_Exec] AS
SELECT 'executing in schema [UserTest1]'
GO
--========= Demonstrate that we can switch Default Schemas:
EXEC('TestSchema_Exec')
EXEC('TestSchema_Exec') AS USER = 'UserTest1'
Other than modifying #QueryText itself, the only thing I can think of is a user's default schema:
ALTER USER SO_Sept09_Reader WITH DEFAULT_SCHEMA = SO_Sept09
...and then connect as a different user for each schema you want to use. Hackity hack.
But if your query is dynamically constructed anyway (and I'm sure you know why that's often not a great idea), it might be easiest to just add a schema placeholder to the query text, and pass the schema name along with the query to a replacement function.
Another possibility is to generated copies of each SP in each schema, the unmodified table name in an SP refers to tables in the same schema.
Note this doesn't work with dynamic SQL inside such an SP:
CREATE PROCEDURE schema_a.SP
#somesql AS varchar(MAX)
AS
BEGIN
EXEC ( #somesql )
END
CREATE PROCEDURE schema_b.SP
#somesql AS varchar(MAX)
AS
BEGIN
EXEC ( #somesql )
END
Won't work, because the schema affinity is lost inside the EXEC.
While this:
CREATE PROCEDURE schema_a.SP
AS
BEGIN
SELECT * FROM tbl -- Will use schema_a.tbl first
END
CREATE PROCEDURE schema_b.SP
AS
BEGIN
SELECT * FROM tbl -- Will use schema_b.tbl first
END
works fine.
Similarly:
EXEC ( 'EXEC schema_a.SP' )
would obviously work fine.
Untried, but: could you combine the different schemas' data into one view, and add a column calling out the schema name?
CREATE VIEW AllPosts AS
SELECT Data1, Data2, 'Sept09' AS Partition FROM SO_Sept09..Posts
UNION ALL
SELECT Data1, Data2, 'Oct09' AS Partition FROM SO_Oct09..Posts
...
SELECT * FROM AllPosts WHERE Partition = 'Sept09'
SELECT * FROM dbo.AllPosts('Sept09') -- if use table-valued function instead
Okay, I have a new way to do this that might work a little better for me. It's a variant on my comment to Michael Petrotta's answer:
But it does give me the idea to maybe have several users and pick the connection string on the fly.
What I will do is have just one user for executing these queries, but I will swap out the connection string on the fly to specify the correct Initial Catalog.

Resources