Slow SQL Update using sub query - sql

Hi all … Wonder if anyone out there can help me with this one please.
I am running a query to update product categories against sales lines and need to back file a few million records so I wrote the query below to run for a specific order ID
DECLARE #ID INT
SET #ID = 659483
UPDATE [TradeSpace].[TradeSpace].[dbo].[SalesLine]
SET [ProductCategory] = [curSync].[pc_Cat]
FROM (SELECT [SC_ID],
[pc_cat]
FROM [MW_MereSys].[dbo].[MWSLines]
INNER
JOIN [MW_MereSys].[dbo].[MWProductCats]
ON [MWSLines].[pc_catref] = [MWProductCats].[pc_catref]
WHERE [sh_id] = #ID
) AS [curSync]
WHERE [SalesLine].[slID] = [curSync].[sc_id]
AND [salesline].[soid] = #ID
The sub SELECT runs in less than one second but the update has yet to finished (have left it for an hour at most). Indexes exist for [slID] and [soid] .. a manual update for one line takes less than one seconds but run like this (10 lines) is desperately slow.
Does anybody have any clues please. I've written plenty of queries like this and never had a problem … stumped :(

Your query rewritten with no changes:
UPDATE s SET
ProductCategory = curSync.pc_Cat
FROM TradeSpace.TradeSpace.dbo.SalesLine s
INNER JOIN
(
SELECT [SC_ID], [pc_cat]
FROM [MW_MereSys].[dbo].[MWSLines] l
INNER JOIN [MW_MereSys].[dbo].[MWProductCats] c ON l.[pc_catref] = c.[pc_catref]
WHERE [sh_id] = #ID
) AS [curSync]
on s.[slID] = [curSync].[sc_id]
WHERE s.[soid] = #ID
Are your sure everything is correct here? That single row from SalesLine always matches only one row from subquery?
Try this then. Will fail if this is not true. Original query would silently update same row with different values in same situation.
UPDATE s SET
ProductCategory = (
SELECT [pc_cat]
FROM [MW_MereSys].[dbo].[MWSLines] l
INNER JOIN [MW_MereSys].[dbo].[MWProductCats] c ON l.[pc_catref] = c.[pc_catref]
WHERE [sh_id] = #ID
AND [sc_id] = s.[slID]
)
FROM TradeSpace.TradeSpace.dbo.SalesLine s
WHERE s.[soid] = #ID
And please check estimated execution plan. Does it hit indexes?

We need other detail like a I mention in comments.
Your update is slow because of very high cardinality estimate when update table is join with Sub query result.
It may be because of wrong join and where predicate.
you can put the sub query result in #Temp table and try.Also you can create same index in #temp table.
DECLARE #ID INT
SET #ID = 659483
create #temp table([SC_ID] int,[pc_cat] int)
insert into #temp
SELECT [SC_ID],
[pc_cat]
FROM [MW_MereSys].[dbo].[MWSLines]
INNER JOIN [MW_MereSys].[dbo].[MWProductCats]
ON [MWSLines].[pc_catref] = [MWProductCats].[pc_catref]
WHERE [sh_id] = #ID
UPDATE SalesLine
SET [ProductCategory] = [curSync].[pc_Cat]
FROM [TradeSpace].[TradeSpace].[dbo].[SalesLine] as SalesLine
inner join #temp AS [curSync]
WHERE [SalesLine].[slID] = [curSync].[sc_id]
AND [salesline].[soid] = #ID
drop table #temp

Related

Query / design performance problem: How to get an Id via a 'link'-table in the most efficient way?

