SQL Stored procedure to obtain top customers - sql

I'm trying to create a stored procedure that goes through a "SALES" table and returns the best two customers of a pharmacy (the two customers who have spent more money).
Here's some code:
Table creation:
create table Customer (
Id_customer int identity(1,1) Primary Key,
Name varchar(30),
Address varchar(30),
DOB datetime,
ID_number int not null check (ID_number > 0),
Contributor int not null check (Contributor > 0),
Customer_number int not null check (Customer_number > 0)
)
create table Sale (
Id_sale int identity(1,1) Primary Key,
Id_customer int not null references Customer(Id_customer),
Sale_date datetime,
total_without_tax money,
total_with_tax money
)
Well, I don't know if this is useful but I have a function that returns the total amount spent by a customer as long as I provide the customer's ID.
Here it is:
CREATE FUNCTION [dbo].[fGetTotalSpent]
(
#Id_customer int
)
RETURNS money
AS
BEGIN
declare #total money
set #total = (select sum(total_with_tax) as 'Total Spent' from Sale where Id_customer=#Id_customer)
return #total
END
Can someone help me get the two top customers?
Thanks
Chiapa
PS: Here's some data to insert so you can test it better:
insert into customer values ('Jack', 'Big street', '1975.02.01', 123456789, 123456789, 2234567891)
insert into customer values ('Jim', 'Little street', '1985.02.01', 223456789, 223456789, 2234567891)
insert into customer values ('John', 'Large street', '1977.02.01', 323456789, 323456789, 3234567891)
insert into customer values ('Jenny', 'Huge street', '1979.02.01', 423456789, 423456789, 4234567891)
insert into sale values (1, '2013.04.30', null, 20)
insert into sale values (2, '2013.05.22', null, 10)
insert into sale values (3, '2013.03.29', null, 30)
insert into sale values (1, '2013.05.19', null, 34)
insert into sale values (1, '2013.06.04', null, 21)
insert into sale values (2, '2013.06.01', null, 10)
insert into sale values (2, '2013.05.08', null, 26)

You can do this with a single query without any special functions:
select top 2 c.id_customer, c.name, sum(s.total_with_tax)
from customer c
join sale s on c.id_customer = s.id_customer
group by c.id_customer, c.name
order by sum(s.total_with_tax) desc

This joins onto a CTE with the top customers.
Remove the WITH TIES option if you want exactly 2 and don't want to include customers tied with the same spend.
WITH Top2
AS (SELECT TOP 2 WITH TIES Id_customer,
SUM(total_with_tax) AS total_with_tax
FROM Sale
GROUP BY Id_customer
ORDER BY SUM(total_with_tax) DESC)
SELECT *
FROM Customer C
JOIN Top2 T
ON C.Id_customer = T.Id_customer

I'm not really into SQL Server dialect, but this one will give you best customers in descending order along with money they spent:
select Id_customer, total_with_tax from
(select Id_customer, sum(total_with_tax) total_with_tax from Sale group by Id_customer)
order by total_with_tax desc

Related

Getting values from multiple tables in one column

