Update With duplicate key - sql

I have one temp table a with idproduct and Qty like
idproduct Qty
123 2
123 2
And anoth table b like
idproduct stock
123 10
Then i want to update (b.stock-a.qty)
So i want result like
idproduct stock
123 6
But it give me reult like
**idproduct stock
123 8**
From comment:
UPDATE INV
SET INV.stock = isnull(INV.stock,0) - ISNULL(TEMP.QTY,0)
FROM INVENTORY INV, #TempBagPack TEMP
WHERE INV.idproduct = TEMP.idproduct

The problem in your query is clearly mentioned by #Gordon Linoff.
This is just another way of scripting using CTE.
;with cte_1
As
(select idproduct, sum(qty) as qty
from #TempBagPack t
group by idproduct
)
update i
set i.stock = coalesce(i.stock, 0) - coalesce(t.qty, 0)
from Cte_1 t
Join inventory i
on i.idproduct = t.idproduct;

First. Never use commas in the FROM clause. Always use explicit JOIN syntax.
Second, your problem is that multiple rows match for the update, but SQL Server only updates the row once. This is well documented.
The solution is to pre-aggregate the data:
update i
set i.stock = coalesce(i.stock, 0) - coalesce(t.qty, 0)
from inventory i join
(select idproduct, sum(qty) as qty
from #TempBagPack t
group by idproduct
) t
on i.idproduct = t.idproduct;
The warning in the documentation is:
Use caution when specifying the FROM clause to provide the criteria
for the update operation. 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, that is if the UPDATE statement is
not deterministic.

Related

using correlated subquery in the case statement

