Update table from another table in SQL is giving an unexpected result - sql

Table Items :
ItemCode---Name-------Stock
111-----------Book---------10
112-----------Bag-----------03
113-----------Pen-----------08
114-----------Pencil--------13
Table INVOICEITEMS(SoldItems) :
BillNumber---ItemCode-----UnitValue(Qty)
1005-----------111------------------3
1005-----------111------------------2
1005-----------113------------------4
1006-----------111------------------6
1007-----------112------------------5
We need to update the ITEMS table from INVOICEITEMS table.
We need to add to the Stock of each ItemCode in ITEMS table from the sum of the UnitValues of BillNumber '1005' in INVOICEITEMS.
Query used :
UPDATE ITEMS SET Stock=Stock+(SELECT SUM(T.UnitValue) FROM INVOICEITEMS T WHERE T.BillNumber LIKE '1005' AND T.ItemCode LIKE ItemCode) WHERE ItemCode IN(SELECT J.ItemCode FROM INVOICEITEMS J WHERE J.BillNumber LIKE '1005')
Expected Result After updation :
Table ITEMS
ItemCode---Name----Stock
111-----------Book-------15
112-----------Bag---------03
113-----------Pen---------12
114-----------Pencil------13
Result we are getting:
Table ITEMS
ItemCode---Name----Stock
111-----------Book--------19
112-----------Bag---------03
113-----------Pen---------17
114-----------Pencil-------13
Please help in correcting this query.

One way to investigate problems with update statements is to convert them to select statements. For your update statement, I come up with the following query:
SELECT Items.*
,(SELECT SUM(T.UnitValue) FROM INVOICEITEMS T WHERE T.BillNumber LIKE '1005' AND T.ItemCode LIKE ItemCode)
FROM Items
WHERE ItemCode IN(SELECT J.ItemCode FROM INVOICEITEMS J WHERE J.BillNumber LIKE '1005')
As you can see, the last column is 9 for every row, which isn't surprising because you have not done anything to return different values based on which row you're looking at.
You can then change the select statement to return the data you do want:
SELECT Items.ItemCode
,Items.Stock
,SUM(T.UnitValue)
,Items.Stock + SUM(T.UnitValue)
FROM Items
JOIN INVOICEITEMS T ON T.ItemCode = Items.ItemCode
WHERE T.BillNumber = 1005
GROUP BY Items.ItemCode, Items.Stock
And finally you can incorporate that back into an UPDATE statement:
UPDATE Items SET Stock = NewSum
FROM Items JOIN
(SELECT Items.ItemCode
,Items.Stock
,Items.Stock + SUM(INVOICEITEMS.UnitValue) NewSum
FROM Items
JOIN INVOICEITEMS ON INVOICEITEMS.ItemCode = Items.ItemCode
WHERE INVOICEITEMS.BillNumber = 1005
GROUP BY Items.ItemCode, Items.Stock
) T ON T.ItemCode = Items.ItemCode
It might then be prudent to extract hard-coded values from the sub-query and make them part of the higher-level query. One reason this might be a good idea is because then the sub-query is more portable. Then you'd end up with this:
UPDATE Items SET Stock = NewSum
FROM Items JOIN
(SELECT Items.ItemCode
,INVOICEITEMS.BillNumber
,Items.Stock
,SUM(INVOICEITEMS.UnitValue) InvoiceQty
,Items.Stock + SUM(INVOICEITEMS.UnitValue) NewSum
FROM Items
JOIN INVOICEITEMS ON INVOICEITEMS.ItemCode = Items.ItemCode
GROUP BY Items.ItemCode, Items.Stock, INVOICEITEMS.BillNumber
) T ON T.ItemCode = Items.ItemCode
WHERE T.BillNumber = 1005

AND T.ItemCode LIKE ItemCode line in the SQL is not getting executed as ItemCode hasn't been specified in the SQL before the execution of the inner SQL statement.
Associate ItemCode explicitly with the ITEMS table and it would work.

