DECLARE #EndID BIGINT,
#StartID BIGINT,
#n_batchSize INT = 3000
SET #EndID = (SELECT MAX(ID) FROM Table WHERE NewColumn IS NULL)
WHILE (#EndID>0)
BEGIN
SET #StartID = #EndID - #n_batchSize;
UPDATE Table WITH (ROWLOCK)
SET NewColumn =
(CASE
WHEN (ColumnA IS NOT NULL AND ColumnA > 0) THEN ColumnA
ELSE
(
SELECT TableC.ID
FROM TableB AS B WITH(NOLOCK)
INNER JOIN TableC AS C WITH(NOLOCK)
ON B.ID = C.ID
WHERE C.ID = Table.ID
) END
)
WHERE ID BETWEEN #StartID AND #EndID
AND NewColumn IS NULL
SET #EndID = #EndID - #n_batchSize;
WAITFOR DELAY '00:00:05'
END
The above script was executed to perform data patching operation.
After waited for it to be completed, there are some values of NewColumn remained null.
The count of NewColumn IS NULL is 140 and the same script executed for second time. Upon it's completion, few of the batches with "n rows affected" as shown below:
And when I check count of NewColumn IS NULL, it's still 140. So my best guess is the "n rows affected" is due to the select query from the SET part.
To perform experiment, I ran specifically targeting one record and see how it works with the below query
UPDATE Table WITH (ROWLOCK)
SET NewColumn =
(CASE
WHEN (ColumnA IS NOT NULL AND ColumnA > 0) THEN ColumnA
ELSE
(
SELECT TableC.ID
FROM TableB AS B WITH(NOLOCK)
INNER JOIN TableC AS C WITH(NOLOCK)
ON terminal.LocationID = location.LocationID
WHERE C.ID = Table.ID
) END
)
WHERE ID = 1 AND EntryZoneID IS NULL
The result is as below:
Based on the result, it seems like my guess was wrong. The statement is not because of the sub-select-query?
n rows affected would be coming from your update query. It shows how many rows were updated. It's not because of the subquery. It's because your update query is updating the rows based on your where condition
WHERE ID BETWEEN #StartID AND #EndID
AND NewColumn IS NULL
Related
I am trying to put table b value when table a inserted but nothing effects on table b. Its like there is no trigger. Do you have any suggestion. I have tried below but no result.
alter trigger triggername on tablea after insert as
begin
update tablea set valuetablea_a = valuetablea_b where id = (select distince id from inserted)
end
begin
update tableb set valuetableb_a = (select valuetablea_a from tablea where id = (select distincd id from Inserted))
where date = (select distinct date from Inserted)
end
Try this:
ALTER TRIGGER triggername ON tablea AFTER INSERT AS
BEGIN
UPDATE a
SET
valuetablea_a = I.valuetablea_b
FROM tablea A
INNER JOIN inserted I
ON A.Id = I.Id
UPDATE B
SET
valuetableb_a = A.valuetablea_a
FROM tableb B
INNER JOIN tablea
ON 1=1 AND EXISTS
(
SELECT 1 FROM inserted WHERE ID = A.ID AND [Date] = B.DATE
)
end
In the first update, you are giving this
update tablea set valuetablea_a = valuetablea_b
Which means From TableA update the value from column valuetablea_b to valuetablea_a for each row that was updated. Instead of getting the values from the Updated valuetablea_b column
2nd Update updates the values in TableB.valuetableb_a by matching the Id and Date fileds in the updated records
I'm using SQL Server 2008.
I have an after trigger for INSERT, UPDATE and DELETE action defined in the table. My problem is that currently my trigger inserts one record at a time and I need multiple records as for one
SELECT TOP 1 #ParentID FROM ... WHERE ID = #ID
returns multiple unique records.
(See this comment below "-- this subquery returns more than 1 value, so I need to insert in the search Audit table as many ParentIDs as it returns")
I believe I need to use cursor, but I'm not sure where exactly to declare and open cursor.
--CREATE PROCEDURE [dbo].[SP_Auditing]
-- #ID INT, #Code VARCHAR(3), #AuditType VARCHAR(10), #ParentCode VARCHAR(3) = NULL, #ParentID INT = NULL
--AS
--BEGIN
-- INSERT INTO myDB.dbo.Table1 (ID, Code, AuditType, ParentCode, ParentID)
-- VALUES(#ID, #Code, #AuditType, #ParentCode, #ParentID)
--END
GO
CREATE TRIGGER [dbo].[Tr_MyFavouriteTable_UPD_INSERT_DEL] ON [dbo].[MyFavouriteTable] AFTER INSERT, DELETE, UPDATE NOT FOR REPLICATION
AS
BEGIN
DECLARE #ID INT, #Code VARCHAR(3), #AuditType VARCHAR(10), #ParentCode VARCHAR(3), #ParentID INT SET #Code = 'DOC'
IF EXISTS (SELECT 1 FROM inserted) AND
NOT EXISTS (SELECT 1 FROM deleted)
BEGIN
SELECT TOP 1
#ID = ins.ID,
#ParentID = (
SELECT TOP 1 CAST(RIGHT(parentId,LEN(parentId) - LEN(LEFT(parentId,3))) AS INT)
FROM [MyDB].[dbo].[MyFavouriteTable] t WITH (NOLOCK)
INNER JOIN [MyDB2].[dbo].[MyView] v WITH (NOLOCK)
ON t.Id = v.ID
WHERE v.ID = #ID --284
), **-- this subquery returns more than 1 value, so I need to insert in the search Audit table as many ParentIDs as it returns**
#AuditType = 'INSERT' FROM inserted ins
IF #ID IS NOT NULL
AND
#ParentID IS NOT NULL
AND
#ParentCode IS NOT NULL
EXEC [MyDB].[dbo].SP_Auditing] #ID, #Code, #AuditType, #ParentCode, #ParentID
END
-- below is the same logic for UPDATE and DELETE actions...
The stored procedure above simply inserts data into the Audit table.
Never use scalar variables in triggers because insert, update, and delete may affect multiple rows. As to your trigger, try something like this.
CREATE TRIGGER [dbo].[Tr_MyFavouriteTable_UPD_INSERT_DEL]
ON [dbo].[MyFavouriteTable] AFTER INSERT, DELETE, UPDATE
NOT FOR REPLICATION
AS
BEGIN
;with act as (
select isnull(i.id,d.id) id, --either deleted or inserted is not null
case when i.id is not null and d.id is not null then 'update'
when i.id is not null then 'insert'
else 'delete' end auditType
from inserted i full outer join deleted d on i.id = d.id
),
audit_cte as (
SELECT act.id, 'DOC' Code,
CAST(RIGHT(parentId,LEN(parentId) - LEN(LEFT(parentId,3))) AS INT) parentid,
act.auditType, 'parentcode' parentCode
FROM [MyDB].[dbo].[MyFavouriteTable] t WITH (NOLOCK)
INNER JOIN [MyDB2].[dbo].[MyView] v WITH (NOLOCK) ON t.Id = v.ID
inner join act on act.id = t.id
)
insert myDB.dbo.Table1 (ID, Code, AuditType, ParentCode, ParentID)
select id,code,AuditType, ParentCode, ParentID
from audit_cte
where parentCode is not null and parentid is not null
end
Why do you need to get the records one by one? From my understanding you want to keep the log.
IF EXISTS (SELECT 1 FROM inserted) AND
NOT EXISTS (SELECT 1 FROM deleted)
BEGIN
INSERT INTO [Your_Log_Table]
SELECT
ins.ID, [Code],'INSERT',[PrentCode],
(SELECT TOP 1 CAST(RIGHT(parentId,LEN(parentId) -
LEN(LEFT(parentId,3))) AS INT)
FROM [MyDB].[dbo].[MyFavouriteTable] t WITH (NOLOCK)
INNER JOIN [MyDB2].[dbo].[MyView] v WITH (NOLOCK)
ON t.Id = v.ID
WHERE v.ID = ins.ID --284
)
FROM inserted ins
END
See Alex Kudryashev's answer. I needed to tweak a little his logic to sort out duplicate records with the same ParentIDs for the insertion into the Audit table. I added one more cte just below Alex's cte_Audit as follows
CREATE TRIGGER [dbo].[Tr_MyFavouriteTable_UPD_INSERT_DEL]
ON [dbo].[MyFavouriteTable] AFTER INSERT, DELETE, UPDATE
NOT FOR REPLICATION
AS
BEGIN
;with act as (
select isnull(i.id,d.id) id, --either deleted or inserted is not null
case when i.id is not null and d.id is not null then 'update'
when i.id is not null then 'insert'
else 'delete' end auditType
from inserted i full outer join deleted d on i.id = d.id
),
audit_cte as (
SELECT act.id, 'DOC' Code,
CAST(RIGHT(parentId,LEN(parentId) - LEN(LEFT(parentId,3))) AS INT) parentid,
act.auditType, 'parentcode' parentCode
FROM [MyDB].[dbo].[MyFavouriteTable] t WITH (NOLOCK)
INNER JOIN [MyDB2].[dbo].[MyView] v WITH (NOLOCK) ON t.Id = v.ID
inner join act on act.id = t.id
)
insert myDB.dbo.Table1 (ID, Code, AuditType, ParentCode, ParentID)
select id,code,AuditType, ParentCode, ParentID
from audit_cte
where parentCode is not null and parentid is not null
,CTE_dupsCleanup AS (
SELECT DISTINCT
Code,
Id,
AuditType,
ParentCode,
ParentId,
-- ROW_NUMBER() OVER(PARTITION BY ParentId, ParentCode, AuditType ORDER BY ParentId) AS Rn
FROM AUDIT_CTE
WHERE ParentCode IS NOT NULL
AND ParentId IS NOT NULL )
Then using Rn = 1 inserted only unique records into the Auidt table. Like this:
INSERT [ISSearch].[dbo].[SearchAudit] (Code, ID, AuditType, ParentCode, ParentID)
SELECT
Code,
ID,
AuditType,
ParentCode,
ParentId
FROM CTE_dupsCleanup
-- WHERE Rn = 1
END
I need to create a trigger which inserts into another table information about price changes. Below I present my solution.
CREATE TABLE Production.Products_AUDIT
(
auditid INT NOT NULL IDENTITY,
productid INT NULL,
old_price MONEY NOT NULL,
new_price MONEY NOT NULL,
CONSTRAINT PK_Products_AUDIT PRIMARY KEY(auditid),
CONSTRAINT FK_Products_AUDIT_AUDIT
FOREIGN KEY(productid) REFERENCES Production.Products(productid)
);
INSERT INTO Production.Products_AUDIT VALUES (1, 18 , 20)
INSERT INTO Production.Products_AUDIT VALUES (2, 19 , 31)
DELETE FROM Production.Products_AUDIT
SELECT unitprice
FROM Production.Products_AUDIT as p1
INNER JOIN Production.Products as p2 on p1.productid = p2.productid
CREATE TRIGGER trig1
ON Production.Products
FOR UPDATE
AS
declare #prodId INT
declare #oldPrice MONEY
declare #newPrice MONEY
SET #prodId = (SELECT i.productid
FROM inserted as i
INNER JOIN Production.Products as pp on i.productid = pp.productid )
SET #oldPrice = (SELECT i.unitprice
FROM deleted as i
INNER JOIN Production.Products as pp on i.productid = pp.productid )
SET #newPrice = (SELECT i.unitprice
FROM inserted as i
INNER JOIN Production.Products as pp on i.productid = pp.productid)
INSERT INTO Production.Products_AUDIT
VALUES(#prodId, #oldPrice, #newPrice)
UPDATE Production.Products
SET unitprice = 45
WHERE productid < 2
SELECT * FROM Production.Products_AUDIT
Everything is OK when I update only one record. The problem is when I try to update many records, then I see the error below:
Msg 512, Level 16, State 1, Procedure trig1, Line 41
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression. The
statement has been terminated.
Does anyone know how to fix this problem?
The problem is that Triggers are fired on a statement bases, and not on a row bases. This means that your trigger is fired once for all the rows updated in your statement, so the inserted and deleted tables might contain more than one row.
However, your trigger code does not take that into consideration, thus raising an error.
Try this instead:
CREATE TRIGGER Products_ForUpdate
ON Production.Products
FOR UPDATE
AS
INSERT INTO Production.Products_AUDIT
SELECT i.productid, d.unitprice, i.unitprice
FROM inserted as i
INNER JOIN Production.Products as pp on i.productid = pp.productid
INNER JOIN deleted as d ON pp.productid = d.productid
The trigger is fired for each Update statement not for each row in an update statement. You do not need any of these variables at all, just select data (old and New) data from inserted and deleted tables and insert it into the audit table directly, something like this........
CREATE TRIGGER trig1
ON Production.Products
FOR UPDATE
as
BEGIN
SET NOCOUNT ON;
INSERT INTO Production.Products_AUDIT (productid , Old_Price , New_Price)
SELECT pp.productid
, d.unitprice AS OldPrice
, i.unitprice AS NewPrice
FROM Production.Products as pp
INNER JOIN inserted i ON i.productid = pp.productid
INNER JOIN deleted d ON d.productid = pp.productid
END
I wish to make a modification (Set Deleted = 1) to rows being inserted into my table CustomerContact if the SELECT statement returns more than 0.
I have the following, but it remains untested:
CREATE TRIGGER mark_cust_contact_deleted ON CustomerContact
AFTER INSERT AS
BEGIN
DECLARE #numrows INT;
/* Determine if order matches criteria for marking customer contact as DELETED immediately */
SELECT #numrows = COUNT(*)
FROM [Order] o
JOIN OrderMeterDetail om
ON o.OrderID = om.OrderID
WHERE o.WorkTypeID = 3 AND o.WorkActionID = 26 AND o.WorkStageID IN (109, 309, 409)
AND om.MeterDetailTypeID = 1 AND om.MeterLocationID IN (2, 4)
AND o.orderid IN (SELECT OrderID FROM INSERTED);
/* If the order matches the criteria, mark the customer contact as deleted */
IF (#numrows >= 1)
UPDATE CustomerContact
SET Deleted = 1
WHERE CustomerContactID IN (SELECT CustomerContactID FROM INSERTED);
END
Within my IF statement, I am using FROM INSERTED, assuming that this will return the newly inserted id for the record that was created by the insert.
I have two questions about this statement:
Will this part of the statement perform an UPDATE just the record
that was just inserted into CustomerContact?
UPDATE CustomerContact
SET Deleted = 1
WHERE CustomerContactID IN (SELECT CustomerContactID FROM INSERTED);
Is this the way that would be deemed correct to make a change to a row that has just been inserted based on the result of a SELECT statement?
CustomerContactID is an auto-incrementing primary key column.
You say "Just the record that was inserted". Inserted can contain more than one record. If there is only one, then your trigger will function as you expect. But if there is more than one, it won't.
I would rewrite your logic into a single update statement along the lines of...
Update CustomerContact
Set Deleted = 1
From CustomerContact
inner join inserted on CustomerContact.CustomerContactID = inserted.CustomerContactID
inner join orders on inserted.OrderID = orders.OrderID
where
-- some criteria.
CREATE TRIGGER mark_cust_contact_deleted ON CustomerContact
AFTER INSERT AS
BEGIN
DECLARE #numrows INT;
/* Determine if order matches criteria for marking customer contact as DELETED immediately */
-- Get all the records into a temp table
SELECT * INTO #Temp
FROM inserted
Declare #ID int;
SELECT #numrows = COUNT(*)
FROM [Order] o
JOIN OrderMeterDetail om
ON o.OrderID = om.OrderID
WHERE o.WorkTypeID = 3 AND o.WorkActionID = 26 AND o.WorkStageID IN (109, 309, 409)
AND om.MeterDetailTypeID = 1 AND om.MeterLocationID IN (2, 4)
AND o.orderid IN (SELECT OrderID FROM #Temp);
IF (#numrows >= 1)
BEGIN
WHILE EXISTS (SELECT TOP 1 * FROM #Temp)
BEGIN
SELECT TOP 1 #ID = ID FROM #Temp
/* If the order matches the criteria, mark the customer contact as deleted */
UPDATE CustomerContact
SET Deleted = 1
WHERE CustomerContactID IN (SELECT CustomerContactID FROM #Temp WHERE ID = #ID);
DELETE FROM #Temp WHERE ID = #ID
END
END
DROP TABLE #Temp
END
I think you can do something like this, tweak the code to futher suit for needs, hope this will help.
Here is the final solution that I used to solve this issue:
CREATE TRIGGER mark_cust_contact_deleted ON CustomerContact
AFTER INSERT AS
BEGIN
UPDATE CustomerContact
SET Deleted = 1
FROM CustomerContact cc
JOIN inserted i
ON cc.CustomerContactID = i.CustomerContactID
JOIN [Order] o
ON i.OrderID = o.OrderID
JOIN OrderMeterDetail om
ON i.OrderID = om.OrderID
WHERE o.WorkTypeID = 3 AND o.WorkActionID = 26 AND o.WorkStageID IN (109, 309, 409)
AND om.MeterDetailTypeID = 1 AND om.MeterLocationID IN (2, 4)
END
I have a SQL Server database and I need to manually do an update query. There for no solutions using any programming language can be used.(stored procedures can be used)
I have 4 tables affected (/used) in the query.
[Orders]
[StatusHistoryForOrder]
[StatusHistory]
[Statuses]
I need to update the field [Orders].[OrderStatusID] which is a foreign key to [Statuses]. (So actually changing the state of the order. The table [StatusHistoryForOrder] is a linking table to [StatusHistory] and only contains 2 colums.
[StatusHistoryForOrder].[OrderId]
[StatusHistoryForOrder].[OrderStatusHistoryid]
Don't say that this is not logically cause I already know that. The company who designed the database is a complete retarded company but the database is now too large to set things straight and there is neither the time or money to do it.
The [StatusHistory] table has multiple columns:
[StatusHistory].[OrderStatusHistoryId]
[StatusHistory].[OrderStatusId]
[StatusHistory].[Date]
[StatusHistory].[Message]
The [StatusHistory].[OrderStatusId] is also a foreign key to [Statuses].
In the update query I need to update the status of the order to status 16. But only on rows that now have status 1 and are older then 60 days. I know I can check the date by using the function
DATEDIFF(DD,[StatusHistory].[Date],GETDATE()) > 60
But how to implement this query if the date field is not in the orders. And to set the new [StatusHistory] a new row has to be made for that table and the [StatusHistoryForOrder] table also needs a new row and the ID of that row needs to be set in the [Orders] table row.
Does anyone know how to do this? I am fairly new to SQL Server (or SQL for that matter) and I have absolutly no clue where to begin.
Conclusion:
I need a stored procedure that first checks every row in [Orders] if the [StatusHistory].[Date] (which is linked to the order using foreign keys) of that order is older that 60. If it is older then a new StatusHistory row must be inserted with the current date and status 16. Then in [StatusHistoryForOrder] a new row must be inserted with the new ID of the statusHistory been set in [StatusHistoryForOrder].[OrderStatusHistoryid] and the order id set in [StatusHistoryForOrder].[OrderId]. And last but not least: The [Orders].[OrderStatusID] also needs to be set to 16.
A select query to select the date and status of the order:
SELECT TOP (100) PERCENT
dbo.Orders.OrderID,
dbo.Statuses.Description AS Status,
dbo.StatusHistory.Date
FROM
dbo.Orders
INNER JOIN
dbo.Statuses
ON
dbo.Orders.OrderStatusID = dbo.Statuses.StatusId
INNER JOIN
dbo.StatusHistoryForOrder
ON
dbo.Orders.OrderID = dbo.StatusHistoryForOrder.OrderId
INNER JOIN
dbo.StatusHistory
ON
dbo.StatusHistoryForOrder.OrderStatusHistoryid = dbo.StatusHistory.OrderStatusHistoryId
WHERE
(dbo.Statuses.StatusId = 1)
AND
(DATEDIFF(DD, dbo.StatusHistory.Date, GETDATE()) > 60)
UPDATE
For #marc_s:
Can anyone help me with that?
Try this CTE (Common Table Expression) to find all those orders - does it work, are the results plausible? (this doesn't update anything just yet - just SELECTing for now):
USE (your database name here)
GO
DECLARE #OrdersToUpdate TABLE (OrderID INT, StatusHistoryID INT, StatusDate DATETIME)
;WITH RelevantOrders AS
(
SELECT
o.OrderId, sh.Date
FROM dbo.Orders o
INNER JOIN dbo.StatusHistoryForOrder ho ON ho.OrderId = o.OrderId
INNER JOIN dbo.StatusHistory sh ON ho.OrderStatusHistoryid = sh.OrderStatusHistoryid
WHERE
sh.Date <= DATEADD(D, -60, GETDATE()) -- older than 60 days back from today
AND o.OrderStatusID = 1 -- status = 1
)
INSERT INTO #OrdersToUpdate(OrderID, StatusDate)
SELECT OrderID, [Date]
FROM RelevantOrders
BEGIN TRANSACTION
BEGIN TRY
DECLARE #OrderIDToInsert INT, -- OrderID to process
#InsertedStatusHistoryID INT -- new ID of the inserted row in StatusHistory
-- grab the first OrderID that needs to be processed
SELECT TOP 1 #OrderIDToInsert = OrderID
FROM #OrdersToUpdate
WHERE StatusHistoryID IS NULL
ORDER BY OrderID
-- as long as there are still more OrderID to be processed ....
WHILE #OrderIDToInsert IS NOT NULL
BEGIN
PRINT 'Now inserting new StatusHistory entry for OrderID = ' + CAST(#OrderIDToInsert AS VARCHAR(10))
INSERT INTO dbo.StatusHistory(OrderStatusID, [Date], [Message])
VALUES(16, GETDATE(), 'Bulk Insert/Update operation') -- enter here whatever you want to store
SELECT #InsertedStatusHistoryID = SCOPE_IDENTITY(); -- grab newly inserted ID
PRINT 'New StatusHistory entry inserted with ID = ' + CAST(#InsertedStatusHistoryID AS VARCHAR(10))
UPDATE #OrdersToUpdate
SET StatusHistoryID = #InsertedStatusHistoryID
WHERE OrderID = #OrderIDToInsert
-- safety - reset #OrderIDToInsert to NULL so that we'll know when we're done
SET #OrderIDToInsert = NULL
-- read next OrderID to be processed
SELECT TOP 1 #OrderIDToInsert = OrderID
FROM #OrdersToUpdate
WHERE StatusHistoryID IS NULL
ORDER BY OrderID
END
-- insert into the StatusHistoryForOrder table
INSERT INTO dbo.StatusHistoryForOrder(OrderID, OrderStatusHistoryID)
SELECT OrderID, StatusHistoryID
FROM #OrdersToUpdate
-- update your Orders to status ID = 16
UPDATE dbo.Orders
SET OrderStatusID = 16
FROM #OrdersToUpdate upd
WHERE dbo.Orders.OrderID = upd.OrderID
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
ROLLBACK TRANSACTION
END CATCH
This CTE basically joins your Orders table to the StatusHistory table (via the intermediate link table) and selects the values you're interested in (hopefully!).
This particular problem seems solvable with set operations only.
DECLARE #Orders TABLE (ID int, rownum int IDENTITY);
DECLARE #StatusHistory TABLE (ID int, rownum int IDENTITY);
/* get the list of orders with expired statuses */
INSERT INTO #Orders (ID)
SELECT o.OrderID
FROM Orders o
INNER JOIN StatusHistoryForOrder shfo ON o.OrderID = shfo.OrderId
INNER JOIN StatusHistory sh ON shfo.OrderStatusHistoryid = sh.OrderStatusHistoryId
GROUP BY o.OrderID
HAVING DATEDIFF(DD, MAX(sh.Date), GETDATE()) > 60
/* add so many new rows to StatusHistory and remember the new IDs */
INSERT INTO StatusHistory (OrderStatusId, Date, Message)
OUTPUT inserted.OrderStatusHistoryId INTO #StatusHistory (ID)
SELECT
16,
GETDATE(),
'Auto-inserted as the previous status has expired'
FROM #Orders
/* join the two temp lists together and add rows to StatusHistoryForOrder */
INSERT INTO StatusHistoryForOrder (OrderId, OrderStatusHistoryid)
SELECT o.ID, sh.ID
FROM #Orders o
INNER JOIN #StatusHistory sh ON o.rownum = sh.rownum
/* finally update the statuses in Orders */
UPDATE Orders
SET OrderStatusID = 16
FROM #Orders o
WHERE Orders.OrderID = o.ID
This should be the body of a single transaction, of course.