select query performs wrong results on parallel execution - mysql

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

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;

How to write a sql show the quantity result based on filter and merge two different column if no filtering option?

Table
|---------------------|------------------|------------------|
| ID | qty_ordered | qty_received |
|---------------------|------------------|------------------|
| 10 | 30 | |
|---------------------|------------------|------------------|
| 10 | 30 | 20 |
|---------------------|------------------|------------------|
I have a filter to let user choose whether to show qty_ordered or qty_received. When user select filter for qty_ordered then result will be 30 else if select qty_received then result will be 20 else will return 50.
SQL
select sum(qty_ordered) as order, sum(qty_received) as received from table...
Currently i will select both, and using if-else statement in my html to show the result of quantity. If filtering by qty_ordered and qty_received will be fine, but problem occurred when need to display all quantity. Mean, i need to merge ordered quantity and received quantity with same id as one row.
|---------------------|------------------|
| ID | quantity |
|---------------------|------------------|
| 10 | 50 |
|---------------------|------------------|
ps: Ordered quantity will only be sum up if there is no received quantity.
You can try aggregating by the ID and using SUM to obtain single values for the quantity ordered and received:
SELECT
ID,
SUM(qty_ordered) AS qty_ordered,
SUM(qty_received) AS qty_received,
SUM(COALESCE(qty_received, qty_ordered)) AS qty_total
FROM yourTable
GROUP BY ID;
ID qty_ordered qty_received qty_total
1 10 60 20 50
I use a COALESCE trick in the expression for the total sum. The logic is that if the quantity received is present, we count it, even if the quantity ordered might also be present. If the quantity received be absent, only then do we count the quantity ordered.
Demo

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.

MySQL nested query

I have 2 tables, one containing products, one purchases. I'm trying to find the average of the last 3 purchase prices for each product. So in the example below, for the product 'beans' I would want to return the average of the last 3 purchase prices before the product time 1230854663, i.e. average of customer C,D,E (239)
Products
+-------+------------+
| Name | time |
+-------+------------+
| beans | 1230854764 |
+-------+------------+
Purchases
+----------+------------+-------+
| Customer | time | price |
+----------+------------+-------+
| B | 1230854661 | 207 |
| C | 1230854662 | 444 |
| D | 1230854663 | 66 |
| E | 1230854764 | 88 |
| A | 1230854660 | 155 |
+----------+------------+-------+
I've come up with a nested select query which nearly gets me there i.e. it works if I hard code the time:
SELECT products.name,(SELECT avg(temp.price) FROM (select purchases.price from purchases WHERE purchases.time < 1230854764 order by purchases.time desc limit 3) temp) as av_price
from products products
But if the query references product.time rather than a hard coded time such as below I get an error that column products.time does not exist.
SELECT products.name,(SELECT avg(temp.price) FROM (select purchases.price from purchases WHERE purchases.time < products.time order by purchases.time desc limit 3) temp) as av_price from products products
I'm not sure if I'm making a simple mistake with nested queries or I'm going about this in totally the wrong way and should be using joins or some other construct, either way I'm stuck. Any help would be greatfully received.
The only problem in your query is you haven't mentioned products table in your inner query.
SELECT products.name,(SELECT avg(temp.price)
FROM (select purchases.price from purchases,products
WHERE purchases.time < products.time order by purchases.time desc limit 3) temp) as
av_price from products products

ORDER BY on multiple conditions

I need a query that will give me the result that is either (a) the highest price or (b) the one with the oldest timestamp. In all cases, price should trump timestamp (ie if a record has a really old timestamp by higher price than all others, it should always return the record with the highest price)
Here are a few scenarios:
id | price | date
1 | 5 | 2012-02-20 08:59:06
2 | 5 | 2012-02-20 09:59:06
3 | 7 | 2012-02-20 10:59:06
Should return id 3 because it is highest price
id | price | date
1 | 5 | 2012-02-20 08:59:06
2 | 5 | 2012-02-20 09:59:06
3 | 5 | 2012-02-20 10:59:06
should return id 1 since it is the oldest
In my current query i am doing this:
SELECT * FROM table ORDER BY price, date DESC LIMIT 1
Unfortunately this query is not working how i've outlined it above.
Thanks for your help
I'm having trouble determining precisely what you are after, however it sounds like you are looking for the oldest timestamp for the highest price, and so the following should suffice
SELECT *
FROM table
ORDER BY
price DESC, // Favour the highest price
date ASC // Now get the one with oldest date at this price
LIMIT 1
i hope i understand your question, try this
SELECT
p.*
FROM table p
WHERE price = (SELECT MAX(price) FROM table)
OR date = (SELECT MIN(date) FROM table)