Count all items on which a user is the high bidder - mysql

I am currently working on an auction system, and the functionality is all complete. I now need to add a count to the user's profile which shows how many items the user is currently bidding on.
The system comprises of two key tables (extra tables feature in the system of course, but these are the only tables related to this issue):
item_sales:
+-----+------------------+------------+-------------+---------+
| id | selling_format | duration | list_date | buyer |
+-----+------------------+------------+-------------+---------+
item_sales_bids:
+-----+-------------+-----------+---------------+-----------+--------+
| id | sale_item | user_id | current_bid | max_bid | date |
+-----+-------------+-----------+---------------+-----------+--------+
item_sales_bids.date is a Unix timestamp of the bid time.
I can easily get a count of all bids a given user has made with the following query:
SELECT COUNT(DISTINCT(`item_sales_bids`.`user_id`)) AS `total`,
SUM((`sale`.`list_date` + (`sale`.`duration` * 86400)) - UNIX_TIMESTAMP()) AS `endTime`
FROM `item_sales_bids`
INNER JOIN `item_sales` `sale` ON `item_sales_bids`.`sale_item` = `sale`.`id`
WHERE `user_id` = 1
GROUP BY `sale_item`
HAVING `endTime` > 0
What I would like to do, is run a query similar to the above, but only include records where the specified user is the current highest bidder (i.e. the max ID entry for a given item's bid set has a user_id value = to our user).
Unfortunately, I'm at a loss on how I might achieve this.
I have set up an SQLFiddle to assist > http://sqlfiddle.com/#!2/b98e4/3

Do a subs query to get the latest bid for all items and then join that to item_sales_bids to only process the latest items.
Something like this:-
SELECT COUNT(DISTINCT(item_sales_bids.user_id)) AS total,
SUM((sale.list_date + (sale.duration * 86400)) - UNIX_TIMESTAMP()) AS endTime
FROM item_sales_bids
INNER JOIN item_sales sale ON item_sales_bids.sale_item = sale.id
INNER JOIN
(
SELECT sale_item, MAX(id) AS LatestBid
FROM item_sales_bids
GROUP BY sale_item
) Sub1
ON item_sales_bids.sale_item = Sub1.sale_item AND item_sales_bids.id = Sub1.LatestBid
WHERE user_id = 1
GROUP BY item_sales_bids.sale_item
HAVING endTime > 0

This should do the trick. Toggling between user 1 & 2 will show the desired behavior. In the sample data, user 1 returns no data (they are not the high bidder) and user 2 returns a single row, with a current high bid of 50.
SELECT COUNT(DISTINCT(`bids`.`user_id`)) AS `total`,
`highbids`.`sale_item`,
`highbids`.`maxBid`,
SUM((`sale`.`list_date` + (`sale`.`duration` * 86400)) - UNIX_TIMESTAMP()) AS `endTime`
FROM `item_sales_bids` `bids`
INNER JOIN `item_sales` `sale` ON `bids`.`sale_item` = `sale`.`id`
INNER JOIN (SELECT MAX(`current_bid`) AS `maxBid`, `sale_item`, `user_id`
FROM `item_sales_bids`
GROUP BY `sale_item`, `user_id`
ORDER BY `current_bid` DESC LIMIT 1,1)
AS `highbids` ON `bids`.`user_id` = `highbids`.`user_id`
WHERE `bids`.`user_id` = 2
GROUP BY `bids`.`sale_item`
HAVING `endTime` > 0

Related

Is there a way to calculate total from a MySQL Query?

I'm trying to build a cart system. So far, I can pull the user cart data easily with some join and even calculate the total price based on unit price and quantity user has in another column. However, I wanted to know if there is a way to actually compute the total amount owed directly from an SQL query. Allow me to showcase an example. Here are my tables:
products
product_id | product_name | product_price | stock
1 | Pasta | 10 | 50
2 | Bread | 2 | 100
cart
user_id | product_id | quantity
1 | 1 | 3
1 | 2 | 2
My Current Query
SELECT
`cart`.`product_id`,
`cart`.`user_id`,
`cart`.`quantity`,
`products`.`product_price` as `unit_price`,
`products`.`product_name`,
`cart`.`quantity` * `products`.`product_price` as `Total`
FROM `cart`
INNER JOIN `products` ON `products`.`product_id` = `cart`.`product_id`
WHERE `cart`.`u_id` = 1;
As you can see, the query above will work and return me all the products in the cart of a specific user and add the Total column to the result with the total price for each item.
Now, if I want to calculate the gross total, I have to read each row in my PHP code and do the total. Although I can do this, I was wondering if MySQL has a way of returning just the gross total directly through a single query.
Bonus question: Is a cart table structure like the one above good enough?
You can use "SUM" Operator in MYSQL to get the value from database directly.
Here is a documentation where you can get more info:
https://www.mysqltutorial.org/mysql-sum/
Regarding the code here is an example:
SELECT SUM(`cart`.`quantity` * `products`.`product_price`) as `CartTotal`
FROM `cart`
INNER JOIN `products` ON `products`.`product_id` = `cart`.`product_id`
WHERE `cart`.`u_id` = 1;
You can calculate total cart cost using SUM aggregation function.
SELECT SUM(`cart`.`quantity` * `products`.`product_price`) as `CartTotal`
FROM `cart`
INNER JOIN `products` ON `products`.`product_id` = `cart`.`product_id`
WHERE `cart`.`u_id` = 1;
Also you can use UNION construction for combine cart total result to main query like:
-- Get cart content for user
SELECT
`cart`.`product_id`,
`cart`.`user_id`,
`cart`.`quantity`,
`products`.`product_price` as `unit_price`,
`products`.`product_name`,
`cart`.`quantity` * `products`.`product_price` as `Total`
FROM `cart`
INNER JOIN `products` ON `products`.`product_id` = `cart`.`product_id`
WHERE `cart`.`u_id` = 1
UNION -- Union summary row to cart results
SELECT
NULL as `product_id`, -- set NULL for non aggregated field
`cart`.`user_id`,
COUNT(*), -- calculate items count in cart
NULL as `unit_price`, -- set NULL for non aggregated field
'Total' as `product_name`, -- set 'Total' string as product name
SUM(`cart`.`quantity` * `products`.`product_price`) as `CartTotal` -- calculate total cart cost
FROM `cart`
INNER JOIN `products` ON `products`.`product_id` = `cart`.`product_id`
WHERE `cart`.`u_id` = 1;

MySQL merge multiple queries for report

See SQLFiddle here: http://sqlfiddle.com/#!9/9bb273
I need to create a report out of 3 queries. It needs to be a single query without subqueries (due to ORM limitations).
The main query is:
SELECT SQL_CALC_FOUND_ROWS
Organization.name as organization_name,
Program.unique_id as program_uuid,
Program.name as program_name,
Program.start_date,
Program.end_date,
Program.grace_period,
'Placeholder A' as 'Participant Count',
'Placeholder B' as 'Total Participant Points',
count(distinct Transaction.id) as 'Transaction Count',
sum(TransactionItem.quantity) as 'Total Redemptions',
sum(((TransactionProduct.retail + IFNULL(TransactionProduct.shipping,0) + IFNULL(TransactionProduct.handling,0)) * TransactionItem.quantity)) as 'Total'
FROM `TransactionItem`
JOIN `Transaction` ON `Transaction`.id = `TransactionItem`.transaction_id
JOIN `TransactionProduct` ON `TransactionItem`.reference_id = `TransactionProduct`.reference_id
JOIN `Participant` ON `Transaction`.participant_id = `Participant`.id
JOIN `Program` ON `Program`.id = `Participant`.program_id
JOIN `Organization` ON `Organization`.id = `Participant`.organization_id
WHERE 1=1
AND `Organization`.`unique_id` = 'demo2'
AND `Program`.`unique_id` = 'demo2'
AND `Transaction`.`created_at` >= '2018-10-01 00:00:00'
AND `Transaction`.`created_at` <= '2018-12-18 00:00:00';
As you can see, this report is for the date range between 10/1 and 12/18. The result set that makes up the report is...
organization_name | program_uuid | program_name | start_date | end_date | grace_period | Participant Count | Total Participant Points | Transaction Count | Total Redemptions | Total
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Demo2 Org demo2 Demo2 2018-10-01 2018-12-27 5 Placeholder A Placeholder B 11 92 2853.13
As you can see, there are 2 data points that I'm unable to get from this query.
(1) The total number of participants belonging to the "demo2" Program. This query gets that data point.
/* Placeholder A */
select program_id, count(*) as 'Participant Count' from participant
where active = 1
group by program_id;
Returns:
program_id | Participant Count
----------------------------------
2 102
(2) The sum of Adjustments.amount for all rows between dates 10/1 and 12/18. This query fulfills that.
/* Placeholder B */
select sum(amount) as 'Total Particpant Points' from adjustment
where participant_id in (select id from participant where program_id =2)
and type = 1
and created_at >= '2018-10-01 00:00:00' and created_at <= '2018-12-18 00:00:00';
Returns:
Total Participant Points
------------------------
10000.50000
Is there a way to gather all of this data in a single query without subqueries?

MySQL Unique Exemption Case with Group By

I have a set of data that lists when a User changes a Product, we look at who changed it, when, the old cost and new cost, and the percentage price difference.
I want to use a group by, where statement, or case to group by and exclude products that filters out changes were the change occurred in the same day and resulted in the original price staying.
So the situation I want to exclude would look like this:
| product | Changed By | Old Price | New Price | % diff | Day Changed |
|----------|------------|-----------|-----------|--------|-------------|
| blue hat | me | 94.00 | 95.00 | 1.05 | 2016-11-28 |
| blue hat | me | 95.00 | 94.00 | 1.05 | 2016-11-28 |
Any ideas how to do this with MySql?
Here is a working version for anyone who wants to see this done using subqueries, where's, and group by's.
This query looks at the changes to an Item's cost by a User for the span of 1 day, where it pulls in all the results from "yesterday". It lists all the changes for that day one asc and one desc and compares the price changes that way. If they are the same from the oldest change to the newest change of that say then it is exempted.
SELECT
us.Name,
it.Name,
pal.CS_PA_Line_Supplier__c as supplier_name,
newest.NewValue as new_value,
oldest.OldValue as old_value,
((newest.NewValue - oldest.OldValue) / oldest.OldValue) * 100 as Percentage
FROM
(
SELECT Id, Name, KNDY4__Item__c, CS_PA_Line_Supplier__c
FROM KNDY4__Contract_Line__c
) pal
LEFT JOIN
(
SELECT *
FROM
(
SELECT
ParentId,
CreatedById,
CreatedDate,
Field,
NewValue
FROM KNDY4__Contract_Line__History
WHERE CreatedDate >= CURDATE() - INTERVAL 1 DAY
AND CreatedDate < CURDATE()
AND Field='KNDY4__Negotiated_Price__c'
ORDER BY CreatedDate DESC
) cd
GROUP BY ParentId
) newest
ON newest.ParentId=pal.Id
LEFT JOIN
(
SELECT * FROM (
SELECT
ParentId,
OldValue
FROM KNDY4__Contract_Line__History
WHERE CreatedDate >= CURDATE() - INTERVAL 1 DAY
AND CreatedDate < CURDATE()
AND Field='KNDY4__Negotiated_Price__c'
ORDER BY CreatedDate ASC
) cd
GROUP BY ParentId
) oldest
ON oldest.ParentId=pal.Id
LEFT JOIN
(
SELECT Id, Name
FROM User
) us
ON us.Id = newest.CreatedById
LEFT JOIN
(
SELECT Id,Name
FROM KNDY4__Item__c
) it
ON it.Id=pal.KNDY4__Item__c
WHERE newest.ParentId IS NOT NULL
AND oldest.OldValue IS NOT NULL
AND newest.NewValue != oldest.OldValue
GROUP BY pal.KNDY4__Item__c, pal.CS_PA_Line_Supplier__c
ORDER BY it.Name ASC

MySQL sum only new rows

I have a MySQL table with this format
transaction | user | amount | week
------------|---------|--------|------
1 | user_1 | 100 | 1
2 | user_2 | 50 | 1
3 | user_1 | 50 | 2
4 | user_3 | 200 | 2
I know how to calculate the sum amount for each week in MySQL, but is there a way in MySQL to calculate new users sum amount per week?
So for this table it would be:
week 1 = 150
week 2 = 200
Yes, there is. You can use the value of MIN(week) to indicate a particular user's first transaction.
SELECT w, SUM(amount)
FROM(
SELECT user, amount, MIN(week) AS w
FROM `trans`
GROUP BY user) newUserTrans
GROUP BY w
If you want to still show a week where there is no new user, then you can use this:
SELECT week, IFNULL(SUM(amount),0) AS total
FROM(
SELECT user, amount, MIN(week) AS w
FROM `trans`
GROUP BY user) newUserTrans RIGHT JOIN (SELECT DISTINCT week FROM trans) weeks ON newUserTrans.w = weeks.week
GROUP BY w
ORDER BY week
UPDATE:
Based on #skobaljic opinion, I also provide another alternative for the case when a user may have multiple records in the same week.
SELECT weeks.week AS Week, IFNULL(SUM(amount),0) AS Total
FROM(
SELECT trans.user, trans.amount, trans.week
FROM trans
JOIN (SELECT user, MIN(week) AS w
FROM trans
GROUP BY user) newWeek ON trans.user = newWeek.user
AND trans.week = newWeek.w) newUserTrans
RIGHT JOIN (SELECT DISTINCT week FROM trans) weeks ON newUserTrans.week = weeks.week
GROUP BY newUserTrans.week
ORDER BY weeks.week
Hope it helps.
I hope this could be helpful in any way:
select sum(A.`amount`) from `table` AS A WHERE A.`user` NOT IN (SELECT B.`user` FROM `table` AS B where A.`week` > B.`week') AND A.`week` = #pWeek
where #pWeek is a parameter with a particular number of a week
I think there is a better way of get the first week for a user.
First week is the minumum week for a user.
select
trans.week
,sum(amount) as amount
from trans
inner join
(select
user
,min(week) as firstweek
from
trans
group by
user
) as firstweek
on trans.user = firstweek.user
where
trans.week = firstweek.firstweek
group by
trans.week
$query = "SELECT * FROM [table] GROUP BY week";
this should work. I don't think you will need the sum(amount), but try it both ways.

MySQL SELECT combining 3 SELECTs INTO 1

Consider following tables in MySQL database:
entries:
creator_id INT
entry TEXT
is_expired BOOL
other:
creator_id INT
entry TEXT
userdata:
creator_id INT
name VARCHAR
etc...
In entries and other, there can be multiple entries by 1 creator. userdata table is read only for me (placed in other database).
I'd like to achieve a following SELECT result:
+------------+---------+---------+-------+
| creator_id | entries | expired | other |
+------------+---------+---------+-------+
| 10951 | 59 | 55 | 39 |
| 70887 | 41 | 34 | 108 |
| 88309 | 38 | 20 | 102 |
| 94732 | 0 | 0 | 86 |
... where entries is equal to SELECT COUNT(entry) FROM entries GROUP BY creator_id,
expired is equal to SELECT COUNT(entry) FROM entries WHERE is_expired = 0 GROUP BY creator_id and
other is equal to SELECT COUNT(entry) FROM other GROUP BY creator_id.
I need this structure because after doing this SELECT, I need to look for user data in the "userdata" table, which I planned to do with INNER JOIN and select desired columns.
I solved this problem with selecting "NULL" into column which does not apply for given SELECT:
SELECT
creator_id,
COUNT(any_entry) as entries,
COUNT(expired_entry) as expired,
COUNT(other_entry) as other
FROM (
SELECT
creator_id,
entry AS any_entry,
NULL AS expired_entry,
NULL AS other_enry
FROM entries
UNION
SELECT
creator_id,
NULL AS any_entry,
entry AS expired_entry,
NULL AS other_enry
FROM entries
WHERE is_expired = 1
UNION
SELECT
creator_id,
NULL AS any_entry,
NULL AS expired_entry,
entry AS other_enry
FROM other
) AS tTemp
GROUP BY creator_id
ORDER BY
entries DESC,
expired DESC,
other DESC
;
I've left out the INNER JOIN and selecting other columns from userdata table on purpose (my question being about combining 3 SELECTs into 1).
Is my idea valid? = Am I trying to use the right "construction" for this?
Are these kind of SELECTs possible without creating an "empty" column? (some kind of JOIN)
Should I do it "outside the DB": make 3 SELECTs, make some order in it (let's say python lists/dicts) and then do the additional SELECTs for userdata?
Solution for a similar question does not return rows where entries and expired are 0.
Thank you for your time.
This should work (assuming all creator_ids appear in the userdata table.
SELECT userdata.creator_id, COALESCE(entries_count_,0) AS entries_count, COALESCE(expired_count_,0) AS expired_count, COALESCE(other_count_,0) AS other_count
FROM userdata
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS entries_count_
FROM entries
GROUP BY creator_id) AS entries_q
ON userdata.creator_id=entries_q.creator_id
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS expired_count_
FROM entries
WHERE is_expired=0
GROUP BY creator_id) AS expired_q
ON userdata.creator_id=expired_q.creator_id
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS other_count_
FROM other
GROUP BY creator_id) AS other_q
ON userdata.creator_id=other_q.creator_id;
Basicly, what you are doing looks correct to me.
I would rewrite it as follows though
SELECT entries.creator_id
, any_entry
, expired_entry
, other_entry
FROM (
SELECT creator_id, COUNT(entry) AS any_entry,
FROM entries
GROUP BY creator_id
) entries
LEFT OUTER JOIN (
SELECT creator_id, COUNT(entry) AS expired_entry,
FROM entries
WHERE is_expired = 1
GROUP BY creator_id
) expired ON expired.creator_id = entries.creator_id
LEFT OUTER JOIN (
SELECT creator_id, COUNT(entry) AS other_entry
FROM other
GROUP BY creator_id
) other ON other.creator_id = entries.creator_id
How about
SELECT creator_id,
(SELECT COUNT(*)
FROM entries e
WHERE e.creator_id = main.creator_id AND
e.is_expired = 0) AS entries,
(SELECT COUNT(*)
FROM entries e
WHERE e.creator_id = main.creator_id AND
e.is_expired = 1) as expired,
(SELECT COUNT(*)
FROM other
WHERE other.creator_id = main.creator_id) AS other,
FROM entries main
GROUP BY main.creator_id;