Query performance is not what I would expect - tsql

I have the following query across a big table (close to 50 million records) and a small table (maybe 1000 records on average) in SQL Server 2008 R2:
UPDATE aa
SET col1 = bb.col1,
col2 = bb.col2
FROM
tblBIG aa
INNER JOIN
#tempTblSmall bb ON aa.colMatchA = bb.colMatchA
AND aa.colMatchB = bb.colMatchB
AND aa.colMatchC = bb.colMatchC
AND aa.colMatchD = bb.colMatchD
WHERE
aa.colMatchD >= #DateChanged
The query runs in around 10 seconds which is okay but not great, so I am trying to optimize it and came up with the following change:
UPDATE aa
SET col1 = bb.col1,
col2 = bb.col2
FROM
tblBIG aa
INNER JOIN
#tempTblSmall bb ON aa.colMatchA = bb.colMatchA
AND aa.colMatchB = bb.colMatchB
AND aa.colMatchC = bb.colMatchC
AND aa.colMatchD = bb.colMatchD
WHERE
aa.colMatchA = #Param1
AND aa.colMatchB = #Param2
AND aa.colMatchD >= #DateChanged
This cut down the query run time drastically (basically immediate at this point), but I am not sure why this works. The temporary table #tempTblSmall is already restricted to #Param1 and #Param2, so I would have assumed that I wouldn't need another set of where clauses.
I am happy that I managed to improve the performance, but I am curious to know if I got lucky and managed to put the query planner into a better estimation plan or if I just misunderstood how SQL Server plans the inner join and should be doing something like this going forward. I thought SQL Server would have an implicit where clause because of the join, but apparently restricting the large table is done before the join?

So the temporary table is limited to #Param2 #DateChanged
Those condition are on the big table
And I would pull the conditions into the join
Update aa
set aa.col1 = bb.col1,
aa.col2 = bb.col2
from tblBIG aa
join #tempTblSmall bb
on aa.colMatchA = bb.colMatchA
and aa.colMatchB = bb.colMatchB
and aa.colMatchC = bb.colMatchC
and aa.colMatchD = bb.colMatchD
and aa.colMatchA = #Param1
and aa.colMatchB = #Param2
and aa.colMatchD >= #DateChanged
and (aa.col1 <> bb.col1 or aa.col2 <> bb.col2)

Related

Updating a table by joining an array of FKs of another table [duplicate]

