How Can I speed up Multiple Left Outer Join, - sql-server

this is MS-SQL Query
I have use Lots of Left Inner Join.
NumberOfRow = about 300,000 Rows
Result time is about 4sec.
How can I speed up this query?.
PK is Id, Indexed RegionID
DECLARE #Location TABLE
(
Id int IDENTITY(1,1) PRIMARY KEY not null,
RegionID int ,
RegionType int ,
SubClass int ,
RegionName nvarchar(255) ,
RegionNameLong nvarchar(512) ,
ParentRegionID int
)
INSERT INTO #Location (RegionID, RegionType, SubClass, RegionName, RegionNameLong, ParentRegionID)
SELECT ORIGIN.RegionID AS RegionID
,ORIGIN.RegionType AS RegionType
,ORIGIN.SubClass AS SubClass
,REFER.RegionName AS RegionName
,REFER.RegionNameLong AS RegionNameLong
,ORIGIN.ParentRegionID AS ParentRegionID
FROM Location_en_US AS ORIGIN
INNER JOIN Location_ko_KR AS REFER ON ORIGIN.RegionID = REFER.RegionID
SELECT
TOP 10
EN_1.RegionID, EN_1.RegionName, EN_2.RegionID, EN_2.RegionName, EN_3.RegionID, EN_3.RegionName, EN_4.RegionID, EN_4.RegionName, EN_5.RegionID, EN_5.RegionName, EN_6.RegionID, EN_6.RegionName
FROM #Location AS EN_1
LEFT OUTER JOIN #Location AS EN_2 ON EN_1.ParentRegionID = EN_2.RegionID
LEFT OUTER JOIN #Location AS EN_3 ON EN_2.ParentRegionID = EN_3.RegionID
LEFT OUTER JOIN #Location AS EN_4 ON EN_3.ParentRegionID = EN_4.RegionID
LEFT OUTER JOIN #Location AS EN_5 ON EN_4.ParentRegionID = EN_5.RegionID
LEFT OUTER JOIN #Location AS EN_6 ON EN_5.ParentRegionID = EN_6.RegionID
INNER JOIN RegionType AS RT ON EN_1.RegionType = RT.TypeCode AND RT.LanguageCode = 'en_US'
INNER JOIN SubClass AS SC ON EN_1.SubClass = SC.TypeCode AND SC.LanguageCode = 'en_US'
WHERE EN_1.RegionNameLong LIKE '%SEUOL%'
this is use hierarchyid, but lowest then left outer join
CREATE TABLE dbo.Location_en_US(
Id int IDENTITY(1,1) PRIMARY KEY NOT NULL,
Level hierarchyid NOT NULL,
RegionID int NOT NULL,
RegionType int NOT NULL,
RelativeSignificance nvarchar(3) NULL,
SubClass int NULL,
RegionName nvarchar(255) NOT NULL,
RegionNameLong nvarchar(512) NOT NULL,
ParentRegionID int NULL,
CreatedAt datetime2 NULL DEFAULT (getdate()),
)
SELECT RegionName AS RegionName1,
(SELECT RegionName FROM Location_en_US WHERE Level = Location.Level.GetAncestor(1)) AS Level2,
(SELECT RegionName FROM Location_en_US WHERE Level = Location.Level.GetAncestor(2)) AS Level3,
(SELECT RegionName FROM Location_en_US WHERE Level = Location.Level.GetAncestor(3)) AS Level4,
(SELECT RegionName FROM Location_en_US WHERE Level = Location.Level.GetAncestor(4)) AS Level5,
(SELECT RegionName FROM Location_en_US WHERE Level = Location.Level.GetAncestor(5)) AS Level6
FROM Location_en_US AS Location WHERE RegionNameLong LIKE '%SEOUL%'

Should use HierarhyId tecnique for operating tree-like structures.
Build index for ParentRegionID, RegionID columns.
Also, None of the listed join tables are picked to query result set.
Best wishes!

You're joining on ParentRegionID and RegionID. An index on either column might help. You probably have to switch from a table variable #Location to a temporary table #Location before you can add an index.
After adding the index, examine the "Query Plan" from the Query menu to see how this helps.