Let's say I have three tables:
table orders:
invoice_ID
customer_ID
202201
1000
202202
2000
202203
3000
202204
4000
table department_north
customer_ID
product
price
4000
VW Rabbit
$5000.00
1000
BMW X5
$15.000
table department_south
customer_ID
product
price
3000
Tesla S
$30.000
2000
BMW X3
$20.000
Wanted Result
A table with invoice_id, a new column with all cars that contain '%BMW%', a new column with the attached price
invoice_ID
product_bmw
price_bmw
202201
BMW X5
$5.000
202202
BMW X3
$20.000
I figured out how to get the results for one department table but can't find a statement for both.
SELECT DISTINCT orders.invoice_ID,
department_north.product AS product_BMW,
department_north.price AS price_BMW
FROM orders
JOIN LEFT department_north
ON department_north.customer_ID = order.customer_id
JOIN LEFT department_south
ON department_south.customer_ID = order.customer_id
WHERE department_north.product LIKE '%BMW%'
I would UNION ALL all departments. See following example:
DECLARE #orders TABLE
(
invoice_ID varchar(20),
customer_ID int
);
INSERT #orders VALUES
(202201, 1000),
(202202, 2000),
(202203, 3000),
(202204, 4000);
DECLARE #department_north TABLE
(
customer_ID int,
product nvarchar(20),
price decimal(15,2)
);
INSERT #department_north VALUES
(4000, 'VW Rabbit', 5000),
(1000, 'BMW X5', 15000);
DECLARE #department_south TABLE
(
customer_ID int,
product nvarchar(20),
price decimal(15,2)
);
INSERT #department_south VALUES
(3000, 'Tesla S', 30000),
(2000, 'BMW X3', 20000);
WITH AllDepartments AS
(
SELECT *
FROM #department_north
UNION ALL
SELECT *
FROM #department_south
)
SELECT invoice_ID, product, price
FROM #orders O
JOIN AllDepartments D ON O.customer_ID=D.customer_ID
WHERE product LIKE '%BMW%';
I would use union all like Paweł Dyl's answer above, but would create a single department table and create an extra column, called location or similar and put an 'S' for south and an 'N' for north into it as per below:
create table #department
(
customer_ID int
, product varchar(64)
, price decimal(15,2)
, "location" varchar(64) -- to allow for other locations
)
;
insert into #department values (4000, 'VW Rabbit', 5000.00, 'N');
insert into #department values (1000, 'BMW X5', 15.000, 'N');
insert into #department values (3000, 'Tesla S', 30.000, 'S');
insert into #department values (2000, 'BMW X3', 20.000, 'S');
This means that you are just using the one department table and you have the additional 'location' column for adding east or west if need be. This will reduce the need to create a new database table for each new location added to your list. You could expand this to include city and/or state or whatever depending on the range of the data but you should aim to use only one table for this purpose.
Creating multiple tables based purely on location would not be recommended and think, what would you do if there were many locations e.g. 50 or more? It would be a nightmare to manage this code by creating a separate table for each location.

Update column in Invoice Table with Payment total from Payments table

I've got 2 tables (shown below) - Invoice and Payments. A single invoice can have multiple payments. I need to SUM the total of PayAmt from Payments and update the PayTotal column in the Invoice table.
This is NOT working:
UPDATE Invoices
SET Invoices.PayTotal = Payments.Total
FROM Invoices
INNER JOIN
(SELECT InvNum_FK, SUM(PayAmt) as Total
FROM Payments) ON Invoices.InvNum_PK = Payments.InvNum_FK
CREATE TABLE dbo.Invoices
(
InvNum_PK nvarchar(255) PRIMARY KEY,
InvAmt money,
InvDate date,
CustName nvarchar(255),
PayTotal money,
PayCount int
);
CREATE TABLE dbo.Payments
(
PayNum_PK int PRIMARY KEY,
InvNum_FK nvarchar(255)
FOREIGN KEY REFERENCES dbo.Invoices(InvNum_PK),
PayAmt money,
PayDate date,
);
INSERT INTO Invoices
VALUES ('GLI101', 838.93, '2021-08-01', 'George Washington', 0, 0);
INSERT INTO Invoices
VALUES ('GLI202', 1280.26, '2021-08-02', 'Abe Lincoln', 0, 0);
INSERT INTO Invoices
VALUES ('GLI303', 1456.23, '2021-08-03', 'Tom Jefferson', 0, 0);
INSERT INTO Invoices
VALUES ('GLI404', 1124.97, '2021-08-04', 'Jim Madison', 0, 0);
INSERT INTO Payments VALUES (1, 'GLI101', 223.33, '08/15/2021')
INSERT INTO Payments VALUES (2, 'GLI101', 211.88, '09/16/2021')
INSERT INTO Payments VALUES (3, 'GLI101', 316.44, '09/14/2021')
INSERT INTO Payments VALUES (4, 'GLI404', 415.46, '09/10/2021')
INSERT INTO Payments VALUES (5, 'GLI404', 115.46, '09/04/2021')
This works if anyone looks at this post in the future with the same question:
WITH cte AS (
SELECT InvNum_FK, SUM(PayAmt) AS PayTotal
FROM Payments
GROUP BY InvNum_FK)
UPDATE Invoices SET Invoices.PayTotal = cte.PayTotal
FROM Invoices INNER JOIN cte ON Invoices.InvNum_PK = cte.InvNum_FK

Querying multiple transactions into a single line

