How to loop through Delete one row at a time - sql

I am wondering if there is a way to restructure the below SQL so that one row is deleted at a time, as opposed to performing a delete in one mass operation? The reason being is that the delete action causes a trigger on this table to execute and (in cases where a USER_ID has more than 1 row) is attempting to insert data into another table that has a datetime stamp as a key and the same time (to the millisecond) is attempting to be inserted and causing a duplicate key insert error.
DELETE ORDERS
FROM LINE_ORDER ORDERS
INNER JOIN LINE_ORDER_XREF B ON B.OPRID = ORDERS.USER_ID
WHERE B.USERID = 'SYSACCT'
The thought was that if each row is deleted separately as it's own transaction then this will make each datetime stamp unique. The number of delete operations will be low and the additional processing time is not a concern in this case. Is it possible to structure into some loop or use a cursor? The primary ID column's in LINE_ORDER (USER_ID and USER_ROLE) are varchar columns so I don't believe I can increment this.
USER_ID USER_ROLE DYNAMIC_SW
11000_600 E_SAML N
11000_602 E_SAML N
11000_602 SUPRV N
11000_604 E_PRO N
11000_605 E_SAML N

Well, you can use TOP for this purpose:
DELETE o
FROM (SELECT TOP (1) o.*
FROM LINE_ORDER o INNER JOIN
LINE_ORDER_XREF lox
ON lox.OPRID = o.USER_ID
WHERE lox.USERID = 'SYSACCT'
) o;
You then need to embed this in a loop to delete all the matching values.

