SQL Server Update with Inner Join - sql

I have 3 tables (simplified):
tblOrder(OrderId INT)
tblVariety(VarietyId INT,Stock INT)
tblOrderItem(OrderId,VarietyId,Quantity INT)
If I place an order, I drop the stock level using this:
UPDATE tblVariety
SET tblVariety.Stock = tblVariety.Stock - tblOrderItem.Quantity
FROM tblVariety
INNER JOIN tblOrderItem ON tblVariety.VarietyId = tblOrderItem.VarietyId
INNER JOIN tblOrder ON tblOrderItem.OrderId = tblOrder.OrderId
WHERE tblOrder.OrderId = 1
All fine, until there are two rows in tblOrderItem with the same VarietyId for the same OrderId. In this case, only one of the rows is used for the stock update. It seems to be doing a GROUP BY VarietyId in there somehow.
Can anyone shed some light? Many thanks.

My guess is that because you have shown us simplified schema, some info is missing that would determine why have the repeated VarietyID values for a given OrderID.
When you have multiple rows, SQL Server will arbritrarily pick one of them for the update.
If this is the case, you need to group first
UPDATE V
SET
Stock = Stock - foo.SumQuantity
FROM
tblVariety V
JOIN
(SELECT SUM(Quantity) AS SumQuantity, VarietyID
FROM tblOrderItem
JOIN tblOrder ON tblOrderItem.OrderId = tblOrder.OrderId
WHERE tblOrder.OrderId = 1
GROUP BY VarietyID
) foo ON V.VarietyId = foo.VarietyId
If not, then the OrderItems table PK is wrong because if allows duplicate OrderID/VarietyID combinations (The PK should be OrderID/VarietyID, or these should be constrained unique)

From the documentation UPDATE
The results of an UPDATE statement are
undefined if the statement includes a
FROM clause that is not specified in
such a way that only one value is
available for each column occurrence
that is updated (in other words, if
the UPDATE statement is not
deterministic). For example, given the
UPDATE statement in the following
script, both rows in table s meet the
qualifications of the FROM clause in
the UPDATE statement, but it is
undefined which row from s is used to
update the row in table t.
CREATE TABLE s (ColA INT, ColB DECIMAL(10,3))
GO
CREATE TABLE t (ColA INT PRIMARY KEY, ColB DECIMAL(10,3))
GO
INSERT INTO s VALUES(1, 10.0)
INSERT INTO s VALUES(1, 20.0)
INSERT INTO t VALUES(1, 0.0)
GO
UPDATE t
SET t.ColB = t.ColB + s.ColB
FROM t INNER JOIN s ON (t.ColA = s.ColA)
GO

You are doing an update. It will update once.
Edit: to solve, you can add in a subquery that will group your orderitems by orderid and varietyid, with a sum on the amount.

Related

How to insert into a table1 column where another table1 column is the same as table2 column

I am trying to insert the product types into a my table called ro_frames which can be accessed from table ro_st_product where the ro_frames.product_id = ro_st_product.product_id .
For this I use the following query:
insert into ro_frames(product_type)
(SELECT a.producttype
FROM ro_st_product a join ro_frames b on a.product_id = b.product_id
where a.product_id = b.PRODUCT_ID);
It says that:
Action:
1,405 rows inserted.
However when I go to ro_frames the values inside the column product_type is all null. Why is this happening and how do I solve this?
You are inserting new rows. I think you want update:
update ro_frames f
set product_type = (select a.producttype
from ro_st_product p
where p.product_id = f.PRODUCT_ID
);
That said, you shouldn't be storing the same value in two places. You should just use join when you are querying to get the type from the product table.

SQL Server: Is there any way to prevent accidental multiple updates of the same row in a single UPDATE Statement?