Add an index on regionId.
You can't explicitly add indexes to a table variable, so use this trick:
DECLARE #Location TABLE
(
Id int IDENTITY(1,1) PRIMARY KEY not null,
RegionID int ,
RegionType int ,
SubClass int ,
RegionName nvarchar(255) ,
RegionNameLong nvarchar(512) ,
ParentRegionID int,
UNIQUE (regionId, id)
)
Also, your left joins don't do anything except probably multiplying the same record. Are you sure your query is correct?

Related

Update table with stored procedure without null

I tried to implement procedure which main goal is to store data into new separate table.First I create table which I want to populate with data
CREATE TABLE dbo.CustomerReportLogs (
ID int IDENTITY (1,1) NOT NULL,
CustomerFullName NVARCHAR(100) NULL,
LocationName NVARCHAR(100) NULL,
Amount decimal (18,2) NULL,
Currency NVARCHAR (20) NULL,
EmployeeId int NULL,
ValidFrom date NULL,
ValidTo date NULL,
CONSTRAINT PK_ID_CustomerReportLogs PRIMARY KEY CLUSTERED (
ID ASC
))
GO
So next step is how to populate this table with stored procedure. I order to do this I wrote this lines of code:
CREATE OR ALTER PROCEDURE dbo.procedure2 (#CustomerId int, #validFrom date, #ValidTo date,#EmployeeID int)
AS
BEGIN
SELECT CONCAT(c.FirstName, ' ', c.LastName) AS CustomerFullName, lo.Name as LocationName,acd.Amount as Amount,cu.Name as Currency,acc.EmployeeId,#validFrom as ValidFrom,#ValidTo as ValidTo
FROM dbo.Customer as c
INNER JOIN dbo.Account AS acc ON acc.CurrencyId=c.Id
INNER JOIN dbo.AccountDetails AS acd ON acd.AccountId=acc.Id
INNER JOIN dbo.Currency AS cu ON cu.id=acc.CurrencyId
INNER JOIN dbo.Location as lo ON lo.Id=acd.LocationId
INNER JOIN dbo.Employee AS emp ON emp.ID=acc.EmployeeId
WHERE acc.CustomerId=#CustomerId and acd.TransactionDate between #validFrom and #ValidTo and acc.EmployeeId=#EmployeeID
DECLARE #CustomerFullName NVARCHAR(100)
DECLARE #LocationName NVARCHAR(100)
DECLARE #Amount decimal (18,2)
DECLARE #Currency NVARCHAR (20)
INSERT INTO dbo.CustomerReportLogs(CustomerFullName,LocationName,Amount,Currency,EmployeeId,ValidFrom,ValidTo)
VALUES (#CustomerFullName,#LocationName,#Amount,#Currency,#EmployeeId,#ValidFrom,#ValidTo)
SELECT #CustomerFullName as CustomerFullName,#LocationName AS LocationName,#Amount AS Amount,#Currency as Currency,
#EmployeeId as EmployeeId,#ValidFrom as ValidFrom ,#ValidTo as ValidTo
END
GO
Above line code create stored procudure but now new problem arise namely when I try to execute first whit this command
EXEC dbo.procedure2 #CustomerId=8,#validFrom='2019.01.25', #ValidTo='2019.03.01', #EmployeeID=8
So this procedure can't update properly dbo.CustomerReportLogs so I got some results with null and some with values.Output you can see on pic below:
[![enter image description here][1]][1]
So can anybody help me how to update this table properly not with null
CREATE OR ALTER PROCEDURE dbo.procedure2 (
#CustomerId INT
,#validFrom DATE
,#ValidTo DATE
,#EmployeeID INT
)
AS
BEGIN
INSERT INTO dbo.CustomerReportLogs (
CustomerFullName
,LocationName
,Amount
,Currency
,EmployeeId
,ValidFrom
,ValidTo
)
OUTPUT INSERTED.[CustomerFullName],INSERTED.[LocationName],INSERTED.[Amount],INSERTED.[Currency],INSERTED.[EmployeeId],INSERTED.[ValidFrom] ,INSERTED.[ValidTo]
SELECT CONCAT (c.FirstName,' ',c.LastName) AS CustomerFullName
,lo.Name AS LocationName
,acd.Amount AS Amount
,cu.Name AS Currency
,acc.EmployeeId
,#validFrom AS ValidFrom
,#ValidTo AS ValidTo
FROM dbo.Customer AS c
INNER JOIN dbo.Account AS acc ON acc.CurrencyId = c.Id
INNER JOIN dbo.AccountDetails AS acd ON acd.AccountId = acc.Id
INNER JOIN dbo.Currency AS cu ON cu.id = acc.CurrencyId
INNER JOIN dbo.Location AS lo ON lo.Id = acd.LocationId
INNER JOIN dbo.Employee AS emp ON emp.ID = acc.EmployeeId
WHERE acc.CustomerId = #CustomerId
AND acd.TransactionDate BETWEEN #validFrom
AND #ValidTo
AND acc.EmployeeId = #EmployeeID
END

How to return table's details from another table colum

I'm trying to make a stored procedure that takes in a parameter of a Department name and
returns all the employee's details for the specific department.
If there's no department name passed in, it has to return all the employees.
I'm trying the above code but doesn't display the expected result
CREATE PROCEDURE SelectEmployee #name varchar(50)
AS
Select * from Employees WHERE name = #name
GO;
Table structure
[Department]
(
[departmentID] INT IDENTITY (1, 1) NOT NULL PRIMARY KEY,
[name] VARCHAR(50) NOT NULL,
[costCentreID] INT FOREIGN KEY REFERENCES CostCentre(costCentreID)
);
[Employees]
(
[employeeNo] INT IDENTITY (1, 1) NOT NULL PRIMARY KEY,
[lastName] VARCHAR(50) NOT NULL,
[firstName] VARCHAR(50) NOT NULL,
[gender] CHAR NOT NULL,
[IDNumber] VARCHAR(20) NOT NULL,
[salaryLevelID] INT,
[departmentID] INT FOREIGN KEY REFERENCES Department(departmentID)
);
As Larnu suggested, you need to explicitly join your tables together to get results from both where the join criteria is met.
In your case, this would look something like this:
select e.employeeNo,
e.lastName,
e.firstName,
e.gender,
e.IDNumber,
e.salaryLevelID,
e.departmentID,
d.name as departmentName
from Department as d
join Employees as e
on d.departmentID = e.departmentID
where d.name = #name
However, the additional logic of returning all employees when there is no #name value provided will require you to make the parameter optional and the script within the stored procedure able to handle both where a value is provided and where it isn't.
If you want to bring back all employees if no name is given you can modify your procedure to accept an optional parameter.
The query you provided had an unnecessary JOIN in the ELSE statement given you wanted to return all employees. So consider the following:
CREATE PROCEDURE SelectEmployee #name varchar(50) = ''
AS
IF (#name <> '')
BEGIN
select e.employeeNo,
e.lastName,
e.firstName,
e.gender,
e.IDNumber,
e.salaryLevelID,
e.departmentID,
d.name as departmentName
from Department as d
join Employees as e
on d.departmentID = e.departmentID
where d.name = #name
END
ELSE
BEGIN
select e.employeeNo,
e.lastName,
e.firstName,
e.gender,
e.IDNumber,
e.salaryLevelID,
e.departmentID,
'ALL' as departmentName
from Employees e
END
So you could run it like this to get all Payroll Employees:
EXEC SelectEmployee 'Payroll'
OR like this to just get all employees
EXEC SelectEmployee

Can't put CTE result to temporary table

I got a SP where I need to put CTE result to a temporary table so I can use the temporary table later on in SP. Since I am new so I am experiencing difficulty. Please help me out.
CREATE TABLE #TempTable1 (
tmp_id INT NULL,
tmp_parent_id INT NULL,
temp_level VARCHAR(50) NULL,
temp_order VARCHAR(50) NULL,
temp_promoter_ID INT NULL
);
DECLARE #promoterid INT = (
SELECT
p.promoterid
FROM dbo.Promoters p
INNER JOIN dbo.UserProfile u
ON u.UserId = p.UserProfileId
WHERE u.UserName = #Username
);
;WITH Empl_Tab (Id, ParentId, LEVEL, [Order], promoterid) AS (
SELECT
promoters.UserProfileId AS ID,
promoters.Level1 AS ParentID,
0 AS LEVEL,
CONVERT([VARCHAR](MAX), promoters.PromoterId) AS [Order],
promoters.PromoterId
FROM promoters
WHERE Promoters.PromoterId = #promoterid
UNION ALL
SELECT
p.UserProfileId AS ID,
p.Level1 AS ParentID,
Empl_Tab.LEVEL + 1 AS LEVEL,
Empl_Tab.[Order] + CONVERT([VARCHAR](30), p.PromoterId) AS [Order],
p.PromoterId
FROM Promoters p
INNER JOIN Empl_Tab
--inner join dbo.UserProfile u on u.UserId= Promoters.UserProfileId
ON Empl_Tab.promoterid = p.Level1
--where p.Active!=2
)
--select Id, ParentId, LEVEL,[Order],promoterid from Empl_Tab
INSERT INTO #TempTable1 --(tmp_id, tmp_parent_id, temp_level, temp_order, temp_promoter_ID )
SELECT *
FROM Empl_Tab;
Now I like to put the Emp1_Tab result to the temporary table and like to use the temporary table data later on in this same SP.
Procedure TeamCustomersListNew, Line 42 String or binary data would be truncated.
The above error message states that one of the values you're inserting exceeds the max length. The temp_order column of the temp table only allows for 50 characters. You may want to increase it or use VARCHAR(MAX) instead:
CREATE TABLE #TempTable1 (
tmp_id INT NULL,
tmp_parent_id INT NULL,
temp_level INT NULL,
temp_order VARCHAR(MAX) NULL,
temp_promoter_ID INT NULL
);
Additionally, temp_level should be INT.

