SQLite select inside of update - sqlite

So there are 2 tables, Transactions with created_at column and Transaction_associations with amount and remaining_balance columns, among others. I need to calculate a running sum(total) on the amount column, sorted by the created_at column, obviously. The only problem is that I need to get the SUM of all transactions that are created before the current transaction that is being calculated. I would've needed a select inside the update query in order to SELECT a current_transactions table in order to get hold of the current created_at date. However I can't. Am I missing something? Are there alternatives to this method?
UPDATE Transaction_associations SET remaining_balance =
(
SELECT SUM (Transaction_associations.amount)
FROM Transactions
JOIN Transaction_associations ON Transactions.id = transaction_id
WHERE created_at <= current_transactions.created_at // here
)
WHERE id IN
(
SELECT id
FROM Transaction_associations
JOIN Transactions ON Transactions.id = transaction_id
WHERE created_at >= '2014-11-24'
)
Edit: Added example.
Transactions Transaction_associations
created_at amount remaining_balance
2014-02-01 100 100
2014-03-01 50 150
2014-04-01 205 355
Later Edit: Added complete code for use on SQLFiddle. I've replaced Transaction_associations with TA2 on SUM, as it complains of misuse of aggregate: SUM()
DROP TABLE IF EXISTS Transactions;
DROP TABLE IF EXISTS Transaction_associations;
CREATE TABLE Transactions ( id integer, created_at text);
CREATE TABLE Transaction_associations ( id integer, amount integer, remaining_balance integer, transaction_id integer);
INSERT INTO Transactions VALUES (1,'2015');
INSERT INTO Transactions VALUES (2,'2014');
INSERT INTO Transactions VALUES (3,'2013');
INSERT INTO Transactions VALUES (4,'2012');
INSERT INTO Transactions VALUES (5,'2010');
INSERT INTO Transaction_associations VALUES (6, 100, 0, 1);
INSERT INTO Transaction_associations VALUES (7, 20, 0, 2);
INSERT INTO Transaction_associations VALUES (8, 3, 0, 3);
INSERT INTO Transaction_associations VALUES (9, 40, 0, 4);
INSERT INTO Transaction_associations VALUES (10, 500, 0, 5);
UPDATE Transaction_associations
SET remaining_balance =
(
SELECT SUM(TA2.amount)
FROM Transactions
JOIN Transaction_associations AS TA2 ON Transactions.id = TA2.transaction_id
WHERE created_at <= (SELECT created_at
FROM Transactions
WHERE id = TA2.transaction_id)
)
WHERE transaction_id IN
(
SELECT id
FROM Transactions
WHERE created_at >= '2013'
);
SELECT * from Transactions join Transaction_associations on Transactions.id = Transaction_associations.transaction_id;
This results in, which is wrong:
1 2015 6 100 663 1
2 2014 7 20 663 2
3 2013 8 3 663 3
4 2012 9 40 0 4
5 2010 10 500 0 5
Result should be:
1 2015 6 100 663 1
2 2014 7 20 563 2
3 2013 8 3 543 3
4 2012 9 40 0 4
5 2010 10 500 0 5

To use the same table name multiple times, rename one of them. This is not possible with UPDATE, so you have to do this in the SELECT.
To look up the corresponding timestamp, use another subquery.
Together with some simplifications, this becomes:
UPDATE Transaction_associations
SET remaining_balance =
(
SELECT SUM(TA2.amount)
FROM Transactions
JOIN Transaction_associations AS TA2 ON Transactions.id = TA2.transaction_id
WHERE created_at <= (SELECT created_at
FROM Transactions
WHERE id = Transaction_associations.transaction_id)
)
WHERE transaction_id IN
(
SELECT id
FROM Transactions
WHERE created_at >= '2014-11-24'
);

Related

How can update each row of a table based on two columns of it's previous row?

I have following table:
Id
offset
length
5000
0
5
5001
5
5
5002
10
4
5003
14
4
5010
23
5
5011
28
5
Offset value in each row is based on summation of offset and length of previous row.
As you can see, 6 rows have been deleted between forth and fifth rows and I need to update again offset column based on regular way. My desired output would be as follow:
Id
offset
length
5000
0
5
5001
5
5
5002
10
4
5003
14
4
5010
18
5
5011
23
5
Is there a pure update SQL statement to achieve this in sqlite?
I Appreciate any help.
If your version of SQLite is 3.33.0+ you can use the UPDATE ... FROM... syntax with SUM() window function:
UPDATE tablename AS t1
SET offset = t2.offset
FROM (
SELECT Id, SUM(length) OVER (ORDER BY Id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) offset
FROM tablename
) AS t2
WHERE t2.Id = t1.Id AND t2.offset IS NOT NULL;
See the demo.
For previous versions use a correlated subquery:
UPDATE tablename AS t1
SET offset = COALESCE(
(SELECT SUM(t2.length) FROM tablename t2 WHERE t2.Id < t1.Id),
t1.offset
);
See the demo.