I’m trying to use a correlated subquery in my sql code and I can't wrap my head around what I'm doing wrong. A brief description about the code and what I'm trying to do:
The code consists of a big query (ALIASED AS A) which result set looks like a list of customer IDs, offer IDs and response status name ("SOLD","SELLING","IRRELEVANT","NO ANSWER" etc.) of each customer to each offer. The customers IDs and the responses in the result set are non-unique, since more than one offer can be made to each customer, and a customer can have different response for different offers.
The goal is to generate a list of distinct customer IDs and to mark each ID with 0 or 1 flag :
if the ID has AT LEAST ONE offer with status name is "SOLD" or "SELLING" the flag should be 1 otherwise 0. Since each customer has an array of different responses, what I'm trying to do is to check if "SOLD" or "SELLING" appears in this array for each customer ID, using correlated subquery in the case statement and aliasing the big underlying query named A with A1 this time:
select distinct
A.customer_ID,
case when 'SOLD' in (select distinct A1.response from A as A1
where A.customer_ID = A1.customer_ID) OR
'SELLING' in (select distinct A1.response from A as A1
where A.customer_ID = A1.customer_ID)
then 1 else 0 end as FLAG
FROM
(select …) A
What I get is a mistake alert saying there is no such object as A or A1.
Thanks in advance for the help!
You can use exists with cte :
with cte as (
<query here>
)
select c.*,
(case when exists (select 1
from cte c1
where c1.customer_ID = c.customer_ID and
c1.response in ('sold', 'selling')
)
then 1 else 0
end) as flag
from cte c;
You can also do aggregation :
select customer_id,
max(case when a.response in ('sold', 'selling') then 1 else 0 end) as flag
from < query here > a;
group by customer_id;
With statement as suggested by Yogesh is a good option. If you have any performance issues with "WITH" statement. you can create a volatile table and use columns from volatile table in your select statement .
create voltaile table as (select response from where response in ('SOLD','SELLING').
SELECT from customer table < and join voltaile table>.
The only disadvantge here is volatile tables cannot be accessed after you disconnect from session.

Conditional Output in SQL

I have a table (lets call it "Items") that contains a set of names, groups and statuses. For example
Name | Group | Status
FF A ON
GG A OFF
HH A UNKN
ZZ B ON
YY B OFF
I am trying to aggregate the status of all records in a given group, by taking the most relevant status (in order by relevance: UNKN, OFF, ON).
Edit 1: These statuses are only examples, and their names and orders could change in my application, so that should be configurable in the query.
For example, if I query for the overall status of Group A, the status should be UNKN, and if I query for Group B, the status should be OFF.
Edit 2: It is possible that there are multiples of the same status for a group. For example two records that are UNKN.
The query I have managed is to select all items from a group. For example Group A:
SELECT Items.[Group], Items.[Status]
FROM Items
WHERE (((Items.[Group])="A"));
Produces:
Name | Group | Status
FF A ON
GG A OFF
HH A UNKN
but I can't boil it down to the single most relevant status for every group. I have tried to use CASE WHEN and IF EXISTS but I can't get it to work. Any input?
Edit 3:
As an example of the desired output for the overall group status:
Group | OverallStatus
A UNKN
B OFF
If you can build another table, a simple solution would be:
Add another table with the values in the order you want.
Then, just build a query like this:
SELECT TOP 1 Table1.*, Table2.VALUE
FROM Table1 INNER JOIN Table2 ON Table1.status = Table2.STATUS
WHERE Table1.group="A"
ORDER BY Tabla2.VALUE DESC
If the status changes or are added new ones, or you need a new order, just refresh the new table.
EDIT
Acording to the new info by OP, the query can be write in another way. The previous query take into account showing all the record in table1.
If you only need the group and the "max" status, you can use something like this:
SELECT A.group, Table2.STATUS
FROM (SELECT Table1.group, Max(Table2.VALUE) AS MaxVALUE
FROM Table1 INNER JOIN Table2 ON Tabla1.status = Table2.STATUS
GROUP BY Table1.group) as A INNER JOIN Table2 ON A.MaxVALUE= Table2.VALUE;
Use conditional aggregation and some other logic:
select grp,
switch(sum(iif(status = "UNK", 1 0) > 0, "UNK", -- any unknowns
sum(iif(status = "OFF", 1, 0) > 0, "OFF", -- any offs
"ON"
) as group_status
from items
group by grp;
This counts the number of each status and then uses that to determine the overall group status. Your question is not really explicit about the rules, but I think these capture what you are trying to do. It should be easy enough to modify for other rules.
Using the "length" as gauge, this should fit Access SQL:
Select *
From Items
Where Items.Name = (
Select Top 1 T.Name
From Items As T
Where T.Group = Items.Group
Order By Len(T.Status) Desc)
Assuming that the status column will have only three distinct values as shown in data example, you can try below query:
SELECT *
FROM ITEMS
WHERE (GROUP,LENGTH(STATUS)) IN (
SELECT GROUP,MAX(LENGTH(STATUS))
FROM ITEMS
GROUP BY GROUP)
Thanks,
Amitabh

How to update specific rows based on identified rows

have identified specific rows based on unique id in the data. I want to update those rows one column. Trying to use update command but its not working
UPDATE L03_A_AVOX_DATA
SET PWC_Exclusion_Flag =
(CASE
WHEN (L03_A_AVOX_DATA.PWC_SEQ_AVOX IN
(SELECT PWC_SEQ_AVOX
FROM L03_A_AVOX_DATA
WHERE client_id IN
(SELECT DISTINCT client_id
FROM ( SELECT DISTINCT
client_id,
extract_type,
COUNT (*)
FROM temp
GROUP BY client_id,
extract_type
HAVING COUNT (*) = 1))
AND extract_type = '0'))
THEN
1
ELSE
L03_A_AVOX_DATA.PWC_Exclusion_Flag
END )
Can anyone help me
You should simplify this statement by trying to simulate an UPDATE with JOIN.
For more details see here:
Update statement with inner join on Oracle
This idea should work for your case too.
So those records which have counterparts in the temp table, you update them.
Those which don't have counterparts - seems you don't want to update them anyway.
You're trying to update the PWC_Exclusion_Flag to 1 if the client_id has exactly 1 record of extract_type 0 in the temp table, am I right?
Try this:
update L03_A_AVOX_DATA
set PWC_Exclusion_Flag = 1
where client_id in (
select client_id
from temp
where extract_type = '0'
group by client_id
having count(1) = 1
);
This also leaves the other records in L03_A_AVOX_DATA untouched.

SQL UPDATE: integer divided by count of a specified value

SQL Server 2008:
Supposing a table of customers, and a column called "Shipping_State". I want to split the $10,000 spent on shipping costs equally amongst all customers who have Shipping_State = Ohio value, so if there's 2 in Ohio 1 month, it'll be 5,000 a piece, if there's 100 the next month, it'll be 100 a piece.
I have a blank column in the table named Cost for that calculated value. Cost is a decimal(18,4) data type. I'd like to be able to use the query for any data types (usually nchar).
How would I accomplish this? My incorrect code in SQL Server Mgmt Studio returns the message:
Msg 157, Level 15, State 1, Line 1 An aggregate may not appear in the
set list of an UPDATE statement.
UPDATE CustomerTable
SET Cost = (10000 / COUNT(CustomerTable.Shipping_State))
WHERE CustomerTable.Shipping_State = 'Ohio';
Use nested SELECT.
UPDATE CustomerTable
SET Cost = (SELECT 10000.0 / count(*)
FROM CustomerTable
WHERE CustomerTable.Shipping_state = 'Ohio')
WHERE CustomerTable.Shipping_State = 'Ohio';
You would need to do a sub-query to get the count, and then update based on this value, something like this should work:
UPDATE CustomerTable
SET Cost = (10000 / CTCount.Shipping_State_Count)
FROM CustomerTable CT
INNER JOIN (
SELECT Shipping_State, COUNT(Shipping_State) AS Shipping_State_Count
FROM CustomerTable
GROUP BY Shipping_State) CTCount ON
CT.Shipping_State = CTCount.Shipping_State
WHERE CT.Shipping_State = 'Ohio';
SQL Server offers two things that really help with this type of query. The first is updatable CTEs and the second are window functions.
with toupdate as (
select ct.*, count(*) over (partition by ct.Shipping_State) as cnt
from CustomerTable
where ct.Shipping_State = 'Ohio'
)
update toupdate
set Cost = cast(10000 as float) / cnt;
Note that 10000 is cast to a floating point number. SQL Server does integer division, and I presume you want integers here (actually, money would probably be a better data type).
It is unclear how "month" fits in, but this might be closer to what you are looking for:
with toupdate as (
select ct.*, count(*) over (partition by ct.Shipping_State, month(ct.Shipping_Date) as cnt
from CustomerTable
where ct.Shipping_State = 'Ohio'
)
update toupdate
set Cost = cast(10000 as float) / cnt;
Note the change to the partition by clause.

subquery the same table in select statement

I have a resturant db and I need to total up the total value of all the items sold individually. So if I sold a hamburger that has a base price of $10.00 with bacon which costs $1.00 and a hambuger(again $10.00) with avacado that costs $0.50 I need to get $21.50 returned. My invoice table looks like this:
invoice_num item_num price item_id parent_item_id
111 hmbg 10.00 guid_1 ''
111 bacn 1.00 guid_2 guid_2
112 hmbg 10.00 guid_3 ''
112 avcd 0.50 guid_4 guid_3
I can get the sum of all the parent items like this:
SELECT item_num, SUM(price) FROM invoices WHERE parent_item_id = ''
it is the adding of the toppings that is confusing me. I feel like I need to add a subquery in the SUM but I'm not sure how to go about doing it and referencing the original query to use the item_id.
SELECT item_num, sum(i.price) + sum(nvl(x.ingred_price,0))
FROM invoices i
LEFT OUTER JOIN
(SELECT parent_item_id
, sum(price) ingred_price
FROM invoices
WHERE parent_item_id IS NOT NULL
GROUP BY parent_item_id) x
ON x.parent_item_id = i.item_id
WHERE i.parent_item_id IS NULL
GROUP BY item_num
Here's a SQL Fiddle that proves the above code works. I used Oracle, but you should be able to adapt it to whatever DB you are using.
Assumption: You don't have more than one level in a parent child relationship. E.g. A can have a child B, but B won't have any other children.
Not clear based on your question (see my comment) but as I understand it a simple group by will give you what you want. If not please explain (in the original question) why does this query does not work --- what is it missing from your requirements?
SELECT item_num, SUM(price)
FROM invoices
GROUP BY item_num
Hard to say, but looks like you need recursive cte.
Here's example for PostgreSQL:
with recursive cte as (
select
t.invoice_num, t.price, t.item_id, t.item_num
from Table1 as t
where t.parent_item_id is null
union all
select
t.invoice_num, t.price, t.item_id, c.item_num
from Table1 as t
inner join cte as c on c.item_id = t.parent_item_id
)
select invoice_num, item_num, sum(price)
from cte
group by invoice_num, item_num
sql fiddle demo
I've used null for empty parent_item_id (it's better solution than using empty strings), but you can change this to ''.