See this part from my ERD:
Part from the design
From a reader, I get a RFID which belongs to a wheel. How can I obtain the corresponding Id from the tblProduct-table. I know how to do it with a number of SELECT-statements, but is this the fastest way? I am asking this because I do not have a lot of experience generating speed-efficient QUERY Statements.
On the moment I did create a function to handle this:
CREATE FUNCTION [dbo].ufnGetProductIdFromRFID
(#StationRFID NVARCHAR(20))
RETURNS
INT
AS
BEGIN
DECLARE #ProductId INT = 0
DECLARE #WheelId INT = 0
SELECT #WheelId = [Id] FROM [dbo].[tblWheel] WHERE RFID = #StationRFID
SELECT #ProductId = [ProductId] FROM [dbo].[tblLinkWheelProduct] WHERE WheelId = #WheelId
RETURN #ProductId
END
GO
So i execute two query's.
My question: will a 'JOIN' or some other contruction lead to a more efficient (less executing time) solution? Since the two SELECT-statements are fairly simple and not time-consuming at all I think.....
Thanks already for thinking along with me!
In this way you can get whatever information from tblProduct table
select * from tblProduct p
where exists (
select 1 from tblLinkWheelProduct lwp
inner join tblWheel w
on w.Id = lwp.WheelId
where p.Id = lwp.ProductId and RFID = ?
)
if your intention is to only get productId then,
select productId from tblLinkWheelProduct lwp
where exists (
select 1 from tblWheel w
where w.id = lwp.WheelId
and w.RFID = ?
)
I would join tblWheel to tblLinkWhelProduct to tblProduct. You would get the data with one query. I think it is the best way:
select p.Id from tblWheel w
inner join tblLinkWheelProduct l on w.Id=t.WheelId
inner join tblProduct p on p.Id=l.ProductId

SQL Join taking too much time to run

This query shown below is taking almost 2 hrs to run and I want to reduce the execution time of this query. Any help would be really helpful for me.
Currently:
If Exists (Select 1
From PRODUCTS prd
Join STORE_RANGE_GRP_MATCH srg On prd.Store_Range_Grp_Id = srg.Orig_Store_Range_Grp_ID
And srg.Match_Flag = 'Y'
And prd.Range_Event_Id = srg.LAR_Range_Event_Id
Where srg.Range_Event_Id Not IN (Select distinct Range_Event_Id
From Last_Authorised_Range)
)
I have tried replacing the Not IN clause by Not Exists and Left join but no luck in runtime execution.
What I have used:
If Exists( Select top 1 *
From PRODUCTS prd
Join STORE srg
On prd.Store_Range_Grp_Id = srg.Orig_Store_Range_Grp_ID
And srg.Match_Flag = 'Y'
And prd.Range_Event_Id = srg.LAR_Range_Event_Id
and srg.Range_Event_Id ='45655'
Where NOT EXISTS (Select top 1 *
From Last_Authorised_Range where Range_Event_Id=srg.Range_Event_Id)
)
Product table has 432837 records and the Store table also has almost the same number of records. This table I am creating in the stored procedure itself and then dropping it in the end in the stored procedure.
Create Table PRODUCTS
(
Range_Event_Id int,
Store_Range_Grp_Id int,
Ranging_Prod_No nvarchar(14) collate database_default,
Space_Break_Code nchar(1) collate database_default
)
Create Clustered Index Idx_tmpLAR_PRODUCTS
ON PRODUCTS (Range_Event_Id, Ranging_Prod_No, Store_Range_Grp_Id, Space_Break_Code)
Should I use non clustered index on this table or what all can I do to lessen the execution time? Thanks in advance
First, you don't need top 1 or distinct in exists and in subqueries. But this shouldn't affect performance.
This is the query, slightly re-arranged so I can understand it better:
Select 1
From PRODUCTS prd Join
STORE srg
On prd.Store_Range_Grp_Id = srg.Orig_Store_Range_Grp_ID and
prd.Range_Event_Id = srg.LAR_Range_Event_Id
Where srg.Match_Flag = 'Y'
srg.Range_Event_Id = 45655 and
Where NOT EXISTS (Select 1
From Last_Authorised_Range lar
where lar.Range_Event_Id = srg.Range_Event_Id)
)
Do note that I removed the double quotes around 45655. I presume this column is actually a number. If so, don't confuse yourself and the optimizer by using a string for the comparison.
Then, try indexes. I think the best indexes are:
store(Range_Event_Id, Match_Flag, Orig_Store_Range_Grp_ID, LAR_Range_Event_Id)
products(Store_Range_Grp_Id, Range_Event_Id) (or any index, clustered or otherwise, that starts with these two columns in either order)
Last_Authorised_Range(Range_Event_Id)
From what you describe as the volume of data, your query should not be taking hours. I think indexes can help.

How can I perform the Count function with a where clause?