When you write an UPDATE statement from a join of two or more tables, there is always a possibility that you accidentally omitted one condition and it may end up updating the same row multiple times and lead to unexpected results, especially when there are complex keys/relationships.
Is there any way to ENSURE that if such situation happens, SQL Server raises an error or gives some kind of warning?
I'm usually careful on those things, but it happened to me few times recently, when I was trying to retrieve data from a not well known to me Database with complex relationships inside.
While my question is about SQL Server, how to prevent this situation, I'd be glad to hear how do you make sure its not happening?
Here is a small made up example of what I mean:
DECLARE #Customers TABLE (Id INT, Name VARCHAR(100), LatestInvoice VARCHAR(100))
DECLARE #Orders TABLE (Id INT, CustomerId INT, Invoice VARCHAR(100), Date DATETIME)
INSERT INTO #Customers (Id, Name)
VALUES (1, 'Customer1')
INSERT INTO #Orders (Id, CustomerId, Invoice, Date)
VALUES (1, 1, 'Invoice 1', '1/1/2019'),
(2, 1, 'Invoice 2', '2/1/2019'),
(3, 1, 'Invoice 3', '3/1/2019')
-- Correct UPDATE
-- one record updates once
UPDATE C
SET LatestInvoice = O.Invoice
FROM #Customers C
JOIN #Orders O ON O.CustomerId = C.Id
WHERE O.Date = '3/1/2019'
-- Incorrect UPDATE
-- one record gets updated 3 times and result of Invoice could be anything
UPDATE C
SET LatestInvoice = O.Invoice
FROM #Customers C
JOIN #Orders O ON O.CustomerId = C.Id
And BTW, how is such UPDATE mistake called?
Thanks a lot!
Not 100% defence, but .. Start designing an UPDATE with a SELECT
SELECT target.PrimaryKey, Count(*)
-- update table expression here
GROUP BY target.PrimaryKey
HAVING Count(*) > 1
For example
SELECT C.id, Count(*)
-- update table expression here
FROM #Customers C
JOIN #Orders O ON O.CustomerId = C.Id
--
GROUP BY C.id
HAVING Count(*) > 1
You can use CROSS APPLY instead of JOIN:
UPDATE C
SET LatestInvoice = O.Invoice
FROM #Customers C CROSS APPLY
(SELECT TOP (1) O.*
FROM #Orders O
WHERE O.CustomerId = C.Id
) O;
This will update once with an arbitrary matching row. You can add an ORDER BY to the subquery to provide more specification on the row that should be used.
EDIT:
I don't think there is a clean way to do this. I don't think there is a built-in function that will return an error from a query (such as throw() or raise_error() in T-SQL code). You can use the handy divide-by-zero error instead:
UPDATE C
SET LatestInvoice = O.Invoice
FROM #Customers C JOIN
(SELECT O.*, COUNT(*) OVER (PARTITION BY O.CustomerId) as cnt
FROM #Orders O
) O
ON O.CustomerId = C.Id
WHERE (CASE WHEN cnt > 1 THEN 1 / 0 ELSE cnt END) = 1;
Looks like if you have an UPDATE which might have multiple references to a target table rows, is the best to use 'MERGE' instead of 'UPDATE'
Unlike non-deterministic UPDATE the standard MERGE statement will
generates an error if multiple source rows match one target row,
requiring to revise the code to make it deterministic.
Here is the conversion of above UPDATE to the MERGE statement and the second one actually errors out!
-- works just fine
MERGE #Customers T
USING #Orders S
ON S.CustomerId = T.Id AND S.Date = '3/1/2019'
WHEN MATCHED
THEN
UPDATE SET LatestInvoice = S.Invoice;
-- omitting Date condition will follow with this error:
-- The MERGE statement attempted to UPDATE or DELETE the same row more than once. This happens when a target row matches more than one source row. A MERGE statement cannot UPDATE/DELETE the same row of the target table multiple times. Refine the ON clause to ensure a target row matches at most one source row, or use the GROUP BY clause to group the source rows.
MERGE #Customers T
USING #Orders S
ON S.CustomerId = T.Id --AND S.Date = '3/1/2019'
WHEN MATCHED
THEN
UPDATE SET LatestInvoice = S.Invoice;
Still, I think MS SQL should have have at least an option for the UPDATE statement to fail in non-deterministic updates as it certainly a mistake and leads to problems.

How to loop statements in SQL Server