I am trying to create a small home finance app. I am trying a sort of Double Entry type design, but really battling to find a way to efficialy generate a staement.
So I have created a dummy script that I am using to test. What I have is:
Accounts table (entities that I can pay to, and from).
Budgets table (budgets that I can assign expenses to, so that I can allocate portions of transactions to them. Note that a transaction can be split amoungst different budgets, as an example I have provided)
Transaction table (Holding the header info for a transaction)
TransactionLine table (A breakdown of the transaction, including amount and account it comes from)
What I am then trying to do, is achieve the following:
I need to efficiently present data to render a statement. So, given an accountID, I want to see the transactions.
But because TransactionLines are split onto multiple lines, I'm finding it hard to get the data in a single line:
Date-- Who I paid or recieved money from in this transaction -- the amount -- If it's a debit or credit.
So the raw data I have to work with:
And then I am trying to break that down to:
So I have mocked up the data, and tried to explain what I need. The script uses table variables, so is re-runnable.
DECLARE #Account TABLE (
Id INT NOT NULL,
Name VARCHAR(20)
)
INSERT INTO #Account VALUES (1, 'My Bank Account')
INSERT INTO #Account VALUES (2, 'My Work')
INSERT INTO #Account VALUES (3, 'A restaurant')
INSERT INTO #Account VALUES (4, 'A coffee shop')
INSERT INTO #Account VALUES (5, 'A Department Store')
DECLARE #Budget TABLE (
Id INT NOT NULL,
Name VARCHAR(20)
)
INSERT INTO #Budget VALUES (1, 'My Budget')
INSERT INTO #Budget VALUES (2, 'My Clothing Budget')
DECLARE #Transaction TABLE (
Id INT NOT NULL ,
Date DATETIME NOT NULL,
Description VARCHAR(20)
)
DECLARE #TransactionLine TABLE (
Id INT NOT NULL,
TransactionId INT NOT NULL,
AccountId INT,
BudgetId INT NULL,
DebitAmount DECIMAL NOT NULL,
CreditAmount DECIMAL NOT NULL
)
-- Got paid, from My Work to My Account
INSERT INTO #Transaction VALUES (1, GETUTCDATE(), 'Got Paid')
INSERT INTO #TransactionLine VALUES (1, 1, 1, NULL, 0, 1000) -- Credit My Bank ccount
INSERT INTO #TransactionLine VALUES (2, 1, 2, NULL, 1000, 0) -- Debit My Work
-- Got a coffee, from My Account to A Coffee Shop
INSERT INTO #Transaction VALUES (2, GETUTCDATE(), 'Got a Coffee')
INSERT INTO #TransactionLine VALUES (3, 2, 1, NULL, 5, 0) -- Debit My Account
INSERT INTO #TransactionLine VALUES (4, 2, 4, NULL, 0, 5) -- Credit a Coffee shop
-- Went to dinner, from My Account to A restaurant. This comes off My Budget
INSERT INTO #Transaction VALUES (3, GETUTCDATE(), 'Went to Dinner')
INSERT INTO #TransactionLine VALUES (5, 3, 1, 1, 25, 0) -- Debit My Account
INSERT INTO #TransactionLine VALUES (6, 3, 3, NULL, 0, 25) -- Credit A restaurant
INSERT INTO #Transaction VALUES (4, GETUTCDATE(), 'Did weekly shopping')
INSERT INTO #TransactionLine VALUES (9, 4, 1, 1, 25, 0) -- Debit My Account with 25, and assign it to My Budget
INSERT INTO #TransactionLine VALUES (9, 4, 1, 2, 75, 0) -- Debit My Account with 75, and assign it to My Clothing Budget
INSERT INTO #TransactionLine VALUES (11, 4, 5, NULL, 0, 50) -- Credit tghe Department store with 100
-- View the raw data.
SELECT t.id, Date, Description, AccountId, a.Name as AccountName, DebitAmount, CreditAmount, b.Name as BudgetName
FROM #Transaction t
INNER JOIN #TransactionLine tl
ON tl.TransactionId = t.Id
INNER JOIN #Account a
ON a.id = tl.AccountId
LEFT JOIN #Budget b
ON b.id = tl.BudgetId
-- View the raw data based on a select Account ID. i.e. I'm viewing a statement for 'My Bank Account'
SELECT t.id, Date, Description, AccountId, a.Name as AccountName, DebitAmount, CreditAmount, b.Name as BudgetName
FROM #Transaction t
INNER JOIN #TransactionLine tl
ON tl.TransactionId = t.Id
INNER JOIN #Account a
ON a.id = tl.AccountId
LEFT JOIN #Budget b
ON b.id = tl.BudgetId
WHERE AccountId = 1
-- Need to get:
SELECT 1 AS Id, GETUTCDATE() AS Date, 'Got Paid' AS Description, 1 AS AccountId, 'My Bank Account' as AccountName, 'My Work' AS OtherAccountName, 'Credit' as Type, 1000 as Amount, NULL AS Budget
UNION
SELECT 2 AS Id, GETUTCDATE() AS Date, 'Got a Coffee' AS Description, 1 AS AccountId, 'My Bank Account' as AccountName, 'A coffee shop' AS OtherAccountName, 'Debit' as Type, 5 as Amount, NULL AS Budget
UNION
SELECT 3 AS Id, GETUTCDATE() AS Date, 'Went to Dinner' AS Description, 1 AS AccountId, 'My Bank Account' as AccountName, 'A restaurant' AS OtherAccountName, 'Debit' as Type, 25 as Amount, 'My Budget' AS Budget
UNION
SELECT 4 AS Id, GETUTCDATE() AS Date, 'Did weekly shopping' AS Description, 1 AS AccountId, 'My Bank Account' as AccountName, 'A Department Store' AS OtherAccountName, 'Debit' as Type, 100 as Amount, '* Multiple Mudgets' AS Budget
-- So that I can create a statement fro My Bank Account.
SELECT '2019-07-28 My Work +1000' UNION
SELECT '2019-07-28 A coffee shop -5' UNION
SELECT '2019-07-28 A restaurant* -25' UNION
SELECT '2019-07-28 A department Store* -100'
-- Where the * in the description indicates it has a Budget assigned.
The main issue I find is: given I have an AccountID, I can find the transactions related to that account, but ... how do I get the OTHER account to which the transaction had an effect.
Firstly, well structured question and thank you for the data. Secondly, you're after window functions to solve your problem. Take special note of the ROWS BETWEEN section.
Please note that you'll usually see people declare a CTE like this (which you shouldn't do):
;with cteFooBar AS
This is because a CTE must come after a semi-colon, so make sure you put a ; on the end of all your statements so the below works:
With transactions AS
(
SELECT t.id, Date, Description, AccountId, a.Name as AccountName, DebitAmount, CreditAmount,
IIF(
COUNT(1) OVER (PARTITION BY Description ORDER BY [Date] ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) > 1, 'Multiple Budgets', b.Name) as BudgetName
FROM #Transaction t
INNER JOIN #TransactionLine tl ON tl.TransactionId = t.Id
INNER JOIN #Account a ON a.id = tl.AccountId
LEFT JOIN #Budget b ON b.id = tl.BudgetId
WHERE a.Name = 'My Bank Account'
AND AccountId = 1
)
SELECT id, Date, Description, AccountId, AccountName, SUM(DebitAmount) [DebitAmount], SUM(CreditAmount) [CreditAmount], BudgetName
FROM transactions
GROUP BY id, Date, Description, AccountId, AccountName, BudgetName;
DECLARE #AccountName VARCHAR(50) = 'A Department Store';
With myAccount AS
(
SELECT t.id, Date, Description, AccountId, a.Name as AccountName, DebitAmount, CreditAmount
FROM #Transaction t
INNER JOIN #TransactionLine tl ON tl.TransactionId = t.Id
INNER JOIN #Account a ON a.id = tl.AccountId
LEFT JOIN #Budget b ON b.id = tl.BudgetId
)
SELECT CAST(Date AS DATE) [Date], AccountName, IIF(SUM(-DebitAmount + CreditAmount) > 0, '-','+') + CAST(SUM(DebitAmount + CreditAmount) AS VARCHAR(1000)) [Amount]
FROM myAccount
WHERE (AccountName = #AccountName OR
(
#AccountName IS NULL AND AccountName != 'My Bank Account'
))
GROUP BY CAST(Date AS DATE), Description, AccountName
ORDER BY AccountName;