I have my database setup to allow a user to "Like" or "Dislike" a post. If it is liked, the column isliked = true, false otherwise (null if nothing.)
The problem is, I am trying to create a view that shows all Posts, and also shows a column with how many 'likes' and 'dislikes' each post has. Here is my SQL; I'm not sure where to go from here. It's been a while since I've worked with SQL and everything I've tried so far has not given me what I want.
Perhaps my DB isn't setup properly for this. Here is the SQL:
Select trippin.AccountData.username, trippin.PostData.posttext,
trippin.CategoryData.categoryname, Count(trippin.LikesDislikesData.liked)
as TimesLiked from trippin.PostData
inner join trippin.AccountData on trippin.PostData.accountid = trippin.AccountData.id
inner join trippin.CategoryData on trippin.CategoryData.id = trippin.PostData.categoryid
full outer join trippin.LikesDislikesData on trippin.LikesDislikesData.postid =
trippin.PostData.id
full outer join trippin.LikesDislikesData likes2 on trippin.LikesDislikesData.accountid =
trippin.AccountData.id
Group By (trippin.AccountData.username), (trippin.PostData.posttext), (trippin.categorydata.categoryname);
Here's my table setup (I've only included relevant columns):
LikesDislikesData
isliked(bit) || accountid(string) || postid(string
PostData
id(string) || posttext || accountid(string)
AccountData
id(string) || username(string)
CategoryData
categoryname(string)
Problem 1: FULL OUTER JOIN versus LEFT OUTER JOIN. Full outer joins are seldom what you want, it means you want all data specified on the "left" and all data specified on the "right", that are matched and unmatched. What you want is all the PostData on the "left" and any matching Likes data on the "right". If some right hand side rows don't match something on the left, then you don't care about it. Almost always work from left to right and join results that are relevant.
Problem 2: table alias. Where ever you alias a table name - such as Likes2 - then every instance of that table within the query needs to use that alias. Straight after you declare the alias Likes2, your join condition refers back to trippin.LikesDislikesData, which is the first instance of the table. Given the second one in joining on a different field I suspect that the postid and accountid are being matched on the same row, therefore it should be AND together, not a separate table instance. EDIT reading your schema closer, it seems this wouldn't be needed at all.
Problem 3: to solve you Counts problem separate them using CASE statements. Count will add the number of non NULL values returned for each CASE. If the likes.liked = 1, then return 1 otherwise return NULL. The NULL will be returned if the columns contains a 0 or a NULL.
SELECT trippin.PostData.Id, trippin.AccountData.username, trippin.PostData.posttext,
trippin.CategoryData.categoryname,
SUM(CASE WHEN likes.liked = 1 THEN 1 ELSE 0 END) as TimesLiked,
SUM(CASE WHEN likes.liked = 0 THEN 1 ELSE 0 END) as TimesDisLiked
FROM trippin.PostData
INNER JOIN trippin.AccountData ON trippin.PostData.accountid = trippin.AccountData.id
INNER JOIN trippin.CategoryData ON trippin.CategoryData.id = trippin.PostData.categoryid
LEFT OUTER JOIN trippin.LikesDislikesData likes ON likes.postid = trippin.PostData.id
-- remove AND likes.accountid = trippin.AccountData.id
GROUP BY trippin.PostData.Id, (trippin.AccountData.username), (trippin.PostData.posttext), (trippin.categorydata.categoryname);
Then "hide" the PostId column in the User Interface.
Instead of selecting Count(trippin.LikesDislikesData.liked) you could put in a select statement:
Select AccountData.username, PostData.posttext, CategoryData.categoryname,
(select Count(*)
from LikesDislikesData as likes2
where likes2.postid = postdata.id
and likes2.liked = 'like' ) as TimesLiked
from PostData
inner join AccountData on PostData.accountid = AccountData.id
inner join CategoryData on CategoryData.id = PostData.categoryid
USE AdventureWorksDW2008R2
GO
SET NOCOUNT ON
GO
/*
Default
*/
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
GO
BEGIN TRAN
IF OBJECT_ID('tempdb.dbo.#LikesDislikesData') IS NOT NULL
BEGIN
DROP TABLE #LikesDislikesData
END
CREATE TABLE #LikesDislikesData(
isLiked bit
,accountid VARCHAR(50)
,postid VARCHAR(50)
);
IF OBJECT_ID('tempdb.dbo.#PostData') IS NOT NULL
BEGIN
DROP TABLE #PostData
END
CREATE TABLE #PostData(
postid INT IDENTITY(1,1) NOT NULL
,accountid VARCHAR(50)
,posttext VARCHAR(50)
);
IF OBJECT_ID('tempdb.dbo.#AccountData') IS NOT NULL
BEGIN
DROP TABLE #AccountData
END
CREATE TABLE #AccountData(
accountid INT
,username VARCHAR(50)
);
IF OBJECT_ID('tempdb.dbo.#CategoryData') IS NOT NULL
BEGIN
DROP TABLE #CategoryData
END
CREATE TABLE #CategoryData(
categoryname VARCHAR(50)
);
INSERT INTO #AccountData VALUES ('1', 'user1')
INSERT INTO #PostData VALUES('1','this is a post')
INSERT INTO #LikesDislikesData (isLiked ,accountid, postid)
SELECT '1', P.accountid, P.postid
FROM #PostData P
WHERE P.posttext = 'this is a post'
SELECT *
FROM #PostData
SELECT *
FROM #LikesDislikesData
SELECT *
FROM #AccountData
SELECT COUNT(L.isLiked) 'Likes'
,P.posttext
,A.username
FROM #PostData P
JOIN #LikesDislikesData L
ON P.accountid = L.accountid
AND L.IsLiked = 1
JOIN #AccountData A
ON P.accountid = A.accountid
GROUP BY P.posttext, A.username
SELECT X.likes, Y.dislikes
FROM (
(SELECT COUNT(isliked)as 'likes', accountid
FROM #LikesDislikesData
WHERE isLiked = 1
GROUP BY accountid
) X
JOIN
(SELECT COUNT(isliked)as 'dislikes', accountid
FROM #LikesDislikesData
WHERE isLiked = 0
GROUP BY accountid) Y
ON x.accountid = y.accountid)
IF (XACT_STATE() = 1 AND ERROR_STATE() = 0)
BEGIN
COMMIT TRAN
END
ELSE IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRAN
END
How do you think about the solution? We create a new table SummaryReport(PostID,AccountID,NumberOfLikedTime,NumberOfDislikedTimes).
An user clicks on LIKE or DISLIKE button we update the table. After that, you can query as you desire. Another advantage, the table can be served reporting purpose.