I am new to SQL Server, I am trying to do something as follows.
Sample code :
SELECT ITEM_ID
FROM 'TABLE_NAME_1'
WHERE ITEM_STATUS = 'ACTIVE'
SET #ITEM_PRICE = (SELECT PRICE
FROM 'TABLE_NAME_2'
WHERE 'PRODUCT_ID' = 'ITEM_ID')
INSERT INTO 'TABLE_NAME_3' (ITEM_ID, PRICE)
VALUES (#ITEM_ID, #ITEM_PRICE)
The first statement will return multiple rows of ITEM_ID
By using the ITEM_ID I need to select the ITEM_PRICE from another table using the second statement
By using the third statement, I need to insert the data into the third table
Here the first statement returns only one ITEM_ID, then everything is easy to do. I f it returns multiple rows how can I do all these processes for all the ITEM_ID which has returned by the first statement?
Actually, If the first statement returns 5 rows I need to loop 5 times.
Is it possible in SQL Server, if yes, please help me to do this
Question would be why not use a straight SQL
INSERT
INTO 'TABLE_NAME_3'
(ITEM_ID
,PRICE
)
SELECT ITEM_ID,ITEM_PRICE
FROM 'TABLE_NAME_1' A
JOIN 'TABLE_NAME_2' B
ON A.ITEM_ID=B.PRODUCT_ID
WHERE A.ITEM_STATUS = 'ACTIVE'
based on your question i have created sample code you can use only one query to insert multiple data if you want to insert common data between table 1 and table 2 then use inner join or left join will be fine.
Code
INSERT INTO 'TABLE_NAME_3' (ITEM_ID,PRICE)
SELECT T1.ITEM_ID , T2.PRICE
FROM 'TABLE_NAME_1' AS T1
INNER JOIN 'TABLE_NAME_2' AS T2 ON T2.PRODUCT_ID = T1.ITEM_ID
WHERE T1.ITEM_STATUS = 'ACTIVE'

Using update with an inner join SQL SERVER 2012

I needed som guidance as to how I can retrieve a column needed from a table with an INNER JOIN and in the same time update and insert a new column into my original table.
This is what I have written so far
SELECT DISTINCT
a.[CustNo],
X.CustomerID
FROM tblA_Add_CustomerID_Column a
INNER JOIN tblX x
ON x.CustomerCode = a.custno
My Table, tblA_Add_CustomerID_Column, only has a column named CustNo, and with the above query, I want to add CustomerID to Table, tblA_Add_CustomerID_Column.
How can I UPDATE and INSERT the column CustomerID from the table tblX?
My normal approach has always been doing the INNER JOIN , then take the results onto the excel sheet, and then import my excel file to the DB.
you can add the column with something like
alter table tblA_Add_CustomerID_Column add CustomerID int
then you can update the table using an update statement, something like
UPDATE tblA_Add_CustomerID_Column
SET CustomerID = x.cusomterID
FROM tblX x
WHERE custno = x.CustomerCode
You could do this instead:
ALTER TABLE tblA_Add_CustomerID_Column
ADD CustomerID INT;
EXEC('
UPDATE a
SET CustomerID = x.cusomterID
FROM tblA_Add_CustomerID_Column a
INNER JOIN tblX x ON x.CustomerCode = a.custno;
');

constructing complex sql statement

I need to select and update a table looking up values from other tables. The idea is to select some details from order, but get the names of the foreign key values using the id
For the select I tried:
SELECT [Order].order_date,
[Order].total_price,
[Order].order_details,
vehicle.reg_no,
Staff.name,
stock.name
FROM
Order,
vehicle,
staff,
stock
WHERE
order.id = #order_id
AND vehicle.id = Order.vehicle_id
AND staff.id = Order.staff_id
AND stock.id = Order.product_id
for the update that i tried
UPDATE order
SET
total_price = #total_price,
order_detail = #order_detail,
product_id = (select is from stock where name = #product_name),
customer_id = (select id from customer where name = #customer_name),
vehicle_regno = (select reg_no from vehicle where name = #name)
WHERE (id = #id)
both does not return anything. i hope am clear enough to get some help, but if not pls i will provide more info.
thanks for helping
You might want to try converting you INNER JOINs to a LEFT JOIN for the SELECT statement.
SELECT [Order].order_date,
[Order].total_price,
[Order].order_details,
vehicle.reg_no,
Staff.name,
stock.name
FROM
[Order]
LEFT JOIN vehicle
ON vehicle.id = [Order].vehicle_id
LEFT JOIN staff
ON staff.id = [Order].staff_id
LEFT JOIN stock
ON stock.id = [Order].product_id
WHERE
[order].id = #order_id
The reasons why this would make a difference is if either a) you allow nulls in the fk fields or b) you don't have fk contraints which may point to a problem with your design.
Your update statement should update some rows provided that the value of #id exists in the ORDER table but as #Danny Varod already commented you won't get rows back only the number of rows affected
You are comparing stock.id with Order.product_id, could that be the problem?
Otherwise why it is not returning any rows we would need to know the content of the tables, perhaps you need a left join instead of inner join to some of them?