Basically, I want to do this:
update vehicles_vehicle v
join shipments_shipment s on v.shipment_id=s.id
set v.price=s.price_per_vehicle;
I'm pretty sure that would work in MySQL (my background), but it doesn't seem to work in postgres. The error I get is:
ERROR: syntax error at or near "join"
LINE 1: update vehicles_vehicle v join shipments_shipment s on v.shi...
^
Surely there's an easy way to do this, but I can't find the proper syntax. So, how would I write this In PostgreSQL?
The UPDATE syntax is:
[ WITH [ RECURSIVE ] with_query [, ...] ]
UPDATE [ ONLY ] table [ [ AS ] alias ]
SET { column = { expression | DEFAULT } |
( column [, ...] ) = ( { expression | DEFAULT } [, ...] ) } [, ...]
[ FROM from_list ]
[ WHERE condition | WHERE CURRENT OF cursor_name ]
[ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ]
In your case I think you want this:
UPDATE vehicles_vehicle AS v
SET price = s.price_per_vehicle
FROM shipments_shipment AS s
WHERE v.shipment_id = s.id
Or if you need to join on two or more tables:
UPDATE table_1 t1
SET foo = 'new_value'
FROM table_2 t2
JOIN table_3 t3 ON t3.id = t2.t3_id
WHERE
t2.id = t1.t2_id
AND t3.bar = True;
The answer of Mark Byers is the optimal in this situation.
Though in more complex situations you can take the select query that returns rowids and calculated values and attach it to the update query like this:
with t as (
-- Any generic query which returns rowid and corresponding calculated values
select t1.id as rowid, f(t2, t2) as calculatedvalue
from table1 as t1
join table2 as t2 on t2.referenceid = t1.id
)
update table1
set value = t.calculatedvalue
from t
where id = t.rowid
This approach lets you develop and test your select query and in two steps convert it to the update query.
So in your case the result query will be:
with t as (
select v.id as rowid, s.price_per_vehicle as calculatedvalue
from vehicles_vehicle v
join shipments_shipment s on v.shipment_id = s.id
)
update vehicles_vehicle
set price = t.calculatedvalue
from t
where id = t.rowid
Note that column aliases are mandatory otherwise PostgreSQL will complain about the ambiguity of the column names.
Let me explain a little more by my example.
Task: correct info, where abiturients (students about to leave secondary school) have submitted applications to university earlier, than they got school certificates (yes, they got certificates earlier, than they were issued (by certificate date specified). So, we will increase application submit date to fit certificate issue date.
Thus. next MySQL-like statement:
UPDATE applications a
JOIN (
SELECT ap.id, ab.certificate_issued_at
FROM abiturients ab
JOIN applications ap
ON ab.id = ap.abiturient_id
WHERE ap.documents_taken_at::date < ab.certificate_issued_at
) b
ON a.id = b.id
SET a.documents_taken_at = b.certificate_issued_at;
Becomes PostgreSQL-like in such a way
UPDATE applications a
SET documents_taken_at = b.certificate_issued_at -- we can reference joined table here
FROM abiturients b -- joined table
WHERE
a.abiturient_id = b.id AND -- JOIN ON clause
a.documents_taken_at::date < b.certificate_issued_at -- Subquery WHERE
As you can see, original subquery JOIN's ON clause have become one of WHERE conditions, which is conjucted by AND with others, which have been moved from subquery with no changes. And there is no more need to JOIN table with itself (as it was in subquery).
For those actually wanting to do a JOIN you can also use:
UPDATE a
SET price = b_alias.unit_price
FROM a AS a_alias
LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id
WHERE a_alias.unit_name LIKE 'some_value'
AND a.id = a_alias.id;
You can use the a_alias in the SET section on the right of the equals sign if needed.
The fields on the left of the equals sign don't require a table reference as they are deemed to be from the original "a" table.
For those wanting to do a JOIN that updates ONLY the rows your join returns use:
UPDATE a
SET price = b_alias.unit_price
FROM a AS a_alias
LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id
WHERE a_alias.unit_name LIKE 'some_value'
AND a.id = a_alias.id
--the below line is critical for updating ONLY joined rows
AND a.pk_id = a_alias.pk_id;
This was mentioned above but only through a comment..Since it's critical to getting the correct result posting NEW answer that Works
Here we go:
update vehicles_vehicle v
set price=s.price_per_vehicle
from shipments_shipment s
where v.shipment_id=s.id;
Simple as I could make it.
To add something quite important to all the great answers above, when you want to update a join-table, you may have 2 problems:
you cannot use the table you want to update to JOIN another one
Postgres wants a ON clause after the JOIN so you cannot only use where clauses.
This means that basically, the following queries are not valid:
UPDATE join_a_b
SET count = 10
FROM a
JOIN b on b.id = join_a_b.b_id -- Not valid since join_a_b is used here
WHERE a.id = join_a_b.a_id
AND a.name = 'A'
AND b.name = 'B'
UPDATE join_a_b
SET count = 10
FROM a
JOIN b -- Not valid since there is no ON clause
WHERE a.id = join_a_b.a_id
AND b.id = join_a_b.b_id
a.name = 'A'
AND b.name = 'B'
Instead, you must use all the tables in the FROM clause like this:
UPDATE join_a_b
SET count = 10
FROM a, b
WHERE a.id = join_a_b.a_id
AND b.id = join_a_b.b_id
AND a.name = 'A'
AND b.name = 'B'
It might be straightforward for some but I got stuck on this problem wondering what's going on so hopefully, it will help others.
Here's a simple SQL that updates Mid_Name on the Name3 table using the Middle_Name field from Name:
update name3
set mid_name = name.middle_name
from name
where name3.person_id = name.person_id;
The link below has a example that resolve and helps understant better how use update and join with postgres.
UPDATE product
SET net_price = price - price * discount
FROM
product_segment
WHERE
product.segment_id = product_segment.id;
See: http://www.postgresqltutorial.com/postgresql-update-join/
First Table Name: tbl_table1 (tab1).
Second Table Name: tbl_table2 (tab2).
Set the tbl_table1's ac_status column to "INACTIVE"
update common.tbl_table1 as tab1
set ac_status= 'INACTIVE' --tbl_table1's "ac_status"
from common.tbl_table2 as tab2
where tab1.ref_id= '1111111'
and tab2.rel_type= 'CUSTOMER';
To UPDATE one Table using another, in PostGRE SQL / AWS (SQL workbench).
In PostGRE SQL, this is how you need to use joins in UPDATE Query:
UPDATE TABLEA set COLUMN_FROM_TABLEA = COLUMN_FROM_TABLEB FROM TABLEA,TABLEB WHERE FILTER_FROM_TABLEA = FILTER_FROM_TABLEB;
Example:
Update Employees Set Date_Of_Exit = Exit_Date_Recorded , Exit_Flg = 1 From Employees, Employee_Exit_Clearance Where Emp_ID = Exit_Emp_ID
Table A - Employees Columns in Table A - Date_Of_Exit,Emp_ID,Exit_Flg Table B is - Employee_Exit_Clearance Columns in Table B - Exit_Date_Recorded,Exit_Emp_ID
1760 rows affected
Execution time: 29.18s
--goal: update selected columns with join (postgres)--
UPDATE table1 t1
SET column1 = 'data'
FROM table1
RIGHT JOIN table2
ON table2.id = table1.id
WHERE t1.id IN
(SELECT table2.id FROM table2 WHERE table2.column2 = 12345)
The first way is slower than the second way.
First:
DO $$
DECLARE
page int := 10000;
min_id bigint; max_id bigint;
BEGIN
SELECT max(id),min(id) INTO max_id,min_id FROM opportunities;
FOR j IN min_id..max_id BY page LOOP
UPDATE opportunities SET sec_type = 'Unsec'
FROM opportunities AS opp
INNER JOIN accounts AS acc
ON opp.account_id = acc.id
WHERE acc.borrower = true
AND opp.sec_type IS NULL
AND opp.id >= j AND opp.id < j+page;
COMMIT;
END LOOP;
END; $$;
Second:
DO $$
DECLARE
page int := 10000;
min_id bigint; max_id bigint;
BEGIN
SELECT max(id),min(id) INTO max_id,min_id FROM opportunities;
FOR j IN min_id..max_id BY page LOOP
UPDATE opportunities AS opp
SET sec_type = 'Unsec'
FROM accounts AS acc
WHERE opp.account_id = acc.id
AND opp.sec_type IS NULL
AND acc.borrower = true
AND opp.id >= j AND opp.id < j+page;
COMMIT;
END LOOP;
END; $$;
WORKS PERFECT!!!
POSTGRE SQL - UPDATE With a JOIN
BELOW CODE - Check the positioning of columns and IDs as below:
If you place it exactly as below, then only it will work!
---IF you want to update FIRST table
UPDATE table1
SET attribute1 = table2.attribute1
FROM table2
WHERE table2.product_ID = table1.product_ID;
OR
---IF you want to update SECOND table
UPDATE table2
SET attribute1 = table1.attribute1
FROM table1
WHERE table1.product_ID = table2.product_ID;

Refactoring a slow performing query with a subquery in Postgres

I have a query that is taking too long to execute (600 ms). It is mostly due to having the Distinct keyword, without it it executes in ~20 ms. I've tried to take out the Distinct and use a Group By on visit.participation_id but I didn't see any performance improvements. Here is the original query:
SELECT COUNT(*) FROM "participations" WHERE "participations"."event_id" = $1 AND "participations"."is_preview" = $2 AND ("participations"."id" NOT IN (SELECT DISTINCT "visits"."participation_id" FROM "visits" INNER JOIN "ahoy_events" ON "ahoy_events"."visit_id" = "visits"."id" WHERE "visits"."event_id" = $3 AND "visits"."participation_id" IN (SELECT "participations"."id" FROM "participations" WHERE "participations"."event_id" = $4 AND "participations"."is_preview" = $5)))
I already have an indexes on Visit participant_id and event_id.
How can this be refactored to not use the Distinct operation or what else can be done? Would a materialized view make sense to use here?
You could rewrite it with EXISTS:
SELECT COUNT(*)
FROM "participations" p2
WHERE p2."event_id" = $1
AND p2."is_preview" = $2
AND NOT EXISTS (SELECT 1
FROM "visits"
JOIN "ahoy_events" ON "ahoy_events"."visit_id" = "visits"."id"
JOIN "participations" ON "visits"."participation_id" = "participations"."id"
WHERE "visits"."event_id" = $3
AND "participations"."event_id" = $4
AND "participations"."is_preview" = $5
AND p2."participation_id" = "participations"."id"))

