Find the number of users with negative total - python

I am trying to figure out a SQL query or a Python Pandas code for the following solution.
There are n number of USER_ID with various transactions.
Every USER_ID has more than one transaction.
Example USER_ID
000e88bb-d302-4fdc-b757-2b1a2c33e7d6
001926be-3245-43fa-86dd-b40ee160b6f9
Every Transaction has a TYPE
TOPUP
Bank_Transaction
P2P
and a couple more
I want to write a query in which
(TOPUP) - (total of every other type of transaction) and returns all the USER_ID where TOPUP < Total of all the transactions.
Finding all the users who have less topup and more spending.
I hope I am making myself clear?

I believe that the following may produce the result that you want :-
WITH counter AS (
SELECT user_id
FROM transactions AS a
WHERE
coalesce((SELECT sum(amount) FROM transactions WHERE transaction_type = 'TOPUP' AND user_id = a.user_id),0.0) -
coalesce((SELECT sum(amount) FROM transactions WHERE transaction_type <> 'TOPUP' AND user_id = a.user_id),0.0)
< 0
GROUP BY user_id
)
SELECT count() FROM counter;
this assumes that the table name is transactions.
If you consider the following data :-
INSERT INTO transactions VALUES
('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','TOPUP',25.00)
,('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','P2P',125.00)
,('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','BANK-TRANSACTION',75.00)
,('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','TOPUP',25.00)
,('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','BANK-TRANSACTION',75.00)
,('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','TOPUP',25.00)
,('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','TOPUP',25.00)
,('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','BANK-TRANSACTION',75.00)
,('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','TOPUP',25.00)
,('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','BANK-TRANSACTION',75.00)
,('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','BANK-TRANSACTION',75.00)
,('000e88bb-d302-4fdc-b757-2b1a2c33e7d6','BANK-TRANSACTION',75.00)
,('001926be-3245-43fa-86dd-b40ee160b6f9','TOPUP',10.00)
,('001926be-3245-43fa-86dd-b40ee160b6f9','TOPUP',10.00)
,('001926be-3245-43fa-86dd-b40ee160b6f9','TOPUP',10.00)
,('001926be-3245-43fa-86dd-b40ee160b6f9','TOPUP',10.00)
,('001926be-3245-43fa-86dd-b40ee160b6f9','TOPUP',10.00)
,('XX1926be-3245-43fa-86dd-b40ee160b6f9','P2P',50.00)
,('XX1926be-3245-43fa-86dd-b40ee160b6f9','P2P',50.00)
,('XX1926be-3245-43fa-86dd-b40ee160b6f9','P2P',50.00)
,('XX1926be-3245-43fa-86dd-b40ee160b6f9','P2P',50.00)
,('XX1926be-3245-43fa-86dd-b40ee160b6f9','P2P',50.00)
;
Then the result of the above is :-
i.e. the first and third users have a negative account balance, whilst the 2nd has a positive balance (and is therefore excluded from the count).

Think of the topup amounts to be positive and other spendings negative, then we are simply looking for users with a negative balance.
select user_id
from transaction
group by user_id
having sum(case when transaction_type = 'TOPUP' then amount else -amount end) < 0

Related

SQLite Find Name where datetime is between start and end

I have the following table in my database, which represents the shifts of a working day.
When a new product is added to another table 'Products' I want to assign a shift to it based on the start_timestamp.
So when I insert into Products its takes start_timestamp and looks in table ProductionPlan and looks for a result (ProductionPlan.name) where it is between the start and end timestamp of that shift.
On that way I can assign a shift to the product.
I hope somebody can help me out with this!
Table ProductionPlan
name
start_timestamp
end_timestamp
shift 1
2021-05-10T07:00:00
2021-05-10T11:00:00
shift 2
2021-05-10T11:00:00
2021-05-10T15:00:00
shift 3
2021-05-10T15:00:00
2021-05-10T19:00:00
shift 1
2021-05-11T07:00:00
2021-05-11T11:00:00
shift 2
2021-05-11T11:00:00
2021-05-11T15:00:00
shift 3
2021-05-11T15:00:00
2021-05-11T19:00:00
Table Products
id
name
start_timestamp
end_timestamp
shift
1
Schroef
2021-05-10T08:09:05
2021-05-10T08:19:05
2
Bout
2021-05-10T08:20:08
2021-04-28T08:30:11
3
Schroef
2021-05-10T12:09:12
2021-04-28T12:30:15
I have the following code to insert into Products:
def insertNewProduct(self, log):
"""
This function is used to insert a new product into the database.
#param log : a object to log
#return None.
"""
debug("Class: SQLite, function: insertNewProduct")
self.__openDB()
timestampStart = datetime.fromtimestamp(int(log.startTime)).isoformat()
queryToExecute = "INSERT INTO Products (name, start_timestamp) VALUES('{0}','{1}')".format(log.summary,
timestampStart)
self.cur.execute(queryToExecute)
self.__closeDB()
return self.cur.lastrowid
It's just a simple INSERT INTO but I want to add a query or even extend this query to fill in the column shift.
You can use a SELECT inside an INSERT.
queryToExecute = """INSERT INTO Products (name, start_timestamp, shift)
SELECT :1, :2, name FROM ProductionPlan pp
WHERE :2 BETWEEN pp.start_timestamp and pp.end_timestamp"""
self.cur.execute(queryToExecute, (log.summary, timestampStart))
In above code I have used a parameterized query because I hate inserting parameters as strings inside a query. It was the cause of too many SQL injection attacks...

