Using sql to fetch account balance for each owner - mysql

I have a table of owners
id | owner
--------
1 | Jack
2 | Lee
3 | Daniel
and a table of their transactions
id | owner_id | change
----------------
1 | 1 | 500
2 | 2 | 300
3 | 1 | -100
4 | 2 | 100
5 | 2 | -300
and I'm trying to get the balance of Jack's account. So for example here I would return
500
400
as Jack will first have 500 as his balance and after the change he will have 400.
What I currently have is
SELECT O.id, change FROM Owners O, Transactions WHERE O.id = 1 & Transactions.owner_id = 1;
but I can only get the rows of Jack's change. What can I do to get the balance for each row?

If you are using MySQL 8, you can do something like this:
SELECT SUM(change_v) OVER(ORDER BY id) AS balance
FROM transactions
WHERE owner_id = 1
An alternative could be to use a variable, like this:
SET #balance := 0;
SELECT (#balance := #balance + change_v) AS balance
FROM transactions
WHERE owner_id = 1
ORDER BY id
Keep in mind that change is a reserved word in MySQL, that's why I simply used the name change_v. There are ways you can use reserved words for column names, but I wouldn't advise you to do so.

Related

Finding an object in db and attaching an extra param from another table in ActiveRecord

I have two tables in my db: accounts and transactions. They look more or less like this, with relevant columns and example entries:
accounts
id | uid |
------------------
1 | "abcde" |
2 | "qwert" |
transactions
id | account_id | amount | balance |
------------------------------------
1 | 1 | 100 | 100 |
2 | 1 | 200 | 300 |
3 | 2 | 500 | 500 |
4 | 2 | 300 | 800 |
So basically Account has many Transactions, and the transaction tells what the amount was + it shows the balance after adding that transaction to the previous balance. So last transaction tells us what the current balance on the account is.
Now I would like to fetch an account, having only its uid, but with a twist - I'd like to also fetch the balance, which would be the balance column of the last transaction (based on id) that belongs to that account. Ideally this will give me an object of the Account model, with the additional balance param accessible using object[:balance].
I know that this would require a left outer join and a select that adds the balance param to the object, something like select('accounts.*', 'transactions.balance as balance') - plus picking up the last one, so ordering by id and limiting to 1 - but I have no idea how to construct this query (using activerecord methods or pure SQL), considering the fact that I only have access to the uid of the account, not its id which is referenced as the account_id.
You can try something along these lines:
WITH ranked AS (
SELECT t.*, ROW_NUMBER() OVER (PARTITION BY account_id ORDER BY id DESC) AS tr
FROM transactions AS t inner join accounts a on a.id = t.id
where a.uid = 'qwert'
)
SELECT * FROM ranked WHERE tr = 1;

select query performs wrong results on parallel execution

Following is my scenario
I have tables named
Products
id | name | count | Price
-------------------------
1 | meat | 1 | 10
Users
id | name | balance
-----------------
1 | Tim | 10
2 | Joe | 10
Work flow
select products if count >= 1,
reduce user's balance and count = count - 1
if no_balance or count < 1 throw error
Let's say if both users placing an order for 1 product at exact same time, products table count updates to -1, means query executes for both users.
Products
id | name | count | Price
-------------------------
1 | meat | -1 | 10
During placeing of an order,I have used the below query to select matching products
Select * from products where count >= 1 and price >= 10
Also, if users place orders with even little time difference, the expecting output gathered.
Is there any solution to this ?
You should consider use lock for each row, for example.
Select * from products where count >= 1 and price >= 10 FOR UPDATE.
But in your scenario, I advice you use Redis to do that.
How to design a second kill system for online shop

Mysql find a value between

Script outputs random value (winning ticket) from 0 to 5000 (in this case). Lets take 3001. I need a query to take a row where id = 4, because it has the winning ticket. How can I do it?
More info:
id 1 has tickets from 0 to 1000
id 2 has tickets from 1000 to 2000
id 3 has tickets from 2000 to 3000
id 4 has tickets from 3000 to 3500 <----- The winning ticket
id 5 has tickets from 3500 to 5000
Mysql table:
+-----------------------+
| id | Userid | Ticket |
+-----------------------+
| 1 | 1234567 | 1000 | // 0 to 1000
| 2 | 1234567 | 1000 | // 1000 to 2000
| 3 | 1234567 | 1000 | // 2000 to 3000
| 4 | 9876543 | 500 | // 3000 to 3500
| 5 | 1234567 | 1500 | // 3500 to 5000
+-----------------------+
Does the following query work for you :
set #winningticket=3001;
set #cstart=0;
SELECT id, Userid, (lastticket-tickets+1) firstticket, lastticket
FROM
(
SELECT id, Userid, Ticket as tickets, (#cstart := #cstart+Ticket) lastticket
FROM mytable
order by id) a
having #winningticket between firstticket and lastticket;
I have made the assumption that id=1 has tickets from 1-1000, id=2 has tickets from 1001 to 2000 - rather than the values you give - otherwise ticket 1000 is owned by id=1 AND id=2, 2000 by id=2 AND id=3 etc - if that is incorrect remove the +1 from the calculation for firstticket & the values will match yours.
The inner subquery creates a cumulative total for the final ticket number owned by that id using a user variable.
The outer query calculates the start ticket number - note this cannot be reliably done in the inner query as the first & last ticket numbers both rely on the same user variable & order of evaluation of columns is not guaranteed & use of the same user variable in the select list is advised against if it is modified in any way.
The outer query uses a having clause to select the winning ticket between first & last ticket number.

Select all if no subset is present, otherwise select subset

Ok here's my problem. Assume a customer has access to a number of regions defined in a CustomerRegions table:
CustomerRegionID | CustomerID | RegionID
----------------------------------------
1 | 1 | 1
2 | 1 | 2
Assume that customer 1 has three users 1, 2, and 3. For each user we can specify to which of the CustomerRegions they have access via a table UserRegions:
UserRegionID | UserID | CustomerRegionID
----------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
So user 1 will have access to both Customerregions and user 2 will only have access to CustomerRegion 2.
If there are UserRegions specified for a given user then only those CustomerRegions are present in the result set, but if no UserRegions are specified for a given user then all CustomerRegions are present in the result. I want to get all accessible regions per user of a given customer. The result I am looking for is something like this:
CustomerID | UserID | RegionID
------------------------------
1 | 1 | 1
1 | 1 | 2
1 | 2 | 2
1 | 3 | 1
1 | 3 | 2
My question is can this be done in a single query and how?
Edit:
I seem to have it working now:
SELECT CustomerID,
UserID,
RegionID
FROM users
LEFT JOIN customerregions ON customerregions.CustomerID = users.CustomerID
LEFT JOIN userregions ON userregions.UserID = users.UserID AND userregions.CustomerRegionID = customerregions.CustomerRegionID
LEFT JOIN regions ON regions.RegionID = customerregions.RegionID
WHERE (userregions.UserID IS NOT NULL
OR (SELECT COUNT(1) FROM userregions WHERE userregions.UserID = users.UserID) = 0)
AND CustomerID = 1
The extra count query in the where seems to do the trick. Thanks #Pablo Martinez for your help. However if someone knows of a better way to do this please let me know.
I'm aggre with #diEcho, the table structure is very confusing
have you try to do a join?
Select CustomerID, UserID, RegionID
from UserRegions join CustomerRegion
on CustomerRegion.CustomerRegionID=UserRegions.CustomerRegionID
where customerID=1

Using SQL to get distinct rows, but also the whole row for those

Ok so its easier to give an example and hopefully some has a solution:
I have table that holds bids:
ID | companyID | userID | contractID | bidAmount | dateAdded
Below is an example set of rows that could be in the table:
ID | companyID | userID | contractID | bidAmount | dateAdded
--------------------------------------------------------------
10 | 2 | 1 | 94 | 1.50 | 1309933407
9 | 2 | 1 | 95 | 1.99 | 1309933397
8 | 2 | 1 | 96 | 1.99 | 1309933394
11 | 103 | 1210 | 96 | 1.98 | 1309947237
12 | 2 | 1 | 96 | 1.97 | 1309947252
Ok so what I would like to do is to be able to get all the info (like by using * in a normal select statement) the lowest bid for each unique contractID.
So I would need the following rows:
ID = 10 (for contractID = 94)
ID = 9 (for contractID - 95)
ID = 12 (for contractID = 96)
I want to ignore all the others. I thought about using DISTINCT, but i haven't been able to get it to return all the columns, only the column I'm using for distinct.
Does anyone have any suggestions?
Thanks,
Jeff
select *
from mytable main
where bidAmount = (
select min(bidAmount)
from mytable
where contractID = main.contractID)
Note that this will return multiple rows if there is more than one record sharing the same minimum bid.
Didn't test it but it should be possible with this query although it might not be really fast:
SELECT * FROM bids WHERE ID IN (
SELECT ID FROM bids GROUP BY contractID ORDER BY MIN(bidAmount) ASC
)
This would be the query for MySQL, maybe you need to adjust it for another db.
You could use a subquery to find the lowest rowid per contractid:
select *
from YourTable
where id in
(
select min(id)
from YourTable
group by
ContractID
)
The problem is that distinct does not return a specific row - it return distinct values, which ( by definition ) could occur on multiple rows.
Subqueries are your answer, and somewhere in the suggestions above is probably the answer. Your subquery need to return the ids or the rows with the minimum bidvalue. Then you can select * from the rows with those ids.