How to access LinkedServer on another database in SQL Server - sql-server

I want to know how to access LinkedServer of databaseA from databaseB in SQL Server.
I need to know sample code. Thanks.

If the servers are already linked, are both running SQL Server (you're not linking to a different kind of database completely), and you are connected to databaseB with a login having appropriate permissions, here is an example:
SELECT <columns>
FROM ServerName.databaseA.schema.Table alias
WHERE alias.Column = 'SomeValue'
Note you need to use all four parts of the name (server, database, schema, object), and therefore you will definitely want to use a table alias. The schema is practically almost always dbo, and can often be elided with just an extra . ( FROM ServerName.databaseA..Table).
But as long as you get the name right, you can use items in databaseA as if they were there in B, including for JOINs and similar. However, performance can be poor, because the server for databaseB must put together an execution plan without knowing anything about indexes or statics from databaseA.
If the databaseA is something other than SQL Server, you will need to use OPENQUERY(). Like the first option, OPENQUERY() results can be given an alias and then used with JOIN, APPLY, subquery, etc, and the same warning about performance applies.

Related

Alias a database name within the query as to avoid dynamic SQL?

We have 4 distinct environments for one of our applications through our Dev to Prod pipeline, each having two databases. Within these databases there are several queries referencing the other database. So for example we may have this:
select a.name, b.description
FROM Database1.dbo.Names a
INNER join Database2.dbo.Descriptions b on a.ID = b.ID;
Just an example, not an actual query from our system... but hopefully you get the point. All four environments live on their own SQL servers so I can gracefully move code from bottom to top without any changes.
Now we're looking at possibly combining the bottom three environments on to the same SQL Server Instance (Prod will keep its own server), so all six databases will be on the same server together. In doing this the database names will have a suffix of their type, so Database1_Dev and Database2_Dev for example.
Well there lies the issue, all queries using just Database1 and Database2 will now break, and if I do change them to use Database1_Dev and Database2_Dev at the lowest level I can't just move them up to the next level Database1_Sandbox and Database2_Sandbox without modifying the queries.
The options I'm looking at are requesting three SQL instances on this server - which I'm sure will get shot down - or changing the queries to use dynamic SQL where I can pass in the DB name as a variable. I don't like either option.
Synonyms are the only thing I can think of that would allow a different name to be used, but this is going the wrong direction. I don't need one DB to have multiple names, rather multiple databases to have the same name within the same context. So like if in a procedure I could pass in the environment and I could alias Database1 to Database1_Sandbox just within the transaction or query. I don't think there is any way to do this, but I thought I'd ask.
Thanks for any suggestions.

Why do I have to query the full path in ssms?

I want to query in ssms but I always have to add the specific schema as a prefix, although I have ran the query:
USE (the specific db I wanna use);
GO
What should I do for ssms to bring back only tables from the specific db and schemas while querying?
Within SQL Server, you use the Fully Qualified Name. That consists of three parts (though technically, when using a linked server, you could add a servername part as well):
Database
Schema
Table
And can be used in the following manner:
SELECT * FROM <database>.<schema>.<table>
The USE keyword simply changes the context in which you are executing a SQL command. It's identical to using the drop-down box in SSMS to change to a different database.
By switching the database context, you can typically skip the part of the query above. By switching context, it is assumed all commands will be executed within the database you changed to.
The reason it's still there is if you want to access objects that physically reside within a different database on the same SQL Server instance.
The schema is just a way to group your tables. The default schema is database owner (dbo). If you omit the schema name, it's assumed the object is in the dbo schema. So the following 2 commands are assumed to be identical:
SELECT * FROM dbo.MyTable
SELECT * FROM MyTable
However, using schemas is a great way to structure your database, as you can logically group related objects within the same schema, and assign permissions accordingly.
From an OLTP perspective, you could have a schema dealing with orders, and one with sales. That way it is easier for people to filter only the objects they are interested in, and for the dba to limit access to schemas to specific departments.
If you work with data warehousing, it's not unusual to see an Extract schema, a Stage schema, and a Fact and Dimension schema.

Name clash with sys.sysusers system view in SQL Server

(Note that this was on SQL Server 2008, but I have a colleague who reports the same issue on SQL Server 2014.)
I'm using a framework that supports multiple database back-ends, and our application has a table called sysUsers, which works fine in MySQL.
We now need to install it on SQLServer and it appears that this name conflicts with a built-in system view. The system view is sys.sysusers and the application table is dbo.sysUsers.
I am aware that the case difference is irrelevant to SQL Server, however the schema seems to be being ignored for some reason.
SELECT * FROM sys.sysusers; returns records from sys.sysusers. This is wholly as expected.
SELECT * FROM sysUsers; returns records from sys.sysusers. This is surprising (I would have thought the local schema would take precedence) but perhaps explicable.
However, SELECT * FROM dbo.sysUsers; still returns records from sys.sysusers. This seems just plain wrong as I am explicitly selecting the dbo schema.
I haven't found anything in the MS documentation that says these names are reserved.
I have tried renaming the table and hacking the code to use a different name, and everything works (i.e. this is nothing to do with the SQLServer integration within the application) and the same results are seen when running the queries from the management console directly. Therefore this appears to definitely be an issue with the conflicting table name and not a middle-ware error or syntax difference.
If this table name is reserved, why does MSSMS allow me to create it? If it is not reserved, why does it not let me query it?
And how can I work round the problem without requiring application updates (as these would be a migration headache for other deployments).
There are at least three workarounds, but none guarantee that no code has to be rewritten (except the one that's horribly unsafe):
Use a case-sensitive collation when creating your database (CREATE DATABASE Foo COLLATE Latin1_General_CS_AS). In this case, sysUsers will be a different object from sysusers, in all circumstances. You can set a case-insensitive collation immediately after creating the database so your data doesn't end up case-sensitive, as this is probably not what the end users want. Obviously this won't work if your application is actually relying on case-insensitive object names, unless you rewrite your queries carefully. Note that this means that all database objects, even those created afterwards, will have case-sensitive names, as this is embedded in the system tables on creation and can't be changed afterwards.
Use a schema other than dbo. The system table mapping occurs only for that scheme, not any others. If your application uses its own schema exclusively, any sysusers you create in that will not be aliased to sys.sysusers. (This isn't documented anywhere, but it is how it works.) Note that in order for this to work, you must always specify the schema explicitly even when it is the default schema for your user, otherwise you will again get the system table (I'd consider this a bug, but it's probably a necessity because of the way old scripts will assume sysusers resolves anywhere).
Enable the Dedicated Administrator Connection, restart SQL Server in single user mode, switch the mssqlsystemresource database to READ_WRITE and DROP VIEW sysusers. This will remove sys.sysusers from all databases. Doing this will void your warranty, it will cause Microsoft Support to laugh at you if you come crying to them, it may make installing future Service Packs and updates impossible and is emphatically not recommended, but I'm mentioning it anyway, for science. No code anywhere should be using this view, but, you know, I'm not an engineer working on SQL Server itself.
Note that lowering the compatibility level is not a workaround, which I mention for completeness. This has no effect on how these table names are resolved, even if it was a desirable approach (which it's not).
I consider the change made in SQL Server 2012 to ignore the dbo qualifier and resolve to these old, deprecated names anyway a mistake and if it were up to me I'd at least make it possible to opt out of this behavior with a trace flag, but it's not up to me. You could consider opening up an issue on Microsoft Connect for it, because the current behavior makes it needlessly complicated for RDBMS-agnostic code to run.

SQL Server How to add a linked server to the same instance without performance impact

in my company, we have several environments with MS SQL database servers (SQL 2008 R2, SQL 2014). For the sake of simplicity, let us consider just a TEST environment and a PROD environment and two sql servers in each. Let the servers be called srTest1, srTest2, srProd1, srProd2 and each be running a default MS SQL Server instance. We work with multiple databases, say DataDb, ReportDb, DWHDb.
We want to keep the same source code in T-SQL for both TEST and PROD, but the problem is the architecture or distribution of the above mentioned databases in each environment:
TEST:
srTest1 - DataDb
srTest2 - DWHDb, ReportDb
PROD:
srProd1 - DataDb, ReportDb
srProd2 - DWHDb
Now, say, in ReportDb, we write stored procedures with many SELECTs referencing tables and other objects in DataDb and DWHDb. In order to have source code as universal as possible, we decided to create linked servers for each database on each db server in each environment and name them with respect to the database they're created for. Therefore, there'll be these linked servers:
lnkDataDb, lnkReportDb and lnkDWHDb on srTest1,
lnkDataDb, lnkReportDb and lnkDWHDb on srTest2,
lnkDataDb, lnkReportDb and lnkDWHDb on srProd1,
lnkDataDb, lnkReportDb and lnkDWHDb on srProd2.
And we'll adjust the source in the stored procs accordingly. For instance:
Instead of
SELECT * FROM DataDb.dbo.Contact
We'll write
SELECT * FROM lnkDataDb.DataDb.dbo.Contact
The example above is reasonable for a situation where the database from which you execute the query (ReportDb) lies on a different server than that with the referenced table (DataDb). Which is the case for the TEST environment. But not so in PROD. It is performance I'm here concerned about. The SQL Server will treat that SELECT as a "remote query" no matter whether, in fact, it is a reference to a local object or not.
Now, it comes the most important part:
If you check these 3 queries for their actual execution plans, you'll see an interesting thing:
(1) SELECT * FROM DataDb.dbo.Contact
(2) SELECT * FROM srProd1.DataDb.dbo.Contact
(3) SELECT * FROM lnkDataDb.DataDb.dbo.Contact
The first two (query #1 and #2) have the same execution plan (the fastest possible) even if you use the four-part name manner of referencing the table Contact in #2.
The last query has a different plan (remote query, thus slower).
The question is:
Can you somehow create a linked server to self (the same sql server instance, the default instance actually) as an "alias" to the name of the host (srProd1) in order for the SQL server to be forced to understand it as local and not issue "remote execution" plans?
Thanks a lot for any hints
Pavel
Recently I found a workaround which seems to solve this kind of issues more efficiently and more elegantly than the solution with self-pointing linked servers.
If you work (making reports, for example) with multiple databases on multiple SQL servers and the physical distribution of the databases on the servers is a challenge since it may differ from one environment to another (e.g. TEST vs PROD), I suggest this:
Use three-part db object names whenever possible. If the objects are local, then execution plans are also local, and thus effective.
Example:
SELECT * FROM DataDb.dbo.Contact
If you happen to run the above query from within a different SQL server instance (residing on a different physical machine, for example, but this not necessarily, the other SQL server instance could be installed even on the same machine), briefly if you're about to use a four-part name:
SELECT * FROM lnkDataDb.DataDb.dbo.Contact
Then you can circumvent that using the following trick:
Let's assume lnkDataDb points to srTest2 and you're executing your queries from srTest1. Now, you'll create a "fake" database DataDb on your local server (srTest1). This fake DataDb shall contain no real db objects (no tables, no views, no stored procedures, no UDFs etc.). There shall only be synonyms defined in it. (And there also shall be the same schemas in it as those in the real DataDb on srTest2). These synonyms shall be named exactly the same way as their real db-object counterparts in DataDb on srTest2. Example:
-- To be executed on srTest1.
EXEC sp_addlinkedserver
#server = N'lnkDataDb',
#srvproduct = N'',
#provider = N'SQLNCLI',
#datasrc = N'srTest2'
;
GO
CREATE DATABASE [DataDb];
GO
USE [DataDb];
GO
CREATE SYNONYM dbo.Contact FOR lnkDataDb.DataDb.dbo.Contact;
GO
Now, if you want to SELECT rows from the table dbo.Contact residing in the database DataDb on srTest2 and you're executing your query from srTest1, you'll use a simple three-part table name:
SELECT * FROM DataDb.dbo.Contact
Of course, on srTest1, this is not a table, that's just a synonym referencing the same-named table on srTest2. However, that's the trick, you use the same query syntax as if you were executing it on srTest2 where the real db object resides.
There are disadvantages of this approach:
On the local server, at the beginning, there must not be a database
with the same name as the remote one. Because you're about to create
a "fake" database with that name to reflect the names of remote
db objects.
You're creating one database that is almost empty, thus
increasing the mess of various databases residing on your local
SQL server. This might provoke reluctance of your database admin
if they prefer having as few databases as possible.
If you're developing your T-SQL scripts in SQL Server Management
Studio, for example, using synonyms cuts you off from the convenience
of the IntelliSense feature.
Advantages outweigh the above-mentioned disadvantages, though:
Your scripts work in any environment (DEV, TEST, PROD) without
the need to change any part of the source code.
If the other database you're querying data from resides on the same
SQL server instance as your script, you also use the three-part name
convention and your SQL server evaluates the query in execution plan
as local which is OK. (This is what the original question of this
post was searching to solve.)
If the other database you're querying data from resides on another
SQL server instance, you still use a "local syntax manner" of a SQL
query (with the synonym) which, only at runtime, evaluates in
a remote execution plan. Which is also fine because the db object
actually is remote.
To summarize
The query executes as local if the referenced object is local, the query executes as remote if the referenced object is remote, but the T-SQL script is always the same. You don't have to change a letter in it.

syntax difference between .. and . in SQL Server in sysobjects

I've seen something like this in SQL Server (running this query against the master database):
select * from tempdb..sysobjects
which seems to return exactly the same results as:
select * from tempdb.sys.objects
I've seen that the double dot can be used as a way to omit the schema name but I don't see anything omitted here, going by that logic then tempdb..objects would be valid which is not).
tempdb..objects will be interpretted as tempdb.dbo.objects
Both are two different system views
sys.objects
Contains a row for each user-defined, schema-scoped object that is
created within a database, including natively compiled scalar
user-defined function.
sys.sysobjects
Contains one row for each object that is created within a database,
such as a constraint, default, log, rule, and stored procedure
Note : This SQL Server 2000 system table is included as a view for backward compatibility. We recommend that you use the current SQL Server system views instead. To find the equivalent system view or views, see Mapping System Tables to System Views (Transact-SQL). This feature will be removed in a future version of Microsoft SQL Server. Avoid using this feature in new development work, and plan to modify applications that currently use this feature.

Resources