use stored procedure to select last item value

i am trying to select the last record value from my database by using stored procedure, to do this i set my #UPID parameter as SCORE_IDENTITY(), but there are no output result as all after i execute my stored procedure
CREATE PROCEDURE [dbo].[spAuditLogSelect_NewUser]
#UPID int
AS
BEGIN
SET #UPID = SCOPE_IDENTITY()
SELECT
siUserProfile.UPID
,siUserProfile.ProfileType, siProfileType.RGDName AS ProfileTypeName
,siUserProfile.CBID, siCompany.ComName + ' - ' + siComBranch.ComBranchName AS CBName
,siUserProfile.FullName
,siUserProfile.ShortName
,siUserProfile.SerialCode
,siUserProfile.Serial
,siUserProfile.Gender
from siUserProfile WITH (NOLOCK)
inner join siUserProfileDetail WITH (NOLOCK) on siUserProfile.upid = siUserProfileDetail.UPID
left outer join siReferenceGroupDetail siProfileType WITH (NOLOCK) ON siUserProfile.ProfileType = siProfileType.RGDID
left outer join siComBranch WITH (NOLOCK) on siComBranch.CBID = siUserProfile.CBID
left outer join siCompany WITH (NOLOCK) ON siComBranch.CompanyID = siCompany.CompanyID
where siUserProfile.UPID = #UPID
SCOPE_IDENTITY() is meant to be used right after insert. It won't work in a different session.
To retrieve the latest entry, try top 1:
select top 1 *
...
where siUserProfile.UPID = #UPID
order by
siUserProfile.ID desc
You require to use IDENT_CURRENT(‘tablename’).
Please refer below link which illustrate difference between ##IDENTITY,SCOPE_IDENTITY() and IDENT_CURRENT(‘tablename’).
http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-retrieve-last-inserted-identity-of-record/
Hope this will help you.
I am assuming that you have some primary key(PK) in your table. Write the procedure, in that procedure fire the query
select * from Your_Table where PK_Column in(select max(PK_Column) from Your_Table)
This way you will be able to fetch the latest record from DB. By opening a cursor, you can play with record in your procedure.

SQL Server Incrementing a counter in Update

I'm updating some columns and incrementing a counter whenever I change a row.
The update statement is the result of a join (simplified code below):
update #to
set
t.num += 1
from #to t
join #source s
on t.id = s.id
When I update one row more than once, the columns hold the last value (as they should), but the counter is only incremented once. So if the join returns (id = 1, id = 1), my table holds (id = 1, num = 1) rather than (id = 1, num = 2).
There are ways to get around this (another join on a select count, for example), but I wonder if there's a way to keep it simple.
There's not really a way to get the count without getting the count. Here is one way to do that (and still only referencing #source once):
;WITH s AS
(
SELECT id, c = COUNT(*)
FROM #source
GROUP BY id
)
UPDATE t SET t.num += s.c
FROM #to AS t
INNER JOIN s
ON t.id = s.id;
Hopefully the rows that end up in #source are already filtered down to only those that will also be found in #to. If not, you can add more conditions to the initial CTE.