How to reset numeration if there are gaps between numbers and they are in random order?

I have products displayed in two HTML tables. Their order is numbered from 1 to infinity, and is continuous through both tables. For example, if the number of the last product in the first table is 5, then the number of the first product in second table will be 6, etc.
When product from the second table is moved to the first table, I need to manually change order numbers in the database. So, if product 8 is moved to the first table, it is now product number 6 there (the last product). This I've done. Now the first table has numeration ...4 - 5 - 6, and the second table: 6 - 7 - 9 - 10 - etc., but needs to be set to 7 - 8 - 9 - 10, etc.
Information about numeration of both tables is stored in one database table. (Each product has a parameter indicating if it goes into table 1 or 2, but to get to it I need to do an inner join.)
What is a good way how to reset the second table numeration with PL/SQL?
I have something along the lines of
select order_number
into v_old_order_number
from products
where product_id = moved_product_id; --find product's current order number
select product_id bulk collect into v_first_table_product_ids
from products p
inner join table_with_parameter t
on p.product_id = t.product_id
and t.parameter = 1; --find all first table product ids
select max(order_number) + 1
into v_new_order_number
from products
where product_id member of v_first_table_product_ids; --find max order value of first table
--products, put in variable and add 1
update products
set order_number = v_new_order_number
where product_id = moved_product_id; --change our product's order number to new order number
update products
set order_number = order_number + 1
where order_number => v_new_order_number
and order_number <= v_oold_order_number
and product_id != moved_product_id; --change all numbers greater than new_order_no
--(except if it has the moved product's id)
--and smaller than old_order_no.
I'm sure this can be done in a better way.
In the future please include a small example (like the one I built below) that people can use to assist you. This helps clarify your question and makes sure the time people are volunteering on SO is well spent.
I think you can do what you want with a MERGE. I am using dense_rank() to calculate the order based on the table ID and the existing order and re-numbering every product. The MERGE will then only update the rows that need to be changed.
CREATE TABLE products (
table_id NUMBER,
product_id NUMBER,
order_id NUMBER);
ALTER TABLE products
ADD CONSTRAINT products_pk
PRIMARY KEY (product_id)
USING INDEX;
BEGIN
INSERT INTO products VALUES (1, 1, 1);
INSERT INTO products VALUES (1, 2, 2);
INSERT INTO products VALUES (1, 3, 3);
INSERT INTO products VALUES (1, 4, 4);
INSERT INTO products VALUES (1, 5, 5);
INSERT INTO products VALUES (2, 6, 6);
INSERT INTO products VALUES (2, 7, 7);
INSERT INTO products VALUES (2, 8, 8);
INSERT INTO products VALUES (2, 9, 9);
INSERT INTO products VALUES (2, 10, 10);
COMMIT;
END;
/
SELECT *
FROM products
ORDER BY table_id,
order_id;
/*
T P O
1 1 1
1 2 2
1 3 3
1 4 4
1 5 5
2 6 6
2 7 7
2 8 8
2 9 9
2 10 10
*/
UPDATE products
SET table_id = 1
WHERE product_id = 8;
SELECT *
FROM products
ORDER BY table_id,
order_id;
/*
T P O
1 1 1
1 2 2
1 3 3
1 4 4
1 5 5 <--
1 8 8 <--
2 6 6
2 7 7 <--
2 9 9 <--
2 10 10
*/
MERGE INTO products p
USING (SELECT product_id,
dense_rank() over(ORDER BY table_id, order_id) AS order_id
FROM products) x
ON (p.product_id = x.product_id)
WHEN MATCHED THEN
UPDATE
SET p.order_id = x.order_id
WHERE p.order_id != x.order_id;
-- 4 rows updated
SELECT *
FROM products
ORDER BY table_id,
order_id;
/*
T P O
1 1 1
1 2 2
1 3 3
1 4 4
1 5 5
1 8 6
2 6 7
2 7 8
2 9 9
2 10 10
*/

SQLite: Group data within certain time interval

I have a single table which stores data of orders:
Orders Table:
id | order_time | quantity | ...
1 | 1592821854318 | 2
2 | 1592901538199 | 4
3 | 1592966454547 | 1
4 | 1593081282406 | 9
5 | 1593141826330 | 6
order_time table is UNIX timestamp.
Using below query I am able to get available data grouped by days (86400000 = 24 hours):
SELECT order_time+ (86400000 - (order_time % 86400000)) as gap, SUM(quantity) as
totalOrdersBetweenInterval
FROM USAGE_DETAILS ud
WHERE order_time >= 1590969600 AND order_time <= 1593388799000
GROUP BY gap
ORDER BY gap ASC
Suppose for this month of June, I receive order on 1, 4, 6, 7 date then by using above query I am able to retrieve data as follow :
gap | totalOrdersBetweenInterval
1 | 5
4 | 6
6 | 4
7 | 10
I would receive UNIX timestamp in gap column but for the sake of example I have used readable dates.
Above query will only retrieve data for the days which would have received order but I want to split data in range like below which also include days with no orders :
gap | totalOrdersBetweenInterval
1 | 5
2 | 0
3 | 0
4 | 6
5 | 0
6 | 4
7 | 10
8 | 0
9 | 0
. | .
. | .
How do I go about that?
You need a query that returns 30 rows:1,2,...,30 for the days of June.
You could do it with a recursive CTE:
with days as (
select 1 day
union all
select day + 1
from days
where day < 30
)
but I'm not sure if Android uses a version of SQLite that supports CTEs.
If it does support them, all you need to do is join the CTE with a LEFT join to your query:
with
days as (
select 1 day
union all
select day + 1
from days
where day < 30
),
yourquery as (
<your query here>
)
select d.day, coalesce(t.totalOrdersBetweenInterval, 0) totalOrdersBetweenInterval
from days d left join yourquery t
on t.gap = d.day
If Android does not support CTEs you will have to build the query that returns the days with UNION ALL:
select d.day, coalesce(t.totalOrdersBetweenInterval, 0) totalOrdersBetweenInterval
from (
select 1 day union all select 2 union all
select 3 union all select 4 union all
......................................
select 29 union all select 30
) d left join (
<your query here>
) t
on t.gap = d.day
Thanks to #forpas for helping me out.
Just posting in case someone is searching for slicing data by unix time intervals.
with
days as (
select 1590969600000 day --Starting of June 1 2020
union all
select day + 86400000 --equivalent to 1 day
from days
where day < 1593388799000 --Less than 28th of June
),
subquery as (
SELECT order_time+ (86400000 - (order_time % 86400000)) as gap, SUM(quantity) as
totalOrdersBetweenInterval
FROM USAGE_DETAILS ud
WHERE order_time >= 1590969600000 AND order_time <= 1593388799000
GROUP BY gap
)
select d.day, coalesce(t.totalOrdersBetweenInterval, 0) totalOrdersBetweenInterval
from days d left join subquery t
on t.gap = d.day
order by d.day

SQLITE: Getting difference from two different column values

I have 2 tables naming..InvoiceGarmentService and Payment..
InvoiceGarmentService Table Data
----------------------------------
IGSID InvoiceID Price
0 1001 50
1 1001 100
2 1002 500
3 1002 600
------------------------------------
Payment Data
------------------------------------
PaymentID InvoiceID Amount
0 1001 20
1 1002 300
2 1003 900
------------------------------------
I want to get the due amount ie. (Sum of Price From IGS)-(Sum of Amount from Payment)
I have used a query like this
SELECT sum(Price) FROM InvoiceGarmentService - sum(Amount)FROM Payment
WHERE InvoiceGarmentService.InvoiceID='1001'
But I am Unable to find result..saying here is a syntax error but I dn knw about that..
Anyone can help me
SELECT igs.InvoiceID, (val1 - val2)
FROM (SELECT InvoiceID, sum(Price) AS val1
FROM InvoiceGarmentService
GROUP BY InvoiceID) igs
JOIN (SELECT InvoiceID, sum(Amount) AS val2
FROM Payment
GROUP BY InvoiceID) p
ON (igs.InvoiceID = p.InvoiceID)
WHERE igs.InvoiceID = '1001'

How To Group By Only Some Rows

I have some records.
ID Salary WillGroupBy Amount
----------------------------------------
6320 100 1 15
6320 150 1 20
6694 200 0 25
6694 300 0 30
7620 400 1 45
7620 500 1 50
How can I group by only which "WillGroupBy = 1" records?
(I will SUM Salary and Amount columns)
I want to get this result:
ID Salary WillGroupBy Amount
----------------------------------------
6320 250 1 35
6694 200 0 25
6694 300 0 30
7620 900 1 95
Can you help me please :( ?
Solution:
SELECT ID, SUM(Salary) Salary, WillGroupBy, SUM(Amount) Amount
FROM YourTable
where WILLGROUPBY = 0
union all
SELECT ID, SUM(Salary) Salary, WillGroupBy, SUM(Amount) Amount
FROM YourTable
where WILLGROUPBY = 1
group by ID, WillGroupBy
I used this solution via Erhan.
I would to know that how it could be in another way.
With MySQL you can do:
SELECT ID, SUM(Salary) Salary, WillGroupBy, SUM(Amount) Amount, #row := #row + 1
FROM YourTable
JOIN (SELECT #row := 0) v
GROUP BY ID, IF(WillGroupBy = 1, -1, #row)
DEMO

Resources