Update values in sqlite database when there are multiple with the same name

I'll do my best to explain my problem.
I'm working on cs50 C$50 Finanace currently implementing a function called sell. The purpose of this function is to update the cash value of a specific user into the database and update his portfolio.
I'm struggling with updating the portfolio database.
This is the database query for better clarification:
CREATE TABLE portfolio(id INTEGER, username TEXT NOT NULL, symbol TEXT NOT NULL, shares INTEGER, PRIMARY KEY(id));
Let's say I've these values in it:
id | username | symbol | shares
1 | eminem | AAPL | 20
2 | eminem | NFLX | 5
3 | eminem | AAPL | 5
And the user sells some of his stocks. I have to update the shares.
If it was for NFLX symbol it is easy. A simple query like the below is sufficient
db.execute("UPDATE portfolio SET shares=shares - ? WHERE username=?
AND symbol=?", int(shares), username, quote["symbol"])
However if I wanted the update the AAPL shares, here is where the problem arises. If I executed the above query, lets say the user sold 5 of his shares, the above query will change the AAPL shares in both ids 1 and 3 into 20, making the total shares of AAPL to 40 not 20.
Which approach should I consider? Should I group the shares based on symbol before inserting them into portfolio table. If so, how? or is there a query that could solve my problem?
If your version of SQLite is 3.33.0+, then use the UPDATE...FROM syntax like this:
UPDATE portfolio AS p
SET shares = (p.id = t.id) * t.shares_left
FROM (
SELECT MIN(id) id, username, symbol, shares_left
FROM (
SELECT *, SUM(shares) OVER (ORDER BY id) - ? shares_left -- change ? to the number of stocks the user sold
FROM portfolio
WHERE username = ? AND symbol = ?
)
WHERE shares_left >= 0
) AS t
WHERE p.username = t.username AND p.symbol = t.symbol AND p.id <= t.id;
The window function SUM() returns an incremental sum of the shares until it reaches the number of shares sold.
The UPDATE statement will set, in all rows with id less than than the first id that exceeds the sold stocks, the column shares to 0 and in the row with with id equal to the first id that exceeds the sold stocks to the difference between the incremental sum and the number of sold shares.
See a simplified demo.
For prior versions you can use this:
WITH
cte AS (
SELECT MIN(id) id, username, symbol, shares_left
FROM (
SELECT *, SUM(shares) OVER (ORDER BY id) - ? shares_left -- change ? to the number of stocks the user sold
FROM portfolio
WHERE username = ? AND symbol = ?
)
WHERE shares_left >= 0
)
UPDATE portfolio
SET shares = (id = (SELECT id FROM cte)) * (SELECT shares_left FROM cte)
WHERE (username, symbol) = (SELECT username, symbol FROM cte) AND id <= (SELECT id FROM cte)
See a simplified demo.

SQL Query, Getting Rid of Repeat Selects

I'm using sqlite3 in python and I have a table that has the following columns:
recordid(int), username(text), locations(text), types(text), occupancy(int), time_added(datetime), token(text) and (undo).
I have the following query where I am selecting data from the table depending on what the occupancy is and the time added is between the 2 specified times the user inputs which is start_date and end_date:
('''SELECT locations, types,
(SELECT COUNT (occupancy) FROM traffic WHERE undo = 0 AND occupancy = 1 AND types = ? AND time_added BETWEEN ? AND ?),
(SELECT COUNT (occupancy) FROM traffic WHERE undo = 0 AND occupancy = 2 AND types = ? AND time_added BETWEEN ? AND ?),
(SELECT COUNT (occupancy) FROM traffic WHERE undo = 0 AND occupancy = 3 AND types = ? AND time_added BETWEEN ? AND ?),
(SELECT COUNT (occupancy) FROM traffic WHERE undo = 0 AND occupancy = 4 AND types = ? AND time_added BETWEEN ? AND ?),
FROM traffic WHERE types = ? GROUP BY type''',
(vehicle, start_date, end_date, vehicle, start_date, end_date, vehicle, start_date, end_date, vehicle, start_date, end_date, vehicle)
Is there anyway to condense this so I don't have to copy and paste the same thing multiple times just to change the occupancy? I tried using a for loop but that didn't really get me anywhere.
Cheers!
I'm pretty sure can simplify the query considerably:
SELECT type
SUM( occupancy = 1 ) as cnt_1,
SUM( occupancy = 2 ) as cnt_2,
SUM( occupancy = 3 ) as cnt_3,
SUM( occupancy = 4 ) as cnt_4
FROM traffic
WHERE undo = 0 AND
type = ? AND
time_added BETWEEN ? AND ?
GROUP BY type;
I'm not sure if that is exactly what your question has in mind, though.

Ordering SQLite3 Database greatest to least

Im trying to make a discord command that gets a column of a database greatest to least and gets the top 10 results, its working but its not printing in order greatest to least.
cursor.execute(f"SELECT bal, user_id FROM moneyTable WHERE guild_id = {ctx.guild.id} ORDER BY bal DESC LIMIT 10")
result = cursor.fetchall()
print(result)
My result is
[('635', '673990922945560599'), ('400', '317652126471815168'), ('200', '323669516489850882'), ('15000', '539305505445642250'), ('1260', '448573840893804614'), ('1000', '531664615755612161'), ('100', '419300570235666432'), ('100', '412106756349624331'), ('100', '408674114958524417'), ('100', '358517557772156929')]
It seems like the data type of bal is TEXT so it is ordered alphabetically.
You must convert bal to an integer either by adding 0 or explicitly:
SELECT bal, user_id
FROM moneyTable
WHERE guild_id = {ctx.guild.id}
ORDER BY bal + 0 DESC LIMIT 10
or:
SELECT bal, user_id
FROM moneyTable
WHERE guild_id = {ctx.guild.id}
ORDER BY CAST(bal AS INTEGER) DESC LIMIT 10

Resetting a rolling sum

I am trying to create a rolling stock count from the quantity. The count should reset everytime there is a real stock count (TypeOfMovement = 3). This should work on each ArticleNo and be grouped by date.
I can get a running stock count (lines <123 in image) and then take the real stock count when TypeOfMovement = 3 (Line 123 of image), but the count doesn't reset, it continues from before the real stock count (ResetRunningTotal in line124 should be 6293).
The solution should run in SSMS. Alternatively a python solution could be run.
My query so far is:
WITH a
AS
(SELECT
DateOfMovement, Quantity, ArticleNo, TypeOfChange,
CASE
WHEN TypeOfChange = 3 Then 0
ELSE Quantity
END AS RunningTotal
FROM Stock_Table
Group by DateOfMovement, Quantity, ArticleNo, TypeOfChange)
SELECT
*
,CASE
WHEN TypeOfChange= 3 THEN Quantity
ELSE Sum(Quantity) OVER(ORDER BY ArticleNo, DateOfMovement)
END AS ResetRunningTotal
FROM a
WHEre ArticleNo = 9410
group by DateOfMovement, ArticleNo, Quantity, TypeOfChange, RunningTotal
order by DateOfMovement asc
Image of results table is..
Ok so you want running totals for each ArticleNo ordered by DateOfMovement that reset whenever you encounter a TypeOfChange value of 3.
To do this you need to create a grouping_id (Grp) for each running total. You can do this with a CTE that calculates group ids, then do the running totals with the CTE results:
with Groups as (
select st.*
, sum(case TypeOfChange when 3 then 1 else 0 end)
over (partition by ArticleNo order by DateOfMovement) Grp
from Stock_Table st
)
select Groups.*
, sum(Quantity) over (partition by ArticleNo, Grp order by DateOfMovement) RunningTotal
from Groups
order by ArticleNo, dateofmovement

Categories

Resources