You can try this:
DECLARE #i INT=
(
SELECT ORDERS
FROM LINE_ORDER ORDERS
INNER JOIN LINE_ORDER_XREF B ON B.OPRID = ORDERS.USER_ID
WHERE B.USERID = 'SYSACCT'
);
DECLARE #count INT= 1;
WHILE(#count <= #i)
BEGIN
DELETE TOP (1)
ORDERS
FROM LINE_ORDER ORDERS
INNER JOIN LINE_ORDER_XREF B ON B.OPRID = ORDERS.USER_ID
WHERE B.USERID = 'SYSACCT';
SET #count = #count + 1;
END;

Related

Duplicate Values in Query

Novice SQL user here - I am trying to determine the delivery date (de_arrdate) for an order based on event data from the events table. A shipment can have multiple events, shipments usually have 4 events so the events table will return data based on shipment ID for all 4 events. Because of this, my total $$$ is overstated. How can I return only the largest value of the shipment sequence which would essentially be the final event date? My query is below. I've also attached a sample of the current output.
select dba.disp_ship.ds_id, dba.disp_ship.ds_bill_charge,
dba.disp_ship.ds_status, dba.disp_ship.ds_ship_type,
dba.disp_events.de_site, dba.disp_events.de_arrdate,
dba.disp_events.de_shipment_id, dba.disp_events.de_ship_seq
from dba.disp_ship
inner join dba.disp_events on dba.disp_ship.ds_id = dba.disp_events.de_shipment_id
Not sure which RDBMS you are using nor the version, but if I understood correctly, you only want the amount stated in the last event of the sequence, right?
In this case, you already have the order of the events in the de_ship_seq column, so all you need to do is:
with last_event as (
select
de.de_arrdate,
de.de_shipment_id,
max(de.de_ship_seq)
from dba.disp_events as de
group by 1, 2
)
select
ds.ds_id,
ds.ds_bill_charge,
ds.de_arrdate
from dba.disp_ship as ds
join last_event as le on ds.ds_id = le.de_shipment_id
This way, you'll not get duplicity by the table disp_events, since you're only grabbing the maximum of the sequence, which it's supposed to be the last event :)
There are two ways to achieve this scenario.
1. Inner Query
select dba.disp_ship.ds_id, dba.disp_ship.ds_bill_charge,
dba.disp_ship.ds_status, dba.disp_ship.ds_ship_type,
dba.disp_events.de_site, dba.disp_events.de_arrdate,
dba.disp_events.de_shipment_id, dba.disp_events.de_ship_seq
from dba.disp_ship
inner join dba.disp_events on dba.disp_ship.ds_id = dba.disp_events.de_shipment_id,
inner Join (Select a.de_shipment_id as shipid,max(a.de_arrdate) as arrdate
from disp_events a) as t on dba.disp_events.de_shipment_id = t.shipid and dba.disp_events.de_arrdate = t.arrdate
2. Procedure
//Datatype for the Temporary tables is an assumption. Replace with your data type.
begin
declare local temporary table tbl1(
ds_id numeric(10),
ds_bill_charge numeric(14,2),
ds_status int,
ds_ship_type int,
de_site char(20),
de_arrdate date,
de_shipment_id numeric(10),
de_ship_seq numeric(10)
)on commit preserve rows;
declare local temporary table tbl1(
rowid numeric(10);
shipmentid numeric(10)
)on commit preserve rows;
declare #rowcount,#ds_id,i numeric(10);
set i = 1;
insert into tbl1
select dba.disp_ship.ds_id, dba.disp_ship.ds_bill_charge,
dba.disp_ship.ds_status, dba.disp_ship.ds_ship_type,
dba.disp_events.de_site, dba.disp_events.de_arrdate,
dba.disp_events.de_shipment_id, dba.disp_events.de_ship_seq
from dba.disp_ship
inner join dba.disp_events on dba.disp_ship.ds_id = dba.disp_events.de_shipment_id;
insert into tbl2
select number(*), ds_id from(select distinct ds_id from tbl1) a;
select count(*) into #rowcount from tbl2;
while i <= #rowcount Loop
Select ds_id into #ds_id from tbl2 where rowid = i;
delete from tbl1 where ds_id = #ds_id and
de_ship_seq not in(select top 1 de_ship_seq from tbl1 a
where a.ds_id = #ds_id order by de_arrdate desc);
i++;
end Loop;
select * from tbl1;
end
Thank You...

SORT Operation in an Update Statement

In the SQL below, why is my Update statement performing a SORT operation. The cost of the SORT operation is 41% and I would like to avoid it.
declare #m_table as table (oh_job_cons_id varchar(36))
Insert into #m_table
select top 100 oh_job_cons_id
from oh_job_cons with (nolock)
-- select * from #m_table
Update j
set oh_locked_by_user_id = null,
oh_locked_on = null
from oh_job_cons j with (nolock)
join #m_table m on j.oh_job_cons_id = m.oh_job_cons_id
The SORT in the update operation is probably due to the join constraint (#m_table m on j.oh_jobs_cons_id = m.oh_job_cons_id).
Particularly if the "oh_job_cons_id" column is not the primary key of the oh_jobs_cons table.

Using a LEFT OUTER JOIN WHERE then updating same data

I have two databases running on MSSQL 2005, SOURCE and DESTINATION, which have the same structure and tables in them.
I'm trying to update data from s to d.
In this example, I'm trying to copy data from s to d using a join and only bringing across entries which aren't already in d.
I'm then trying to update the same records just inserted with vales thus:
INSERT DESTINATION.ITEM_REPLENISH_VENDOR ([ITEM_CODE],[VEND_CODE],[PRIMARY_VENDOR],[PURCHASE_MEASURE],[STD_COST],[LAST_COST],[EOQ],[VENDOR_PART_NO],[LEAD_TIME],[COST])
SELECT s.[ITEM_CODE],s.[VEND_CODE],s.[PRIMARY_VENDOR],s.[PURCHASE_MEASURE],s.[STD_COST],s.[LAST_COST],s.[EOQ],s.[VENDOR_PART_NO],s.[LEAD_TIME], s.[COST] FROM SOURCE.dbo.ITEM_REPLENISH_VENDOR s
LEFT OUTER JOIN DESTINATION.dbo.ITEM_REPLENISH_VENDOR d ON (d.ITEM_CODE = s.ITEM_CODE)
WHERE d.ITEM_CODE IS NULL
UPDATE DESTINATION.dbo.ITEM_REPLENISH_VENDOR
SET VEND_CODE='100004', PRIMARY_VENDOR='T',STD_COST='0',LAST_COST='0',COST='0'
WHERE
My issue is once I reach the second WHERE I don't know how to refer to the data I've just updated. This script is going to run either every day at a set time and I don't want to overwrite that whole column with those values, just the entries that have been inserted on this execution.
It looks like you want the output clause This will let you stash away the inserted values.
-- item_code needs to have the same type as the source table
declare #inserted table (item_code int not null primary key);
insert destination.item_replenish_vendor (
[item_code], [vend_code], [primary_vendor],
[purchase_measure], [std_cost], [last_cost],
[eoq], [vendor_part_no], [lead_time],[cost]
) -- save inserted values
output
inserted.item_code into #inserted
select
s.[item_code], s.[vend_code], s.[primary_vendor],
s.[purchase_measure], s.[std_cost], s.[last_cost],
s.[eoq], s.[vendor_part_no], s.[lead_time], s.[cost]
from
source.dbo.item_replenish_vendor s
left outer join
destination.dbo.item_replenish_vendor d
on d.item_code = s.item_code
where
d.item_code is null;
update
d
set
vend_code = '100004',
primary_vendor = 'T',
std_cost = '0',
last_cost = '0,
cost = '0'
from
destination.dbo.item_replenish_vendor d
inner join
#inserted i
on d.item_code = i.item_code;
In this case, you could just put constant values in the insert statement, instead of doing things in two steps...
In the example you have:
UPDATE DESTINATION.dbo.ITEM_REPLENISH_VENDOR
SET VEND_CODE='100004', PRIMARY_VENDOR='T',STD_COST='0',LAST_COST='0',COST='0'
If your VEND_CODE, PRIMARY_VENDOR, STD_COST, LAST_COST, COST are always going to be a static value, you could just put them into the first query.
INSERT DESTINATION.ITEM_REPLENISH_VENDOR ([ITEM_CODE],[VEND_CODE],[PRIMARY_VENDOR],[PURCHASE_MEASURE],[STD_COST],[LAST_COST],[EOQ],[VENDOR_PART_NO],[LEAD_TIME],[COST])
SELECT s.[ITEM_CODE],'100004','T',s.[PURCHASE_MEASURE],'0','0',s.[EOQ],s.[VENDOR_PART_NO],s.[LEAD_TIME], '0'
FROM SOURCE.dbo.ITEM_REPLENISH_VENDOR s
LEFT OUTER JOIN DESTINATION.dbo.ITEM_REPLENISH_VENDOR d ON (d.ITEM_CODE = s.ITEM_CODE)
WHERE d.ITEM_CODE IS NULL
but if they do need to be calculated after insert, then I agree with Laurence's approach.

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.

Slow stored procedure

I currently have a stored procedure that copies content from one table to another.
However when it is trying to only insert 27 new rows it continues on for over 12 minutes (after which point I stopped it) it said Affected 27 rows 4 times, however changes were not made.
Can you spot any reason the following SP would be slow?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER Procedure [dbo].[sp_CopyCompanyContent]
(
#intCopyFromCompanyID Int,
#intNewCompanyID Int
)
As
Begin
/*
RaisError If any of Odl/New Company ID's are 0
*/
If (#intCopyFromCompanyID = 0 Or #intNewCompanyID = 0)
Begin
RaisError('New Company ID or Old Company ID can not be 0', 16, 1)
Return
End
/*
Create Temp Table For the Content Sections
*/
If Object_ID('tempdb..#ContentSections') IS Not Null
Begin
Drop Table dbo.#ContentSections
End
/*
Have to get all the existing data for the Old company we are copying from.
Have to also add the Max(ContentSectionID) From ContentSection. Max(ContentSectionID) +
The Identity (Added further down due to laziness) will be our seed for the ContentID
*/
Select CS.ContentID,
CS.SectionID,
CS.MenuOrder,
#intNewCompanyID NewCompanyID,
CS.CompanyID OldCompanyID,
CS.SubMenu,
CS.Link,
CS.HeaderMenu,
CS.ParentContentID,
CRS.*
Into dbo.#ContentSections
From dbo.Company COMP
Join dbo.ContentSection CS
On COMP.Company_id = CS.CompanyID
Join dbo.Content CONT
On CONT.ContentID = CS.ContentID
Cross Join (
Select MAx(ContentSectionID) MaxContentSectionID
From dbo.ContentSection CONT
) crs
Where COMP.Company_id = #intCopyFromCompanyID
Order By COMP.Company_id
/*
We now need to create a table for the existing content for the old company.
Also have to create the seed. Same principle as above.
*/
If Object_ID('tempdb..#Content') IS Not Null
Begin
Drop Table dbo.#Content
End
Select CONT.*,
CRS.*
Into dbo.#Content
From dbo.Company COMP
Join dbo.ContentSection CS
On COMP.Company_id = CS.CompanyID
Join dbo.Content CONT
On CONT.ContentID = CS.ContentID
Cross Join (
Select MAx(ContentID) MaxContentID
From dbo.Content CONT
) crs
Where COMP.Company_id = #intCopyFromCompanyID
Order By COMP.Company_id
/*
Add Identity to each of the tables we have created above. The ID fields will add to
the Max of each table to mimic what the future seeds will be.
*/
exec('Alter table #ContentSections Add ID Int Identity(1,1)')
exec('Alter table #Content Add ID Int Identity(1,1)')
/*
Add content data from the temp table.
*/
Insert Into dbo.Content
(
Title,
Content
)
Select Title,
Content
From dbo.#Content
/*
Have to the Content table up to the content sections table
as this contains what ID has been add to the Content Table.
*/
Insert Into dbo.ContentSection
(
ContentID,
SectionID,
MenuOrder,
CompanyID,
SubMenu,
Link,
HeaderMenu,
ParentContentID
)
Select C.MaxContentID + C.ID,
CS.SectionID,
CS.MenuOrder,
CS.NewCompanyID,
CS.Submenu,
CS.Link,
CS.HEaderMEnu,
CS.ParentContentID
From dbo.#Content C
Join dbo.#ContentSections CS
On C.ID = CS.ID
End
First thing to do is check the query plan for the selects since cross joins are dangerous beasts, if i'm not reading it wrong you'd display the same value in CRS.* in every record right?
If so, make that query before the select and stored the result in a variable and display it in the select, something like this.
DECLATE #maxValue INTEGER
Select #maxValue=MAX(ContentID) MaxContentID
From dbo.Content CONT
Select CONT.*,
#maxValue as MaxContentID
Into dbo.#Content
From dbo.Company COMP
Join dbo.ContentSection CS
On COMP.Company_id = CS.CompanyID
Join dbo.Content CONT
On CONT.ContentID = CS.ContentID
Where COMP.Company_id = #intCopyFromCompanyID
Order By COMP.Company_id
It is probably because of the identity (it is 2 times from the 4 times*27 rows)
exec('Alter table #ContentSections Add ID Int Identity(1,1)')
exec('Alter table #Content Add ID Int Identity(1,1)')
Instead, if you don't want to create the table, try to use ROW_NUMBER() OVER()... clause as ID, and then you don't need to create the identity later.
And as I see, you don't even need to use the Temp Tables, because then you can simply use the two select as INSERT INTO .... SELECT ... With the ROW_NUMBER().
It seems you don't make any changes on the Temp Tables, so you probably don't need them. (Only if you want to use them out of the SP's scope)