How to update a column based on matching ID's

I create two tables housing_listing and buyer. How to update the value of sold in housing_listing table to TRUE if the id (transaction_id) of housing_listing matches the id (transaction_id) of buyer?
Creating tables:
CREATE TABLE housing_listing (
transaction_id INT PRIMARY KEY,
number_of_bedrooms INT,
number_of_bathrooms INT,
listing_price INT,
listing_agent TEXT,
agent_email TEXT,
listing_office TEXT,
date_of_listing DATETIME,
zip_code INT,
sold BOOL,
Commission INT
);
CREATE TABLE buyer (
transaction_id INT PRIMARY KEY,
buyer_name TEXT,
buyer_id INT,
sale_price INT,
date_of_sale DATETIME,
selling_agent TEXT
)
Inserting data to buyer table:
INSERT INTO buyer VALUES (1, "Ania Kraszka", 1, 2000000,'2020/02/27','FADU');
INSERT INTO buyer VALUES (2, "Ania Kraszka", 2, 2000000,'2011/02/27','FADU');
Inserting data to housing_listing table:
INSERT INTO housing_listing VALUES (1, 3, 2, 2000000, 'Liza','liza#uba.ar', 'UBA','2018/02/27',45049, 'FALSE',0);
INSERT INTO housing_listing VALUES (2, 2, 1, 3000, 'Tom','tom#utn.ar', 'UTN','2011/02/27',45049,'FALSE',0);
INSERT INTO housing_listing VALUES (9, 1, 1, 40000, 'Tom','tom#fadu.ar', 'FADU','2011/02/27',45049, 'FALSE',0);
You can use a correlated subquery:
update housing_listing
set sold = true
where exists (select 1
from buyer b
where b.transaction_id = housing_listing.transaction_id
);
I assume you mean that the transaction ids match.
You can do something like this-
UPDATE housing_listing
SET sold = 'TRUE'
WHERE transaction_id IN (SELECT transaction_id FROM buyer);
SELECT * FROM housing_listing;
1|3|2|2000000|Liza|liza#uba.ar|UBA|2018/02/27|45049|TRUE|0
2|2|1|3000|Tom|tom#utn.ar|UTN|2011/02/27|45049|TRUE|0
9|1|1|40000|Tom|tom#fadu.ar|FADU|2011/02/27|45049|FALSE|0