T-SQL query one table, get presence or absence of other table value

I'm not sure what this type of query is called so I've been unable to search for it properly. I've got two tables, Table A has about 10,000 rows. Table B has a variable amount of rows.
I want to write a query that gets all of Table A's results but with an added column, the value of that column is a boolean that says whether the result also appears in Table B.
I've written this query which works but is slow, it doesn't use a boolean but rather a count that will be either zero or one. Any suggested improvements are gratefully accepted:
SELECT u.number,u.name,u.deliveryaddress,
(SELECT COUNT(productUserid)
FROM ProductUser
WHERE number = u.number and productid = #ProductId)
AS IsInPromo
FROM Users u
UPDATE
I've run the query with actual execution plan enabled, I'm not sure how to show the results but various costs are:
Nested Loops (left semi join): 29%]
Clustered Index scan (User Table): 41%
Clustered Index Scan (ProductUser table): 29%
NUMBERS
There are 7366 users in the users table and currently 18 rows in the productUser table (although this will change and could be in the thousands)
You can use EXISTS to short circuit after the first row is found rather than COUNT-ing all matching rows.
SQL Server does not have a boolean datatype. The closest equivalent is BIT
SELECT u.number,
u.name,
u.deliveryaddress,
CASE
WHEN EXISTS (SELECT *
FROM ProductUser
WHERE number = u.number
AND productid = #ProductId) THEN CAST(1 AS BIT)
ELSE CAST(0 AS BIT)
END AS IsInPromo
FROM Users u
RE: "I'm not sure what this type of query is called". This will give a plan with a semi join. See Subqueries in CASE Expressions for more about this.
Which management system are you using?
Try this:
SELECT u.number,u.name,u.deliveryaddress,
case when COUNT(p.productUserid) > 0 then 1 else 0 end
FROM Users u
left join ProductUser p on p.number = u.number and productid = #ProductId
group by u.number,u.name,u.deliveryaddress
UPD: this could be faster using mssql
;with fff as
(
select distinct p.number from ProductUser p where p.productid = #ProductId
)
select u.number,u.name,u.deliveryaddress,
case when isnull(f.number, 0) = 0 then 0 else 1 end
from Users u left join fff f on f.number = u.number
Since you seem concerned about performance, this query can perform faster as this will cause index seek on both tables versus an index scan:
SELECT u.number,
u.name,
u.deliveryaddress,
ISNULL(p.number, 0) IsInPromo
FROM Users u
LEFT JOIN ProductUser p ON p.number = u.number
WHERE p.productid = #ProductId

How to do an update + join in PostgreSQL?

Basically, I want to do this:
update vehicles_vehicle v
join shipments_shipment s on v.shipment_id=s.id
set v.price=s.price_per_vehicle;
I'm pretty sure that would work in MySQL (my background), but it doesn't seem to work in postgres. The error I get is:
ERROR: syntax error at or near "join"
LINE 1: update vehicles_vehicle v join shipments_shipment s on v.shi...
^
Surely there's an easy way to do this, but I can't find the proper syntax. So, how would I write this In PostgreSQL?
The UPDATE syntax is:
[ WITH [ RECURSIVE ] with_query [, ...] ]
UPDATE [ ONLY ] table [ [ AS ] alias ]
SET { column = { expression | DEFAULT } |
( column [, ...] ) = ( { expression | DEFAULT } [, ...] ) } [, ...]
[ FROM from_list ]
[ WHERE condition | WHERE CURRENT OF cursor_name ]
[ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ]
In your case I think you want this:
UPDATE vehicles_vehicle AS v
SET price = s.price_per_vehicle
FROM shipments_shipment AS s
WHERE v.shipment_id = s.id
Or if you need to join on two or more tables:
UPDATE table_1 t1
SET foo = 'new_value'
FROM table_2 t2
JOIN table_3 t3 ON t3.id = t2.t3_id
WHERE
t2.id = t1.t2_id
AND t3.bar = True;
The answer of Mark Byers is the optimal in this situation.
Though in more complex situations you can take the select query that returns rowids and calculated values and attach it to the update query like this:
with t as (
-- Any generic query which returns rowid and corresponding calculated values
select t1.id as rowid, f(t2, t2) as calculatedvalue
from table1 as t1
join table2 as t2 on t2.referenceid = t1.id
)
update table1
set value = t.calculatedvalue
from t
where id = t.rowid
This approach lets you develop and test your select query and in two steps convert it to the update query.
So in your case the result query will be:
with t as (
select v.id as rowid, s.price_per_vehicle as calculatedvalue
from vehicles_vehicle v
join shipments_shipment s on v.shipment_id = s.id
)
update vehicles_vehicle
set price = t.calculatedvalue
from t
where id = t.rowid
Note that column aliases are mandatory otherwise PostgreSQL will complain about the ambiguity of the column names.
Let me explain a little more by my example.
Task: correct info, where abiturients (students about to leave secondary school) have submitted applications to university earlier, than they got school certificates (yes, they got certificates earlier, than they were issued (by certificate date specified). So, we will increase application submit date to fit certificate issue date.
Thus. next MySQL-like statement:
UPDATE applications a
JOIN (
SELECT ap.id, ab.certificate_issued_at
FROM abiturients ab
JOIN applications ap
ON ab.id = ap.abiturient_id
WHERE ap.documents_taken_at::date < ab.certificate_issued_at
) b
ON a.id = b.id
SET a.documents_taken_at = b.certificate_issued_at;
Becomes PostgreSQL-like in such a way
UPDATE applications a
SET documents_taken_at = b.certificate_issued_at -- we can reference joined table here
FROM abiturients b -- joined table
WHERE
a.abiturient_id = b.id AND -- JOIN ON clause
a.documents_taken_at::date < b.certificate_issued_at -- Subquery WHERE
As you can see, original subquery JOIN's ON clause have become one of WHERE conditions, which is conjucted by AND with others, which have been moved from subquery with no changes. And there is no more need to JOIN table with itself (as it was in subquery).
For those actually wanting to do a JOIN you can also use:
UPDATE a
SET price = b_alias.unit_price
FROM a AS a_alias
LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id
WHERE a_alias.unit_name LIKE 'some_value'
AND a.id = a_alias.id;
You can use the a_alias in the SET section on the right of the equals sign if needed.
The fields on the left of the equals sign don't require a table reference as they are deemed to be from the original "a" table.
For those wanting to do a JOIN that updates ONLY the rows your join returns use:
UPDATE a
SET price = b_alias.unit_price
FROM a AS a_alias
LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id
WHERE a_alias.unit_name LIKE 'some_value'
AND a.id = a_alias.id
--the below line is critical for updating ONLY joined rows
AND a.pk_id = a_alias.pk_id;
This was mentioned above but only through a comment..Since it's critical to getting the correct result posting NEW answer that Works
Here we go:
update vehicles_vehicle v
set price=s.price_per_vehicle
from shipments_shipment s
where v.shipment_id=s.id;
Simple as I could make it.
To add something quite important to all the great answers above, when you want to update a join-table, you may have 2 problems:
you cannot use the table you want to update to JOIN another one
Postgres wants a ON clause after the JOIN so you cannot only use where clauses.
This means that basically, the following queries are not valid:
UPDATE join_a_b
SET count = 10
FROM a
JOIN b on b.id = join_a_b.b_id -- Not valid since join_a_b is used here
WHERE a.id = join_a_b.a_id
AND a.name = 'A'
AND b.name = 'B'
UPDATE join_a_b
SET count = 10
FROM a
JOIN b -- Not valid since there is no ON clause
WHERE a.id = join_a_b.a_id
AND b.id = join_a_b.b_id
a.name = 'A'
AND b.name = 'B'
Instead, you must use all the tables in the FROM clause like this:
UPDATE join_a_b
SET count = 10
FROM a, b
WHERE a.id = join_a_b.a_id
AND b.id = join_a_b.b_id
AND a.name = 'A'
AND b.name = 'B'
It might be straightforward for some but I got stuck on this problem wondering what's going on so hopefully, it will help others.
Here's a simple SQL that updates Mid_Name on the Name3 table using the Middle_Name field from Name:
update name3
set mid_name = name.middle_name
from name
where name3.person_id = name.person_id;
The link below has a example that resolve and helps understant better how use update and join with postgres.
UPDATE product
SET net_price = price - price * discount
FROM
product_segment
WHERE
product.segment_id = product_segment.id;
See: http://www.postgresqltutorial.com/postgresql-update-join/
First Table Name: tbl_table1 (tab1).
Second Table Name: tbl_table2 (tab2).
Set the tbl_table1's ac_status column to "INACTIVE"
update common.tbl_table1 as tab1
set ac_status= 'INACTIVE' --tbl_table1's "ac_status"
from common.tbl_table2 as tab2
where tab1.ref_id= '1111111'
and tab2.rel_type= 'CUSTOMER';
To UPDATE one Table using another, in PostGRE SQL / AWS (SQL workbench).
In PostGRE SQL, this is how you need to use joins in UPDATE Query:
UPDATE TABLEA set COLUMN_FROM_TABLEA = COLUMN_FROM_TABLEB FROM TABLEA,TABLEB WHERE FILTER_FROM_TABLEA = FILTER_FROM_TABLEB;
Example:
Update Employees Set Date_Of_Exit = Exit_Date_Recorded , Exit_Flg = 1 From Employees, Employee_Exit_Clearance Where Emp_ID = Exit_Emp_ID
Table A - Employees Columns in Table A - Date_Of_Exit,Emp_ID,Exit_Flg Table B is - Employee_Exit_Clearance Columns in Table B - Exit_Date_Recorded,Exit_Emp_ID
1760 rows affected
Execution time: 29.18s
--goal: update selected columns with join (postgres)--
UPDATE table1 t1
SET column1 = 'data'
FROM table1
RIGHT JOIN table2
ON table2.id = table1.id
WHERE t1.id IN
(SELECT table2.id FROM table2 WHERE table2.column2 = 12345)
The first way is slower than the second way.
First:
DO $$
DECLARE
page int := 10000;
min_id bigint; max_id bigint;
BEGIN
SELECT max(id),min(id) INTO max_id,min_id FROM opportunities;
FOR j IN min_id..max_id BY page LOOP
UPDATE opportunities SET sec_type = 'Unsec'
FROM opportunities AS opp
INNER JOIN accounts AS acc
ON opp.account_id = acc.id
WHERE acc.borrower = true
AND opp.sec_type IS NULL
AND opp.id >= j AND opp.id < j+page;
COMMIT;
END LOOP;
END; $$;
Second:
DO $$
DECLARE
page int := 10000;
min_id bigint; max_id bigint;
BEGIN
SELECT max(id),min(id) INTO max_id,min_id FROM opportunities;
FOR j IN min_id..max_id BY page LOOP
UPDATE opportunities AS opp
SET sec_type = 'Unsec'
FROM accounts AS acc
WHERE opp.account_id = acc.id
AND opp.sec_type IS NULL
AND acc.borrower = true
AND opp.id >= j AND opp.id < j+page;
COMMIT;
END LOOP;
END; $$;
WORKS PERFECT!!!
POSTGRE SQL - UPDATE With a JOIN
BELOW CODE - Check the positioning of columns and IDs as below:
If you place it exactly as below, then only it will work!
---IF you want to update FIRST table
UPDATE table1
SET attribute1 = table2.attribute1
FROM table2
WHERE table2.product_ID = table1.product_ID;
OR
---IF you want to update SECOND table
UPDATE table2
SET attribute1 = table1.attribute1
FROM table1
WHERE table1.product_ID = table2.product_ID;

T-SQL Problem converting a Cursor into a SET based operation

Basically I have this cursor that was not written by me but is taking some time to process and I was wanting to try and improve it by getting rid of the cursor all together.
Here is the code:
DECLARE #class_id int, #title_code varchar(30)
DECLARE title_class CURSOR FOR
SELECT DISTINCT title_code FROM tmp_business_class_titles (NOLOCK)
OPEN title_class
FETCH title_class INTO #title_code
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT TOP 1 #class_id = bc1.categoryid
FROM tmp_business_class_titles bct,
dbo.Categories bc1 (nolock)
join dbo.Categories bc2 (nolock) on bc2.categoryid = bc1.highercategoryid
join dbo.Categories bc3 (nolock) on bc3.categoryid = bc2.highercategoryid
WHERE bc1.categoryid = bct.class_id
AND title_code = #title_code
ORDER BY Default_Flag DESC
UPDATE products
SET subcategoryid = #class_id
WHERE ccode = #title_code
AND spdisplaytype = 'Table'
UPDATE products
SET subcategoryid = #class_id
WHERE highercatalogid IN (
SELECT catalogid FROM products (nolock)
WHERE ccode = #title_code AND spdisplaytype = 'Table')
FETCH title_class INTO #title_code
END
CLOSE title_class
DEALLOCATE title_class
The table tmp_business_class_titles looks like this:
class_id,title_code,Default_flag
7,101WGA,0
7,10315,0
29,8600,0
The default flag can always be 0 but if it is 1 then the logic should automatically pick the default class_id for that title_id.
So the current logic loops through the above table in a cursor and then selects the top 1 class id for each title, ordered by the the default flag (so the class_id with a default_flag of 1 should always be returned first.) and applies the default class_id to the products table.
This code takes around 1:20 to run and I am trying to convert this into one or 2 update statements but I have exhausted my brain in doing so.
Any TSQL Guru's have any ideas if this is possible or should I re-evaluate the entire logic on how the default flag works?
cheers for any help.
I don't have quite enough information to work with, so the following query is likely to fail. I particularly need more information on the products table to make this work, but assuming that you have SQL Server 2005 or higher, this might be enough to get you started in the right direction. It utilizes common table expressions along with the RANK function. I highly recommend learning about them, and in all likelihood, it will greatly improve the efficiency of the query.
;WITH cteTitle As (
SELECT
sequence = RANK() OVER (PARTITION BY bct.title_code ORDER BY Default_Flag desc)
,bct.title_code
,bc1.categoryid
FROM
tmp_business_class_titles bct
join Categories bc1 ON bc1.categoryid = bct.class_id
join Categories bc2 ON bc2.categoryid = bc1.highercategoryid
join Categories bc3 ON bc3.categoryid = bc2.highercategoryid
)
UPDATE
prod
SET
subcategoryid = ISNULL(t.categoryid,t2.categoryid)
FROM
products prod
LEFT join products subprod ON subprod.catalogid = prod.highercatalogid
LEFT join cteTitle t ON prod.ccode = t.title_code AND t.sequence = 1 AND prod.spdisplaytype = 'Table'
LEFT join cteTitle t2 ON subprod.ccode = t2.title_code And t2.sequence = 1 AND subprod.spdisplaytype = 'Table'
WHERE
t2.categoryid IS NOT NULL