Update Table From Select - sql

I am using ms-sql server. I have table which I want to update from select statement. For example the table which I want to update is Table_A with 2 rows in it. The update statement from which I want to update Table_A return 10 rows. So I want to update Table_A 10 times. The problem is that Table_A is updated 2 times(the count of rows in Table_A).
Example:
CREATE TABLE #tmp
(
AccountID INT,
Inflow DECIMAL(10,2)
)
DECLARE #n INT = 0
WHILE (#n <10 )
BEGIN
INSERT INTO #tmp SELECT 2, 10
SET #n += 1
END
UPDATE dbo.Table_A
SET Balance += sss.Inflow
FROM ( SELECT t.AccountID ,
t.Inflow
FROM #tmp AS t
) AS sss
WHERE dbo.tAccount.AccountID = sss.AccountID;
-- Updates only 2 times
-- What I expected here is Table_A to be updated as many times as the count of the select statement which is 10, based on the insert before.

Your expectation is wrong. Admittedly, the documentation buries this idea:
The example runs without error, but each SalesYTD value is updated
with only one sale, regardless of how many sales actually occurred on
that day. This is because a single UPDATE statement never updates the
same row two times.
The documentation continues with the solution:
In the situation in which more than one sale for a specified
salesperson can occur on the same day, all the sales for each sales
person must be aggregated together within the UPDATE statement, as
shown in the following example:
So, simply aggregate before doing the join:
UPDATE dbo.Table_A
SET Balance += sss.Inflow
FROM (SELECT t.AccountID, SUM(t.Inflow) as Inflow
FROM #tmp t
GROUP BY t.AccountId
) sss
WHERE dbo.tAccount.AccountID = sss.AccountID;
Note you can also write this as:
UPDATE a
SET Balance += sss.Inflow
FROM dbo.Table_A a JOIN
(SELECT t.AccountID, SUM(t.Inflow) as Inflow
FROM #tmp t
GROUP BY t.AccountId
) sss
ON a.AccountID = sss.AccountID;
This makes the JOIN more explicit.

Related

T-SQL UPDATE statement affects less records than select statement

I am trying to update a DateTime column of one table with a date column from another table.
Before updating it, I am getting the records affected in order to see previously what records will be affected in the UPDATE. So I perform a SELECT statement with below WHERE clause:
NOTE:
DateTimeField is of type DateTime
DateField is of type Date
Code:
SELECT tblToUpdate.*
FROM MyTable1 tblToUpdate
INNER JOIN MyTable2 fromTbl on tblToUpdate.Id = fromTbl.Id
WHERE
ISNULL(fromTbl.DateField, GETDATE()) >= DATEFROMPARTS(1753, 1, 1)
AND ((fromTbl.DateField IS NOT NULL AND tblToUpdate.DateTimeField IS NULL)
OR
(fromTbl.DateField IS NULL AND tblToUpdate.DateTimeField IS NOT NULL)
OR
fromTbl.DateField <> CAST(tblToUpdate.DateTimeField AS DATE))
UPDATE tblToUpdate
SET tblToUpdate.DateTimeField = fromTbl.DateField
FROM MyTable1 tblToUpdate
INNER JOIN MyTable2 fromTbl ON tblToUpdate.Id = fromTbl.Id
WHERE
ISNULL(fromTbl.DateField, GETDATE()) >= DATEFROMPARTS(1753, 1, 1)
AND (
(fromTbl.DateField IS NOT NULL AND tblToUpdate.DateTimeField IS NULL)
OR
(fromTbl.DateField IS NULL AND tblToUpdate.DateTimeField IS NOT NULL)
OR
fromTbl.DateField <> CAST(tblToUpdate.DateTimeField AS DATE)
)
Note that I check DateField in Where clause to be in DateTime range before I update it.
The problem is that the number of records returned by the SELECT statement is not the same as the number of records affected returned by UPDATE statement.
The UPDATE statement affects fewer records than the SELECT statement returns.
Why is it happening if from and where clause are the same in both statements?
I think the number of records returned by SELECT and affected by UPDATE statements respectively should be the same.
This is easy to demonstrate. When there are multiple rows meeting the join predicates you will get differing row counts from a select and an update.
create table Header(HeadID int identity, Name varchar(50))
insert Header select 'test'
create table Details(DetailsID int identity, HeadID int, Name varchar(50))
insert Details values(1, 'asdf'),(1,'qwer')
select * --this returns 2 rows
from Header h
join Details d on d.HeadID = h.HeadID
update h --only 1 row affected
set Name = 'what?'
from Header h
join Details d on d.HeadID = h.HeadID

The aggregate expression cannot be used in the WHERE clause

I have the following tables in my database:
The first table is named Amount, second Product, third Purchase.
And I should to create the trigger on insert to amount table. For example, I'll insert the following values: 4, 1, 10, where 4 is id_purchase, 1 is id_product and 4 is amount of this products. And trigger should subtract this amount from Amount_On_Stock. In my example, it should be: was 48, became 38.
Here's the code of my trigger:
CREATE TRIGGER AmountInsert ON Amount
AFTER INSERT
AS
BEGIN
UPDATE Product
SET Amount_On_Stock = (
SELECT
Amount_On_Stock
FROM Product
WHERE ID_Product = (
SELECT
MAX(ID_Product)
FROM Purchase
WHERE ID_Purchase = (
SELECT
MAX(ID_Purchase)
FROM Purchase
)
)
)-(
SELECT
Amount
FROM AMOUNT
WHERE ID_Product = (
SELECT
MAX(ID_Product)
FROM Purchase
WHERE ID_Purchase = (
SELECT
MAX(ID_Purchase)
FROM Purchase
)
)
)
END
But when I try to create this trigger I have the following error:
The aggregate expression cannot be used in the WHERE clause unless it
is contained in a subquery of the HAVING clause or in the select list,
and the column being aggregated is not an external reference.
So, how can I solve this problem?
Your trigger looks nothing like a SQL Server trigger. I would expect your trigger to look more like this:
CREATE TRIGGER AmountInsert ON Amount AFTER INSERT
AS
BEGIN
UPDATE p
SET Amount_On_Stock = p.Amount_On_Stock - i.amount
FROM Product p JOIN
inserted i
ON p.ID_Product = i.ID_Product;
END;
However, this will not do the right thing if you have multiple inserts on the same product at the same time. To handle that you need aggregation:
CREATE TRIGGER AmountInsert ON Amount AFTER INSERT
AS
BEGIN
UPDATE p
SET Amount_On_Stock = p.Amount_On_Stock - i.amount
FROM Product p JOIN
(SELECT i.ID_Product, SUM(i.amount) as amount
FROM inserted i
GROUP BY i.ID_Product
) i
ON p.ID_Product = i.ID_Product;
END;

SQL Server trigger affect 1 row in a column

I am new to triggers and am having a bit of a problem. I am trying to create a trigger to add the new inserted value of 90 in table sales to the the total sales for id 100 in salesYTD in table internetServices. It seems to do the calculation correctly but it is only suppose to affect the row with the id of 100. Sadly it seems to be changing it for every salesYTD.
CREATE TRIGGER InsertTrigger
ON Sales
AFTER INSERT As
UPDATE InternetServices
SET SalesYTD = (SELECT SUM(Amount)
FROM Sales
WHERE ServiceID = 100)
WHERE ServiceID = 100;
GO
Print 'Master table Before Insert'
Select * From InternetServices
Print 'After Insert'
INSERT INTO Sales VALUES( 11, '2012-11-14' , 90 , 100 );
Select * From InternetServices
Not sure if I gave enough information this is my first time posting a SQL question. Please don't rate down just let me know and I will update it. Thank you.
You need to use INSERTED table. so its add the inserted amount(in table sales) to existing amount in
column SalesYTD of table InternetServices .
CREATE TRIGGER InsertTrigger
ON Sales
AFTER INSERT AS
BEGIN
UPDATE ITS
SET SalesYTD = ITS.Amount + I.Amount
FROM InternetServices ITS
JOIN INSERTED I ON ITS.ID = I.ID
END
GO
You need a where clause for the update, not just in the subquery:
UPDATE InternetServices
SET SalesYTD = (SELECT SUM(Amount)
FROM Sales
WHERE ServiceID = 100
)
WHERE ServiceID = 100;

How to select info from row above?

I want to add a column to my table that is like the following:
This is just an example of how the table is structured, the real table is more than 10.000 rows.
No_ Name Account_Type Subgroup (New_Column)
100 Sales 3
200 Underwear 0 250 *100
300 Bikes 0 250 *100
400 Profit 3
500 Cash 0 450 *400
So for every time there is a value in 'Subgroup' I want the (New_Column) to get the value [No_] from the row above
No_ Name Account_Type Subgroup (New_Column)
100 Sales 3
150 TotalSales 3
200 Underwear 0 250 *150
300 Bikes 0 250 *150
400 Profit 3
500 Cash 0 450 *400
There are cases where the table is like the above, where two "Headers" are above. And in that case I also want the first above row (150) in this case.
Is this a case for a cursor or what do you recommend?
The data is ordered by No_
--EDIT--
Starting from the first line and then running through the whole table:
Is there a way I can store the value for [No_] where [Subgroup] is ''?
And following that insert this [No_] value in the (New_Column) in each row below having value in the [Subgroup] row.
And when the [Subgroup] row is empty the process will keep going, inserting the next [No_] value in (New_Column), that is if the next line has a value in [Subgroup]
Here is a better image for what I´m trying to do:
SQL Server 2012 suggests using Window Offset Functions.
In this case : LAG
Something like this:
SELECT [No_]
,[Name]
,[Account_Type]
,[Subgroup]
,LAG([No_]) OVER(PARTITION BY [Subgroup]
ORDER BY [No_]) as [PrevValue]
FROM table
Here is an example from MS:
http://technet.microsoft.com/en-us/library/hh231256.aspx
The ROW_NUMBER function will allow you to find out what number the row is, but because it is a windowed function, you will have to use a common table expression (CTE) to join the table with itself.
WITH cte AS
(
SELECT [No_], Name, Account_Type, Subgroup, [Row] = ROW_NUMBER() OVER (ORDER BY [No_])
FROM table
)
SELECT t1.*, t2.[No_]
FROM cte t1
LEFT JOIN cte t2 ON t1.Row = t2.Row - 1
Hope this helps.
Next query will return Name of the parent row instead of the row itself, i.e. Sales for both Sales, Underwear, Bikes; and Profit for Profit, Cash:
select ISNULL(t2.Name, t1.Name)
from table t1
left join table t2 on t1.NewColumn = t2.No
So in SQL Server 2008 i created test table with 3 values in it:
create table #ttable
(
id int primary key identity,
number int,
number_prev int
)
Go
Insert Into #ttable (number)
Output inserted.id
Values (10), (20), (30);
Insert in table, that does what you need (at least if understood correctly) looks like this:
declare #new_value int;
set #new_value = 13; -- NEW value
Insert Into #ttable (number, number_prev)
Values (#new_value,
(Select Max(number) From #ttable t Where t.number < #new_value))
[This part added] And to work with subgroup- just modify the inner select to filter out it:
Select Max(number) From #ttable t
Where t.number < #new_value And Subgroup != #Subgroup
SELECT
No_
, Name
, Account_Type
, Subgroup
, ( SELECT MAX(above.No_)
FROM TableX AS above
WHERE above.No_ < a.No_
AND above.Account_Type = 3
AND a.Account_Type <> 3
) AS NewColumn
FROM
TableX AS a

Select rows and Update same rows for locking?

I need to write a procedure that will allow me to select x amount of rows and at the same time update those rows so the calling application will know those records are locked and in use. I have a column in the table named "locked". The next time the procedure is called it will only pull the next x amount of records that do not have the "locked" column checked. I have read a little about the OUTPUT method for SQL server, but not sure that is what I want to do.
As you suggested, you can use the OUTPUT clause effectively:
Live demo: https://data.stackexchange.com/stackoverflow/query/8058/so3319842
UPDATE #tbl
SET locked = 1
OUTPUT INSERTED.*
WHERE id IN (
SELECT TOP 1 id
FROM #tbl
WHERE locked = 0
ORDER BY id
)​
Also see this article:
http://www.sqlmag.com/article/tsql3/more-top-troubles-using-top-with-insert-update-and-delete.aspx
Vote for Cade Roux's answer, using OUTPUT:
UPDATE #tbl
SET locked = 1
OUTPUT INSERTED.*
WHERE id IN (SELECT TOP 1 id
FROM #tbl
WHERE locked = 0
ORDER BY id)​
Previously:
This is one of the few times I can think of using a temp table:
ALTER PROCEDURE temp_table_test
AS
BEGIN
SELECT TOP 5000 *
INTO #temp_test
FROM your_table
WHERE locked != 1
ORDER BY ?
UPDATE your_table
SET locked = 1
WHERE id IN (SELECT id FROM #temp_test)
SELECT *
FROM #temp_test
IF EXISTS (SELECT NULL
FROM tempdb.dbo.sysobjects
WHERE ID = OBJECT_ID(N'tempdb..#temp_test'))
BEGIN
DROP TABLE #temp_test
END
END
This:
Fetches the rows you want, stuffs them into a local temp table
Uses the temp table to update the rows to be "locked"
SELECTs from the temp table to give you your resultset output
Drops the temp table because they live for the session