UPDATE source table, AFTER Grouping?

I have a table (source) with payments for a person - called 'Item' in the example below.
This table will have payments for each person, added to it over a period.
I then generate invoices, which basically takes all the payments for a particular person, and sums them up into a single row.
This must be stored in an invoice table, for auditing reasons.
I do this in the example below.
What I am missing, though, as I am not sure how to do it, is that each payment, once assigned to the Invoice table, needs to had the Invoice ID that it was assigned to, stored in the Items table.
So, see the example below:
CREATE TABLE Items
(
ID INT NOT NULL IDENTITY(1,1),
PersonID INT NOT NULL,
PaymentValue DECIMAL(16,2) NOT NULL,
AssignedToInvoiceID INT NULL
)
CREATE TABLE Invoice
(
ID INT NOT NULL IDENTITY(1,1),
PersonID INT NOT NULL,
Value DECIMAL(16,2)
)
INSERT INTO Items (PersonID, PaymentValue) VALUES (1, 100)
INSERT INTO Items (PersonID, PaymentValue) VALUES (2, 132)
INSERT INTO Items (PersonID, PaymentValue) VALUES (2, 65)
INSERT INTO Items (PersonID, PaymentValue) VALUES (1, 25)
INSERT INTO Items (PersonID, PaymentValue) VALUES (3, 69)
SELECT * FROM Items
INSERT INTO Invoice (PersonID, Value)
SELECT PersonID, SUM(PaymentValue) FROM Items
WHERE AssignedToInvoiceID IS NULL
GROUP BY PersonID
SELECT * FROM Invoice
DROP TABLE Items
DROP TABLE Invoice
What I need to do is then update the Items table, to say that the first row has been assigned to Invoice.ID 1, row two was assigned to Invoice ID 2. Row 3, was assigned to Invoice ID 2 as well.
Note, there are many other columns in the table. This is a basic example.
Simply, I need to record which invoice, each source row was assigned to.
The key thing here to ensure payments are correctly linked to invoices is to ensure that:
A: No updates are made to Items between reading the unassigned items and updating AssignedToInvoiceID.
B: No new invoices are created with the Items being process before updating AssignedToInvoiceID.
As you are updating two tables it will have to be a two step process. To ensure A it will need to be run in a transaction with a least REPEATABLE READ isolation. To ensure B requires a transaction with SERIALIZABLE isolation. See SET TRANSACTION ISOLATION LEVEL
It can be done like this:
BEGIN TRAN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
DECLARE #newInvoices TABLE (PersonID INT, InvoiceID INT)
INSERT INTO Invoice (PersonID, Value)
OUTPUT inserted.ID, inserted.PersonID INTO #newInvoices(InvoiceID, PersonID)
SELECT PersonID, SUM(PaymentValue) FROM Items
WHERE AssignedToInvoiceID IS NULL
GROUP BY PersonID
UPDATE Items
SET AssignedToInvoiceID = InvoiceID
FROM Items
INNER JOIN #newInvoices newInvoice ON newInvoice.PersonID = Items.PersonID
WHERE AssignedToInvoiceID IS NULL
COMMIT
An alternative if you are using SQL Server 2012 or later is to use the SEQUNCE object, this will allow the Items to be assigned new invoice IDs before the Invoices are created, reducing the locking required.
It works like this:
-- Run once with your table setup.
CREATE SEQUENCE InvoiceIDs AS INT START WITH 1 INCREMENT BY 1
CREATE TABLE Items
(
ID INT NOT NULL IDENTITY(1,1),
PersonID INT NOT NULL,
PaymentValue DECIMAL(16,2) NOT NULL,
AssignedToInvoiceID INT NULL
)
CREATE TABLE Invoice
(
-- No longer a IDENTITY column
ID INT NOT NULL,
PersonID INT NOT NULL,
Value DECIMAL(16,2)
)
BEGIN TRAN
DECLARE #newInvoiceLines TABLE (PersonID INT, InvoiceID INT, PaymentValue DECIMAL(16,2))
-- Reading and updating AssignedToInvoiceID happens in one query so is thread safe.
UPDATE Items
SET AssignedToInvoiceID = newInvoices.InvoiceID
OUTPUT inserted.PersonID, inserted.AssignedToInvoiceID, inserted.PaymentValue
INTO #newInvoiceLines(PersonID, InvoiceID, PaymentValue)
FROM Items
INNER JOIN (
SELECT PersonID, NEXT VALUE FOR InvoiceIDs AS InvoiceID
FROM Items
GROUP BY PersonID
) AS newInvoices ON newInvoices.PersonID = Items.PersonID
WHERE Items.AssignedToInvoiceID IS NULL
INSERT INTO Invoice (ID, PersonID, Value)
SELECT InvoiceID, PersonID, SUM(PaymentValue) FROM #newInvoiceLines
GROUP BY PersonID, InvoiceID
COMMIT
You will still want to use a transaction to ensure the Invoice gets created.
Based on what I understand, you could run an update from join after you have inserted records into Invoices table, like so:
update items
set assignedtoinvoiceid = v.id
from
items m
inner join invoice v on m.personid = v.personid
Demo
Each time you do an invoice "run", select the most recent invoice for each person something like,
update items
set AssignedToInvoiceID = inv.id
from
(select personid, max(id) id
from invoice
group by personid) inv
where items.personid = inv.personid
and AssignedToInvoiceID is null
this assumes that AssignedToInvoiceID is null when it isn't populated, if it gets defaulted to an empty string or something then you would need to change the where condition.
1) Get MAX(ID) from Invoice table before inserting new rows from Items table. Store this value into a variable: #MaxInvoiceID
2) After inserting records into Invoice table, update AssignedToInvoiceID in Items table with Invoice.ID>#MaxInvoiceID
Refer below code:
CREATE TABLE #Items
(
ID INT NOT NULL IDENTITY(1,1),
PersonID INT NOT NULL,
PaymentValue DECIMAL(16,2) NOT NULL,
AssignedToInvoiceID INT NULL
)
CREATE TABLE #Invoice
(
ID INT NOT NULL IDENTITY(1,1),
PersonID INT NOT NULL,
Value DECIMAL(16,2)
)
DECLARE #MaxInvoiceID INT;
SELECT #MaxInvoiceID=ISNULL(MAX(ID),0) FROM #Invoice
SELECT #MaxInvoiceID
INSERT INTO #Items (PersonID, PaymentValue) VALUES (1, 100)
INSERT INTO #Items (PersonID, PaymentValue) VALUES (2, 132)
INSERT INTO #Items (PersonID, PaymentValue) VALUES (2, 65)
INSERT INTO #Items (PersonID, PaymentValue) VALUES (1, 25)
INSERT INTO #Items (PersonID, PaymentValue) VALUES (3, 69)
SELECT * FROM #Items
INSERT INTO #Invoice (PersonID, Value)
SELECT PersonID, SUM(PaymentValue)
FROM #Items
WHERE AssignedToInvoiceID IS NULL
GROUP BY PersonID
SELECT * FROM #Invoice
UPDATE Itm
SET Itm.AssignedToInvoiceID=Inv.ID
FROM #Items Itm
JOIN #Invoice Inv ON Itm.PersonID=Inv.PersonID AND Itm.AssignedToInvoiceID IS NULL AND Inv.ID>#MaxInvoiceID
SELECT * FROM #Items
DROP TABLE #Items
DROP TABLE #Invoice