Need to speed up SQL Server SP that uses system metadata

Let me apologize in advance for the length of this question. I don't see how to ask it without giving all the definitions.
I've inherited a SQL Server 2005 database that includes a homegrown implementation of change tracking. Through triggers, changes to virtually every field in the database are stored in a set of three tables. In the application for this database, the user can request the history of various items, and what's returned is not just changes to the item itself, but also changes in related tables. The problem is that in some cases, it's painfully slow, and in some cases, the request eventually crashes the application. The client has also reported other users having problems when someone requests history.
The tables that store the change data are as follows:
CREATE TABLE [dbo].[tblSYSChangeHistory](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[date] [datetime] NULL,
[obj_id] [int] NULL,
[uid] [varchar](50) NULL
This table tracks the tables that have been changed. Obj_id is the value that Object_ID() returns.
CREATE TABLE [dbo].[tblSYSChangeHistory_Items](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[h_id] [bigint] NOT NULL,
[item_id] [int] NULL,
[action] [tinyint] NULL
This table tracks the items that have been changed. h_id is a foreign key to tblSYSChangeHistory. item_id is the PK of the changed item in the specified table. action indicates insert, delete or change.
CREATE TABLE [dbo].[tblSYSChangeHistory_Details](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[i_id] [bigint] NOT NULL,
[col_id] [int] NOT NULL,
[prev_val] [varchar](max) NULL,
[new_val] [varchar](max) NULL
This table tracks the individual changes. i_id is a foreign key to tblSYSChangeHistory_Items. col_id indicates which column was changed, and prev_val and new_val indicate the original and new values for that field.
There's actually a fourth table that supports this architecture. tblSYSChangeHistory_Objects maps plain English descriptions of operations to particular tables in the database.
The code to look up the history for an item is incredibly convoluted. It's one branch of a very long SP. Relevant parameters are as follows:
#action varchar(50),
#obj_id bigint = 0,
#uid varchar(50) = '',
#prev_val varchar(MAX) = '',
#new_val varchar(MAX) = '',
#start_date datetime = '',
#end_date datetime = ''
I'm storing them to local variables right away (because I was able to significantly speed up another SP by doing so):
declare #iObj_id bigint,
#cUID varchar(50),
#cPrev_val varchar(max),
#cNew_val varchar(max),
#tStart_date datetime,
#tEnd_date datetime
set #iObj_id = #obj_id
set #cUID = #uid
set #cPrev_val = #prev_val
set #cNew_val = #new_val
set #tStart_date = #start_date
set #tEnd_date = #end_date
And here's the code from that branch of the SP:
create table #r (obj_id int, item_id int, l tinyint)
create clustered index #ri on #r (obj_id, item_id)
insert into #r
select object_id(obj_name), #iObj_id, 0
from dbo.tblSYSChangeHistory_Objects
where obj_type = 'U' and descr = cast(#cPrev_val AS varchar(150))
declare #i tinyint, #cnt int
set #i = 1
while #i <= 4
begin
insert into #r
select obj_id, item_id, #i
from dbo.vSYSChangeHistoryFK a with (nolock)
where exists (select null from #r where obj_id = a.rel_obj_id and item_id = a.rel_item_id and l = #i - 1)
and not exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id)
set #cnt = ##rowcount
insert into #r
select rel_obj_id, rel_item_id, #i
from dbo.vSYSChangeHistoryFK a with (nolock)
where object_name(obj_id) not in (<this is a list of particular tables in the database>)
and exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id and l between #i - 1 and #i)
and not exists (select null from #r where obj_id = a.rel_obj_id and item_id = a.rel_item_id)
set #i = case #cnt + ##rowcount when 0 then 100 else #i + 1 end
end
select date, obj_name, item, [uid], [action],
pkey, item_id, id, key_obj_id into #tCH_R
from dbo.vSYSChangeHistory a with (nolock)
where exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id)
and (#cUID = '' or uid = #cUID)
and (#cNew_val = '' or [action] = #cNew_val)
declare ch_item_cursor cursor for
select distinct pkey, key_obj_id, item_id
from #tCH_R
where item = '' and pkey <> ''
open ch_item_cursor
fetch next from ch_item_cursor
into #cPrev_val, #iObj_id, #iCol_id
while ##fetch_status = 0
begin
set #SQLStr = 'select #val = ' + #cPrev_val +
' from ' + object_name(#iObj_id) + ' with (nolock)' +
' where id = #id'
exec sp_executesql #SQLStr,
N'#val varchar(max) output, #id int',
#cNew_val output, #iCol_id
update #tCH_R
set item = #cNew_val
where key_obj_id = #iObj_id
and item_id = #iCol_id
fetch next from ch_item_cursor
into #cPrev_val, #iObj_id, #iCol_id
end
close ch_item_cursor
deallocate ch_item_cursor
select date, obj_name,
cast(item AS varchar(254)) AS item,
uid, [action],
cast(id AS int) AS id
from #tCH_R
order by id
return
As you can see, the code uses a view. Here's that definition:
ALTER VIEW [dbo].[vSYSChangeHistoryFK]
AS
SELECT i.obj_id, i.item_id, c1.parent_object_id AS rel_obj_id, i2.item_id AS rel_item_id
FROM dbo.vSYSChangeHistoryItemsD AS i INNER JOIN
sys.foreign_key_columns AS c1 ON c1.referenced_object_id = i.obj_id AND c1.constraint_column_id = 1 INNER JOIN
dbo.vSYSChangeHistoryItemsD AS i2 ON c1.parent_object_id = i2.obj_id INNER JOIN
dbo.tblSYSChangeHistory_Details AS d1 ON d1.i_id = i.min_id AND d1.col_id = c1.referenced_column_id INNER JOIN
dbo.tblSYSChangeHistory_Details AS d1k ON d1k.i_id = i2.min_id AND d1k.col_id = c1.parent_column_id AND ISNULL(d1.new_val,
ISNULL(d1.prev_val, '')) = ISNULL(d1k.new_val, ISNULL(d1k.prev_val, '')) --LEFT OUTER JOIN
UNION ALL
SELECT i0.obj_id, i0.item_id, c01.parent_object_id AS rel_obj_id, i02.item_id AS rel_item_id
FROM dbo.vSYSChangeHistoryItemsD AS i0 INNER JOIN
sys.foreign_key_columns AS c01 ON c01.referenced_object_id = i0.obj_id AND c01.constraint_column_id = 1 AND col_name(c01.referenced_object_id,
c01.referenced_column_id) = 'ID' INNER JOIN
dbo.vSYSChangeHistoryItemsD AS i02 ON c01.parent_object_id = i02.obj_id INNER JOIN
dbo.tblSYSChangeHistory_Details AS d01k ON i02.min_id = d01k.i_id AND d01k.col_id = c01.parent_column_id AND ISNULL(d01k.new_val,
d01k.prev_val) = CAST(i0.item_id AS varchar(max))
And finally, that view uses one more view:
ALTER VIEW [dbo].[vSYSChangeHistoryItemsD]
AS
SELECT h.obj_id, m.item_id, MIN(m.id) AS min_id
FROM dbo.tblSYSChangeHistory AS h INNER JOIN
dbo.tblSYSChangeHistory_Items AS m ON h.id = m.h_id
GROUP BY h.obj_id, m.item_id
Working with the Profiler, it appears that view vSYSChangeHistoryFK is the big culprit, and my testing suggests that the particular problem is in the join between the two copies of vSYSChangeHistoryItemsD and the foreign_key_columns table.
I'm looking for any ideas on how to give acceptable performance here. The client reports sometimes waiting as much as 15 minutes without getting results. I've tested up to nearly 10 minutes with no result in at least one case.
If there were new language elements in 2008 or later that would solve this, I think the client would be willing to upgrade.
Thanks.
Wow that's a mess. Your big gain should be in removing the cursor. I see 'where exists' - that's nice and efficient b/c as soon as it finds one match it aborts. And I see 'where not exists' - by definition that has to scan everything. Is it finding the top 4? You can do better with using ROW_NUMBER() OVER (PARTITON BY [whatever makes it unique] ORDER BY [whatever your id is]. It's hard to tell. select object_id(obj_name), #iObj_id, 0 makes it seem like only the #i=1 loop actually does anything (?)
If that is what it's doing, you could write it as
SELECT * from
(
select ROW_NUMBER() OVER (PARTITION BY obj_id ORDER BY item_id desc) as Row,
obj_id, item_id
FROM bo.vSYSChangeHistoryFK a with (nolock)
where obj_type = 'U' and descr = cast(#cPrev_val AS varchar(150))
) paged
where Row between 1 and 4
ORDER BY Row
A DBA level change that could help would be to set up a partitioning scheme based on date. Roll over to a new partition every so often. Put the old partitions on different disks. Most queries may only need to hit the recent partition, which will be say 1/5th the size that it used to be, making it much faster without changing anything else.
Not a full answer, sorry. That mess would take hours to parse

SQL how to get an equality comparator on a list of ints

I'm writing an SQL query as follows:
ALTER proc [dbo].[Invoice_GetHomePageInvoices] (
#AreaIdList varchar(max)
, #FinancialYearStartDate datetime = null
, #FinancialYearEndDate datetime = null
) as
set nocount on
select *
from Invoice i
left outer join Organisation o on i.OrganisationId = o.Id
left outer join Area a on i.AreaId = a.Id
where i.InvoiceDate BETWEEN #FinancialYearStartDate AND #FinancialYearEndDate
The #AreaIdList parameter is going to be in the format "1,2,3" etc.
I'm wanting to add a line which will only return invoices who have area id equal to any of the ids in #AreaIdList.
I know how to do a statement if it was on areaId to search on ie. where i.AreaId == areaId problem is now I have this list I got to compare for every area Id in #AreaIdList.
Can anybody tell me how you would go about this?
Unpack your ID list to a table and use where AreadID in (select ID from ...)
ALTER proc [dbo].[Invoice_GetHomePageInvoices] (
#AreaIdList varchar(max)
, #FinancialYearStartDate datetime = null
, #FinancialYearEndDate datetime = null
) as
set nocount on
set #AreaIdList = #AreaIdList+','
declare #T table(ID int primary key)
while len(#AreaIdList) > 1
begin
insert into #T(ID) values (left(#AreaIdList, charindex(',', #AreaIdList)-1))
set #AreaIdList = stuff(#AreaIdList, 1, charindex(',', #AreaIdList), '')
end
select *
from Invoice i
left outer join Organisation o on i.OrganisationId = o.Id
left outer join Area a on i.AreaId = a.Id
where i.InvoiceDate BETWEEN #FinancialYearStartDate AND #FinancialYearEndDate and
i.AreadID in (select ID from #T)

Resources