Well, in this case you could use a Stored Procedure or a Function:
e.g. Define a Function
CREATE FUNCTION getItemSum(#BillID int, #ItemID int)
RETURNS int
AS
-- Returns the stock level for the product.
BEGIN
DECLARE #ret int;
SELECT #ret = SUM(UnitValue)
FROM INVOICEITEM p
WHERE p.ITEMCODE = #ItemID
AND p.BillNumber = #BillID;
IF (#ret IS NULL)
SET #ret = 0;
RETURN #ret;
END;
GO
And Call SQL Statement like this:
UPDATE ITEMS
SET Stock=Stock + getItemSum(1005, ItemCode)
GO

One way to do this is to use a join with a sub-query:
UPDATE items
SET Stock += item_qty_sum
FROM items i
INNER JOIN (
SELECT itemcode, SUM(unitvalue) AS item_qty_sum
FROM INVOICEITEMS
WHERE BillNumber = 1005
GROUP BY ItemCode
) ii
ON ii.ItemCode = i.ItemCode
Sample SQL Fiddle showing the query in action giving the desired results.

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...

Adding value of COUNT to an already existing value

I have a table for managing inventory movements and a table to manage stock.
I want to add the count of movements that the item exists in between given dates to the stock table.
My update statement looks like this:
UPDATE inventory
SET quantity = quantity + 1
WHERE ItemID IN
( SELECT ItemID FROM movements
WHERE group = '3' AND store = '500'
AND DateMove BETWEEN 20201219 AND 20201223 )
AND StoreNumber = '500'
How can I change this query to add the amount of times that the ItemID appears in movements to the quantity in the inventory table?
I've been thinking that I can add a count(itemID) and group by and add an alias in the subquery and use the alias after the + but it doesn't seem to work.
Thanks for any help
Using an UPDATE, you can use a correlated subquery:
UPDATE inventory i
SET quantity = (i.quantity +
(SELECT COUNT(*)
FROM movements m
WHERE m.ItemId = i.ItemId AND
m.group = 3 AND m.store = i.store AND
m.DateMove BETWEEN 20201219 AND 20201223
)
)
WHERE i.store = 500 AND
EXISTS (SELECT 1
FROM movements m
WHERE m.ItemId = i.ItemId AND
m.group = 3 AND m.store = 500 AND
m.DateMove BETWEEN 20201219 AND 20201223
);
Note that I removed the single quotes around 500 and 3. These values look like numbers. Only use the single quotes if they are strings.
Oracle also allows you to update using a subquery under some circumstances, so this should work as well:
update (select i.*,
(SELECT COUNT(*)
FROM movements m
WHERE m.ItemId = i.ItemId AND
m.group = 3 AND m.store = i.store AND
m.DateMove BETWEEN 20201219 AND 20201223
) as inc_quantity
from inventory i
where store = 500
)
set quantity = quantity + inc_quantity
where quantity > 0;
You appear to want a MERGE statement and to COUNT the movements:
MERGE INTO inventory dst
USING (
SELECT ItemID,
store,
COUNT(*) AS num_movements
FROM movements
WHERE group = 3
AND store = 500
AND DateMove BETWEEN DATE '2020-12-19' AND DATE '2020-12-23'
GROUP BY
ItemId,
store
) src
ON ( src.ItemId = dst.ItemId AND dst.StoreNumber = src.store )
WHEN MATCHED THEN
UPDATE
SET quantity = dst.quantity + src.num_movements;
(Also, if values are numbers then use number literals and not string literals and, for dates, use date literals and not numbers.)
You need a correlated subquery. For brevity, I've omitted all other where conditions.
UPDATE inventory AS inv
SET quantity = quantity + (SELECT COUNT(*) FROM movements AS mov WHERE mov.itemID = inv.itemID);
I think it's better to create a view such as
CREATE OR REPLACE VIEW v_inventory AS
SELECT i.*,
SUM(CASE
WHEN i.StoreNumber = 500 AND m."group" = 3 AND m.store = 500 AND
m.DateMove BETWEEN date'2020-12-19' AND date'2020-12-23'
THEN
1
ELSE
0
END) OVER (PARTITION BY m.ItemID) AS mov_quantity
FROM inventory i
LEFT JOIN movements m
ON m.ItemID = i.ItemID
rather than applying a DML for the sake of good db design, since the desired count already can be calculated, and may yield for later confusions

Needing to access table from subquery in main update query

I'm in need of a way to access a column from the inner table from a subquery, which I have included below with database/table names changed. The inner query returns all of the Item_Ids of Items that have a specific term config and belong to a specific customer. Obviously, the SET currently doesn't work as I can't access any of the rows of let from the main query.
I'm fairly new to SQL and am struggling to wrap my head around how I might convert this to using joins instead of a subquery. The problem is that I need to be able to set ALL of the Grand Totals for any of the Item_Ids that come from the subquery.
UPDATE [Database].[dbo].[Items]
SET GrandTotal = GrandTotal / let.CurrentValue
WHERE Id IN (
SELECT let.Item_Id
FROM [Database].[dbo].[ItemTerms] let
WHERE TermConfig_Id = 'TERM_CONFIG_ID'
AND Item_Id IN (
SELECT le2.Id
FROM [Database].[dbo].[LaneExhibits] le2
WHERE Customer_Id = 'CUST_ID'
)
)
EDIT: Add Sample Row from Subquery
Columns are Id, DataValueStatus, CurrentValue, PreviousValue, Item_Id, TermConfig_Id. The subquery only returns the Item_Id but that is the rest of the data.
424C8BF4-0FCB-E711-80C9-005056BA0972 1 460 NULL 2D4C8BF4-0FCB-E711-80C9-005056BA0972 B8FCE730-27BE-E711-80C9-005056BA0972
The inner join version:
UPDATE i
SET GrandTotal = GrandTotal / let.CurrentValue
FROM [Database].[dbo].[Items] i
INNER JOIN [Database].[dbo].[ItemTerms] let ON i.Id=let.Item_Id
WHERE TermConfig_Id = 'TERM_CONFIG_ID'
AND Item_Id IN (
SELECT le2.Id
FROM [Database].[dbo].[LaneExhibits] le2
WHERE Customer_Id = 'CUST_ID'
)
Merge is your friend https://learn.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-2017
simplified sample from your example
MERGE [Database].[dbo].[Items] AS target
USING (SELECT let.Item_Id, let.CurrentValue FROM [Database].[dbo].[ItemTerms] let
/* other joins and filters here */
) AS source (Item_Id, CurrentValue)
ON (target.Item_Id = source.id /* or what you want */)
WHEN MATCHED THEN
UPDATE SET GrandTotal = source.GrandTotal / source.CurrentValue

Return single row on case statement on left outer joined table

I have the following (simplified) tables
Table Products
ID
Title
Table Data_Tags
ID
ForeignID
Tag
There are two tags added for product ID 1: 'solid' and 'strong'
Query below will return 2 rows and if I add DISTINCT it will only return 1 row as expected.
SELECT Products.ID, Products.Title
FROM Products LEFT OUTER JOIN Data_Tags ON Products.ID = Data_Tags.ForeignID
WHERE (Products.ID = 1)
I would like to add a CASE statement to the query to calculate a specific relevance to certain keywords
CASE WHEN CONTAINS(Data_tags.tag, 'solid') THEN 100 ELSE 0 END AS TagsMatch
Query will become this:
SELECT DISTINCT Products.ID, Products.Title, CASE WHEN CONTAINS(Data_tags.tag, 'solid') THEN 100 ELSE 0 END AS TagsMatch
FROM Products LEFT OUTER JOIN Data_Tags ON Products.ID = Data_Tags.ForeignID
WHERE (Products.ID = 1)
When running the query it will return two rows, which makes perfect sense as it will output 100 for tag 'solid' (match) and 0 for tag 'strong' (no match)
However I am unsure on how to modify the query to only return a single row with value 0 if none of the related tags are a match and 100 if any of the related tags is a match.
I would also like to avoid subqueries to keep performance best as possible.
I am using MS SQL Server 2014.
Please advise. Thank you very much!
I have been trying to find a solution for your question. It turns out 1000111 was correct. You cannot use Contains without full-text search... If you replace Contains() with like I think this should solve your problem. Here is what I did:
Declare #Products table
(ID int,
Title varchar(50))
insert into #Products values (1, 'Test')
Declare #Data_Tags table
(ID int,
ForeignID int,
Tag varchar(50))
insert into #Data_Tags values (1, 1, 'solid')
insert into #Data_Tags values (2, 1, 'strong')
SELECT DISTINCT Products.ID, Products.Title, max(CASE WHEN Data_tags.tag like '%stttt%' THEN 100 ELSE 0 END) AS TagsMatch
FROM #Products Products LEFT OUTER JOIN #Data_Tags Data_Tags ON Products.ID = Data_Tags.ForeignID
WHERE (Products.ID = 1)
group by Products.ID, Products.Title
The above return 0, when you change '%stttt%' to '%solid%' it returns 100.
Let me know if this helped.
SELECT Products.ID, Products.Title, max(CASE WHEN Data_tags.tag = 'solid' THEN 100 ELSE 0 END) AS TagsMatch
FROM #Products Products
LEFT OUTER JOIN #Data_Tags Data_Tags
ON Products.ID = Data_Tags.ForeignID
WHERE (Products.ID = 1)
group by Products.ID, Products.Title

Error using Update Join statement from a Table Variable

If I make a table variable here:
declare #Table Table (ProductID int, Color = varchar(60))
Then populate it of course, and try to use it in an Update with Join statement like below, I get errors.
UPDATE [Product]
SET [Product].[Active] = 1
,[Product].[Color] = t.[Color]
INNER JOIN #Table t
ON t.[ProductID] = [Product].[ProductID]
Error:
Msg 156, Level 15, State 1, Procedure
Incorrect syntax near the keyword 'INNER'.
Any suggestions how to do this?
You can also do this (works on SQL Server and all ANSI SQL conforming database):
UPDATE [Product] SET
[Product].[Active] = 1
,[Product].[Color] = t.[Color]
FROM #Table t
WHERE t.[ProductID] = [Product].[ProductID]
There's something to be desired from SQL Server's proprietary UPDATE FROM though, you can easily change the UPDATE statement to make it match whole table regardless if there's no matching rows.
It's easy to fashion this matched-rows-only update... http://www.sqlfiddle.com/#!3/8b5a3/26
update p SET
Qty = t.Qty
from Product p
inner join Latest t
on t.ProductId = p.ProductId;
...to an UPDATE that matches all rows, you merely change the INNER JOIN to LEFT JOIN: http://www.sqlfiddle.com/#!3/8b5a3/27
update p SET
Qty = ISNULL(t.Qty,0)
from Product p
left join Latest t
on t.ProductId = p.ProductId;
select * from Product;
Whereas if you want to fashion the ANSI SQL UPDATE with matched-rows-only ... http://www.sqlfiddle.com/#!3/8b5a3/28
update Product SET
Qty = t.Qty
from Latest t
where t.ProductId = Product.ProductId
...to UPDATE statement that matches all rows, you have to adjust your query a bit: http://www.sqlfiddle.com/#!3/8b5a3/29
update Product SET
Qty = ISNULL(t.Qty, 0)
from
(
select x.ProductId, lat.Qty
from Product x
left join Latest lat on lat.ProductId = x.ProductId
) as t
where t.ProductId = Product.ProductId;
Though as most choices in development, one should weigh the pros and cons in terms of code readability/maintainability against flexibility
Data sample:
create table Product
(
ProductId int primary key not null,
Name varchar(50) not null,
Qty int not null
);
insert into Product(Name,Qty) values
(1,'CAR',1),
(2,'Computer',1000),
(3,'Shoes',2);
create table Latest
(
ProductId int primary key not null,
Qty int not null
);
insert into Latest(ProductId, Qty) values
(2,2000),
(3,3);