Linked Servers and local tables join - sql-server

I'm having a problem joining local tables and linked server tables. I can do it just by using inner joins, but it's taking me too long to execute the query.
I know there's a way to do it with OPENQUERY, but I can't get it.
Here's what I was doing in the beginning:
SELECT DISTINCT
local.L_ID
FROM dbo.local_table AS local
INNER JOIN [server].[db].[dbo].[TB_TEST] as ts
on local.L_ID = ts.L_ID
LEFT JOIN [server].[db].[dbo].[TB_EXE] as ex
on ts.A_ID = ex.T_ID
Now I'm trying to do this:
SELECT DISTINCT
local.L_ID
FROM dbo.local_table AS local
INNER JOIN (
SELECT *
FROM OPENQUERY(SERVER,'SELECT L_ID FROM TB_TEST'
) ts
on local.L_ID = ts.L_ID
left join OPENQUERY(SERVER,'SELECT T_ID FROM TB_EXE') ex
on ts.A_ID = ex.T_ID
Can you help me doing this the right way so the query runs quicker?

This kind of query's (with Linked Servers) may be slow because of bad connection of current instance to another or if on one of the servers is used older version of SQL Server. More info in this article.
I recommend you to use temp tables:
SELECT *
INTO #ts
FROM OPENQUERY(SERVER,'SELECT L_ID FROM TB_TEST;')
SELECT *
INTO #ex
FROM OPENQUERY(SERVER,'SELECT T_ID FROM TB_EXE;')
SELECT DISTINCT
l.L_ID
FROM dbo.local_table AS l
INNER JOIN #ts
on l.L_ID = ts.L_ID
LEFT JOIN #ex
on ts.A_ID = ex.T_ID
DROP TABLE #ts
DROP TABLE #ex
About your query's.
You use almost right syntax. Try it like:
SELECT DISTINCT
local.L_ID
FROM dbo.local_table AS local
INNER JOIN (
SELECT *
FROM OPENQUERY(SERVER,'SELECT L_ID FROM TB_TEST;')
) ts
on local.L_ID = ts.L_ID
left join (
SELECT *
FROM OPENQUERY(SERVER,'SELECT T_ID FROM TB_EXE;')
) ex
on ts.A_ID = ex.T_ID
Or:
SELECT DISTINCT
local.L_ID
FROM dbo.local_table AS local
INNER JOIN OPENQUERY(SERVER,'SELECT L_ID FROM TB_TEST;') ts
on local.L_ID = ts.L_ID
left join OPENQUERY(SERVER,'SELECT T_ID FROM TB_EXE;') ex
on ts.A_ID = ex.T_ID
Also note that you are using LEFT JOIN with third table and don't use it at all.

Related

SQL Server 2017 vs SQL Server 2012 performance problem

I have SQL code that is run on SQL Server 2017 in less than 200 ms, but the same code on SQL Server 2012 takes more than 3 seconds - can anyone tell me:
why is this happening
how do I solve it
select count(*) from dbo.mConsultationQuestionsReplies = 1,300,000
This is my code:
DECLARE #maxCount int;
DECLARE #ddlIds nvarchar(max);
SET #maxCount = 6;
SET #ddlIds = '4,8,840,779,10,813,3,18,7,918';
IF OBJECT_ID('tempdb..#rList') IS NOT NULL
DROP TABLE #rList
IF OBJECT_ID('tempdb..#docList') IS NOT NULL
DROP TABLE #docList
SELECT
rd.UserID,
(CASE
WHEN ids1.value IS NULL
THEN CAST(ids2.value AS int)
ELSE CAST(ids1.value AS int)
END) [cid]
INTO
#docList
FROM
mDJDoctors [rd]
LEFT JOIN
dbo.[mDJDoctorsSpeciality] [rs] ON rd.DoctorID = rs.doctorId
LEFT JOIN
dbo.mDJSpecialtyCategory [rca] ON rca.SpecialtyId = rs.specialId
LEFT JOIN
STRING_SPLIT(#ddlIds, ',') [ids1] ON rca.CategoryId = ids1.value
LEFT JOIN
dbo.mDJDoctorsSpecialtyAbove [ars] ON ars.doctorId = rd.DoctorID
LEFT JOIN
dbo.mDJSpecialtyAboveCategory [arca] ON arca.AboveSpecialtyId = ars.SpecialtyAboveId
LEFT JOIN
STRING_SPLIT(#ddlIds, ',') [ids2] ON [arca].CategoryId = ids2.value
WHERE
[ids1].value IS NOT NULL
OR [ids2].value IS NOT NULL
SELECT *
INTO #rList
FROM
(SELECT
*,
ROW_NUMBER() OVER (PARTITION BY t.cid ORDER BY t.ReplyDateInsert DESC) AS rowNumber
FROM
(SELECT DISTINCT r.ReplyUserID, r.ReplyDateInsert, d.cid
FROM dbo.mConsultationQuestionsReplies[r]
JOIN #docList [d] ON r.ReplyUserID = d.UserID
WHERE r.ReplyId IN (SELECT MAX(r.ReplyId)[id]
FROM dbo.mConsultationQuestionsReplies[r]
JOIN #docList [d] on r.ReplyUserID = d.UserID
GROUP BY d.cid, d.UserID)) [t]) [t]
WHERE
t.rowNumber <= #maxCount
SELECT
u.FirstName AS DoctorName,
u.UserID AS DoctorUserId,
sp.specialFaName AS DoctorSpecialty,
ab.SpecialtyAboveFaName AS DoctorAboveSpecialty,
md.DoctorGUID,
md.DoctorID,
cp.ProfileISOnline,
p.ProfilePicture AS DoctorProfilePicture,
p.ProfileDateInserted,
r.ReplyDateInsert AS LastReplyDateInsert,
r.cid
FROM
dbo.mDJDoctors AS md
INNER JOIN
dbo.Core_Users AS u ON md.UserID = u.UserID
INNER JOIN
dbo.Core_Profiles AS p ON u.UserID = p.UserID
INNER JOIN
(SELECT * FROM #rList) [r] ON r.ReplyUserID = u.UserID
INNER JOIN
dbo.mConsultationDocterProfile AS cp ON cp.UserID = u.UserID
LEFT OUTER JOIN
dbo.mDJDoctorsSpeciality AS mdad ON md.DoctorID = mdad.doctorId
LEFT OUTER JOIN
dbo.mDJSpecialty AS sp ON mdad.specialId = sp.specialId
LEFT OUTER JOIN
dbo.mDJDoctorsSpecialtyAbove AS mdad2 ON md.DoctorID = mdad2.doctorId
LEFT OUTER JOIN
dbo.mDJSpecialtyAbove AS ab ON mdad2.SpecialtyAboveId = ab.SpecialtyAboveId
WHERE
cp.ProfileISOnline = 1
update :
base on guid from marc-s , i removed STRING_SPLIT and new result is
declare #maxCount int;
set #maxCount = 6;
IF OBJECT_ID('tempdb..#rList') IS NOT NULL DROP TABLE #rList
IF OBJECT_ID('tempdb..#docList') IS NOT NULL DROP TABLE #docList
select rd.UserID, (case when arca.CategoryId is null then cast(rca.CategoryId as int) else cast(arca.CategoryId as int) end)[cid] into #docList
from mDJDoctors [rd] WITH (NOLOCK)
left join dbo.[mDJDoctorsSpeciality] [rs] WITH (NOLOCK) on rd.DoctorID = rs.doctorId
left join dbo.mDJSpecialtyCategory [rca] WITH (NOLOCK) on rca.SpecialtyId = rs.specialId
left join dbo.mDJDoctorsSpecialtyAbove [ars] WITH (NOLOCK) on ars.doctorId = rd.DoctorID
left join dbo.mDJSpecialtyAboveCategory [arca] WITH (NOLOCK) on arca.AboveSpecialtyId = ars.SpecialtyAboveId
where arca.CategoryId in (4,8,840,779,10,813,3,18,7,918) or rca.CategoryId in (4,8,840,779,10,813,3,18,7,918)
select * into #rList from (
select * , ROW_NUMBER() OVER (PARTITION BY t.cid ORDER BY t.ReplyDateInsert DESC) AS rowNumber from (
select distinct r.ReplyUserID, r.ReplyDateInsert, d.cid
from dbo.mConsultationQuestionsReplies[r] WITH (NOLOCK)
join #docList [d] on r.ReplyUserID = d.UserID
where r.ReplyId in(
select max(r.ReplyId)[id]
from dbo.mConsultationQuestionsReplies[r] WITH (NOLOCK)
join #docList [d] on r.ReplyUserID = d.UserID
group by d.cid,d.UserID
))[t])[t] where t.rowNumber <= #maxCount
SELECT distinct
u.FirstName AS DoctorName,
u.UserID AS DoctorUserId,
sp.specialFaName AS DoctorSpecialty,
ab.SpecialtyAboveFaName AS DoctorAboveSpecialty,
md.DoctorGUID,
md.DoctorID,
cp.ProfileISOnline,
p.ProfilePicture AS DoctorProfilePicture,
p.ProfileDateInserted,
r.ReplyDateInsert AS LastReplyDateInsert,
r.cid
FROM dbo.mDJDoctors AS md WITH (NOLOCK)
INNER JOIN dbo.Core_Users AS u WITH (NOLOCK)
ON md.UserID = u.UserID
INNER JOIN dbo.Core_Profiles AS p WITH (NOLOCK)
ON u.UserID = p.UserID
INNER JOIN
(
select * from #rList
) [r]
ON r.ReplyUserID = u.UserID
INNER JOIN dbo.mConsultationDocterProfile AS cp WITH (NOLOCK)
ON cp.UserID = u.UserID
LEFT OUTER JOIN dbo.mDJDoctorsSpeciality AS mdad WITH (NOLOCK)
ON md.DoctorID = mdad.doctorId
LEFT OUTER JOIN dbo.mDJSpecialty AS sp WITH (NOLOCK)
ON mdad.specialId = sp.specialId
LEFT OUTER JOIN dbo.mDJDoctorsSpecialtyAbove AS mdad2 WITH (NOLOCK)
ON md.DoctorID = mdad2.doctorId
LEFT OUTER JOIN dbo.mDJSpecialtyAbove AS ab WITH (NOLOCK)
ON mdad2.SpecialtyAboveId = ab.SpecialtyAboveId
WHERE cp.ProfileISOnline = 1
order by cid, LastReplyDateInsert
its take 200 ms in sql 2017 and take 1038ms in sql 2012
update 3:
this i my execution plans xml for 2012 and 2017
2012 and 2017 execution plans
update 4:
server config
The SQL Server 2012 database is missing an index (compared to SQL Server 2017) on [dbo].[Core_Profiles]
For SQL Server 2012, there is no index on [dbo].[Core_Profiles].Userid and although the query has only 52 distinct userids, it scans the full [Core_Profiles] table (~0.5mil rows) to hash match it with 52 rows and return: 52 rows (the scale diff is considerable).
For SQL Server 2017, there is the [missing_index_9245_9243] (which also includes the ProfilePicture{?}). Instead of half million table scan, it performs a 52 rows/times loops join. There is still a keylookup to [Core_Profiles] for the retrieval of ProfileDateInserted.
The first thing to try, would be to create an index on [dbo].[Core_Profiles].Userid. Whether you choose to include the ProfilePicture is a bit irrelevant to the particular query since a key lookup is performed anyway.
A side suggestion would be to create indexes on the #temp tables. Also the retrieval of the mDJDoctorsSpeciality & mDJDoctorsSpecialtyAbove (the set of 4 table outer-joins) is used twice in the batch. Wouldn't the first execution satisfy the second(returned resultset?), so why not store it and use the #temp table instead of querying the tables again (caveat: have not looked at the query logic/model at all, only at the plans) . Most likely, the queries could be nested (inner joins), something like:
from mDJDoctors [rd] WITH (NOLOCK)
left join
(
dbo.[mDJDoctorsSpeciality] [rs] WITH (NOLOCK)
join dbo.mDJSpecialtyCategory [rca] WITH (NOLOCK) on rca.SpecialtyId = rs.specialId and rca.CategoryId in (4,8,840,779,10,813,3,18,7,918)
) on rd.DoctorID = rs.doctorId
left join
(
dbo.mDJDoctorsSpecialtyAbove [ars] WITH (NOLOCK)
join dbo.mDJSpecialtyAboveCategory [arca] WITH (NOLOCK) on arca.AboveSpecialtyId = ars.SpecialtyAboveId and arca.CategoryId in (4,8,840,779,10,813,3,18,7,918)
) on ars.doctorId = rd.DoctorID
where rs.doctorId is not null or rd.DoctorID is not null
on a second thought, isn't this a union?
(
DoctorId from dbo.mDJSpecialtyCategory....
union
DoctorId from dbo.mDJSpecialtyAboveCategory....
) as X
join mDJDoctors [rd] on DoctorId....
If I would be in your situation I would first look at the differences between the 2 instances; there are a lot of things that can impact performance of a query, just looking at the code is not enough in your case (running the same query on 2 separate instances).
So, take a look at the memory allocated to each instance, the settings (MAXDOP and CTP are a good start), are they running on the same machine? If not, are there any significant HW differences? Is there more usage (other people or applications running queries) of your SQL Server 2017 than your SQL Server 2012? Do you have the same amount of data on both servers? Are your indexes the same and are they maintained on both instances (could it be that you have a high level of fragmentation on your SQL Server 2017)? Also, take a look at the execution plans to see if they are identical on both servers.
I know it seems like a lot of questions, but without knowing the full context is hard to answer; any of the above could be the culprit for the slowness.

Sql server T SQL 2014 (Reusing same query)

I have a query which joins 3,4 tables(like select * from table1 join table2 join table3) and the same query is used in my stored procedure multiple time. Is there any way to keep that query in one place and i can use it in my entire stored procedure(with in a single stored procedure)?
SELECT p.PK_PatientID
FROM Patients p
INNER JOIN PT ON e.FK_PatientID = p.PK_PatientID
INNER JOIN PN n ON n.FK_PatientEncounterID = e.PK_PatientEncounterID
INNER JOIN BP ep ON ep.FK_PatientEncounterID = e.PK_PatientEncounterID
INNER JOIN SOP pd ON ep.FK_SharedProcedureDetailID = pd.PK_SharedOfficeProcedureDetailID
INNER JOIN CQM vs ON pd.FK_SharedProcedureCode = vs.Code
This is the query i want to use it in my procedure multiple time
You could use Temp table or Table variable and insert data into it,if you are using the query multiple times
If you don't want to use temp tables or tables variables for reasons unknown,you could use a view
You can store your select query in a view:
CREATE VIEW vW_Example
AS
SELECT p.PK_PatientID
FROM Patients p
INNER JOIN PT ON e.FK_PatientID = p.PK_PatientID
INNER JOIN PN n ON n.FK_PatientEncounterID = e.PK_PatientEncounterID
INNER JOIN BP ep ON ep.FK_PatientEncounterID = e.PK_PatientEncounterID
INNER JOIN SOP pd ON ep.FK_SharedProcedureDetailID = pd.PK_SharedOfficeProcedureDetailID
INNER JOIN CQM vs ON pd.FK_SharedProcedureCode = vs.Code
Then you can call the view like this:
SELECT * FROM vW_Example

How to rewrite legacy join syntax *= in SQL Server

I am trying to rewrite legacy join syntax with new standards.
SELECT count(*)
FROM es_dbo.tablTypes t
,es_dbo.tablReg r
,es_dbo.tabl_PRGandCLI p
WHERE t.ClientType *= r.ClientType
AND p.ID IN (
SELECT DISTINCT ClientID
FROM esinet_dbo.tablReG
)
AND t.ClientType IN (#intClientType)
Here is what I am trying.
SELECT count(*)
FROM es_dbo.tablTypes t
LEFT JOIN es_dbo.tablReg r ON t.ClientType = r.ClientType
LEFT JOIN es_dbo.tabl_PRGandCLI p ON p.ID IN (
SELECT DISTINCT ClientID
FROM es_dbo.tablReG
)
I am getting same no of records whether I use LEFT JOIN or INNER JOIN in 2nd part of query. Can anyone explain
Try the following:
SELECT count(*)
FROM es_dbo.tablTypes t
left join es_dbo.tablReg r on t.ClientType = r.ClientType
WHERE t.ClientType IN (#intClientType)
EXISTS (SELECT 1 FROM esinet_dbo.tablReG p WHERE r.ClientID = p.ID)
1) I assumed #intClientType is a scalar value, so no need for IN
2) removed DISTINCT and subquery as you check for existence. EXISTS should be faster as it involves finding the first element, rather than doing some sorting for DISTINCT.
3) *= was replaced with LEFT JOIN, based on discussion from here.
It is neither inner join nor left join according to query it seems like cross join so you can use following query:
SELECT count(*)
FROM es_dbo.tablTypes t
LEFT JOIN es_dbo.tablReg r ON t.ClientType = r.ClientType,
es_dbo.tabl_PRGandCLI p WHERE p.ID IN (
SELECT DISTINCT ClientID
FROM es_dbo.tablReG
)

What are other ways to get this result set

What are other ways to get the same result set
I am using 4 tables
SELECT * FROM Terminal
SELECT * FROM Customer
SELECT * FROM Contract
SELECT * FROM ExternalTable
Data in these 4 tables are as below:
The query that i've written is
select ex.TerminalName,ex.CustomerName from ExternalTable ex
except
select t.TerminalName,ct.CustomerName from [Contract] c
inner join Terminal t on t.TerminalID=c.TerminalID
inner join Customer ct on ct.CustomerId=c.CustomerId
Which gets the below result
So just curious to know what are the other ways to get this same result
I'm not really what you are looking for, but here is another way to write the query that should produce the same results:
SELECT
ex.TerminalName,
ex.CustomerName
FROM
ExternalTable ex
WHERE
NOT EXISTS( SELECT
NULL
FROM
[Contract] c
INNER JOIN
Terminal t on t.TerminalID = c.TerminalID
INNER JOIN
Customer ct on ct.CustomerId = c.CustomerId
WHERE
t.TerminalName = ex.TerminalName
AND
ex.CustomerName = ct.CustomerName
)
This ought to be equivalent:
select ex.TerminalName,ex.CustomerName
from
ExternalTable ex
left outer join
(
[Contract] c
inner join Terminal t
on t.TerminalID=c.TerminalID
inner join Customer ct
on ct.CustomerId=c.CustomerId
)
on ex.TerminalName = t.TerminalName and ex.CustomerName = ct.CustomerName
where t.TerminalName is null and ct.CustomerName is null
You should note that except returns distinct results and to be strictly equivalent I would need to specify select distinct.
Here is a self-contained example that shows two alternatives.
One uses NOT IN, and the other uses a LEFT OUTER JOIN, but they should be equivalent.
--set up temp tables with dummy data to replicate the issue
declare #Terminal table(terminalid int,terminalname nvarchar(100));
insert into #Terminal select 1,'Terminal1' union select 2,'Terminal2'
declare #Customer table(customerid int,customername nvarchar(100));
insert into #Customer select 1,'Customer1' union select 2,'Customer2';
declare #Contract table(contractid int,terminalid int,customerid int,contractname nvarchar(100));
insert into #Contract select 1,1,1,'Contract1';
declare #ExternalTable table(externalid int,terminalname nvarchar(100),customername nvarchar(100),contractname nvarchar(100));
insert into #ExternalTable select 1,'Terminal1','Customer1','Contract1' union select 2,'Terminal2','Customer1','Contract1'
--SELECT * FROM #Terminal
--SELECT * FROM #Customer
--SELECT * FROM #Contract
--SELECT * FROM #ExternalTable
--goal: show records that are in the external table, but are not fully linked in the other tables
--original
select ex.TerminalName,ex.CustomerName from #ExternalTable ex
except
select t.TerminalName,ct.CustomerName from #Contract c
inner join #Terminal t on t.TerminalID=c.TerminalID
inner join #Customer ct on ct.CustomerId=c.CustomerId
--revised (left outer join method)
select et.TerminalName,et.CustomerName
from
#ExternalTable et
left join (
select ex.externalid
from
#Contract c
inner join #Terminal t on t.TerminalID=c.TerminalID
inner join #Customer ct on ct.CustomerId=c.CustomerId
inner join #ExternalTable ex on ex.terminalname = t.terminalname and ex.customername = ct.customername
) excludes on excludes.externalid = et.externalid
where excludes.externalid is null
--revised ("not in" method)
select et.TerminalName,et.CustomerName
from
#ExternalTable et
where et.externalid not in(
select ex.externalid
from
#Contract c
inner join #Terminal t on t.TerminalID=c.TerminalID
inner join #Customer ct on ct.CustomerId=c.CustomerId
inner join #ExternalTable ex on ex.terminalname = t.terminalname and ex.customername = ct.customername
)

Select only columns from joined tables from CTE

The following is my CTE:
;WITH CTE AS
(SELECT O.*, E.Num, E.Amount
FROM OData O
INNER JOIN Equip E
ON O.Name = E.Name)
SELECT * FROM CTE -- gives results I want to join to
The following is the query that I want to SELECT from (and only use this SELECT statement for my query results:
SELECT
MU.Type
,MU.Num
,MU.MTBUR
,MF.MTBF
,MU.Hours
,MF.Hours
FROM
MUType_Stage MU
INNER JOIN
MFType_Stage MF
ON
MU.Type = MF.Type
AND
MU.Num = MF.Num
-- Need do JOIN to CTE right here
INNER JOIN
Status_STAGE S
ON
MU.Nu = S.Part
LEFT OUTER JOIN
RCN N
ON
N.Name = R.Part
LEFT OUTER JOIN
Repair RR
ON
R.ACSS_Name = RR.Name
So basically I need to JOIN to the CTE inside the SELECT query in which I want the results.
OR ALTERNATIVELY Uses this select statement to join to the CTE but only what the selected columns from the second select statement
Try this syntax
WITH CTE
AS (SELECT O.*,
E.Num,
E.Amount
FROM OData O
INNER JOIN Equip E
ON O.Name = E.Name)
SELECT MU.Type,
MU.Num,
MU.MTBUR,
MF.MTBF,
MU.Hours,
MF.Hours
FROM MUType_Stage MU
INNER JOIN MFByACType_Stage MF
ON MU.Type = MF.Type
AND MU.Num = MF.Num
INNER JOIN CTE C --- JOIN HERE as like other tables
ON C.Num = MF.Num
INNER JOIN Status_STAGE S
ON MU.Nu = S.Part
LEFT OUTER JOIN RCN N
ON N.Name = R.Part
LEFT OUTER JOIN Repair RR
ON R.ACSS_Name = RR.Name

Resources