Retrieving top 2 bids for each item in an auction - mysql

I am having trouble combining data from multiple tables. I have tried joins and subqueries but to no avail. I basically need to combine 2 queries into one. My tables (simplified):
Stock:
id int(9) PrimaryIndex
lot_number int(4)
description text
reserve int(9)
current_bid int(9)
current_bidder int(6)
Members:
member_id int(11) PrimaryIndex
name varchar(255)
Bids:
id int(9)
lot_id int(9)
bidder_id int(5)
max_bid int(9)
time_of_bid datetime
I'm currently using 2 separate queries which with 1000's of lots, makes it very inefficient. 1st query:
SELECT S.id, S.lot_number, S.description, S.reserve FROM stock S ORDER BY
S.lot_number ASC
The 2nd query within a while loop then gets the bidding info:
SELECT DISTINCT B.bidder_id, B.lot_id, B.max_bid, B.time_of_bid,
M.fname, M.lname FROM bids B, members M WHERE B.lot_id=? AND
B.bidder_id=M.member_id ORDER BY B.max_bid DESC LIMIT 2
Below is what i would like as output from a single query, if possible:
Lot No. | Reserve | Current Bid | 1st Max Bid | 1st Bidder | 2nd Max Bid | 2nd Max Bidder
1 | $100 | $120 | $150 | Steve | $110 | John
2 | $500 | $650 | $900 | Tom | $600 | Paul
I have had partial success with just getting the MAX(B.bid) and then its related details (WHERE S.id=B.id), but i cant get the top 2 bids for each lot.

First assign a row number rn to rows within each group of lot_id in table bids (highest bid gets 1, 2nd highest bid gets 2 and so on). The highest bid and second highest bid will be on two different rows after the LEFT JOIN. Use GROUP BY to merge the two rows into one.
select s.lot_number, s.reserve, s.current_bid,
max( case when rn = 1 then b.max_bid end) as first_max_bid,
max( case when rn = 1 then m.name end) as first_bidder,
max( case when rn = 2 then b.max_bid end) as second_max_bid,
max( case when rn = 2 then m.name end ) as second_bidder
from
stock s
left join
(select * from
(select *,
(#rn := if(#lot_id = lot_id, #rn+1,
if( #lot_id := lot_id, 1, 1))) as rn
from bids cross join
(select #rn := 0, #lot_id := -1) param
order by lot_id, max_bid desc
) t
where rn <= 2) b
on s.lot_number = b.lot_id
left join members m
on b.bidder_id = m.member_id
group by s.lot_number, s.reserve, s.current_bid
order by s.lot_number

Related

Group_Concat with multiple joined tables

I have two main tables that comprise bookings for events.
A Registrants table (Bookings) R and an Events table E.
There are also two connected tables, Field_Values V and Event_Categories C
This diagram shows the relationship
What I am trying to do is create an Invoice query that mirrors the user's shopping cart. Often a user will book multiple events in one transaction, so my invoice should have columns for the common items e.g. User Name, User Email, Booking Date, Transaction ID and aggregated columns for the invoice line item values e.g. Quantity "1,2" Description "Desc1, Desc2" Price "10.00, 20.00" where there are two line items in the shopping cart.
The Transaction ID (dcea4_eb_registrant.transaction_id) is unique per Invoice and repeated per line item in that sale.
I have the following query which produces rows for each line item
SELECT
R.id as ID,
E.event_date as ServiceDate,
E.event_date - INTERVAL 1 DAY as DueDate,
Concat('Ad-Hoc Booking:',E.title) as ItemProductService,
Concat(R.first_name, ' ',R.last_name) as Customer,
R.first_name as FirstName,
R.last_name as LastName,
R.email,
R.register_date as InvoiceDate,
R.amount as ItemAmount,
R.comment,
R.number_registrants as ItemQuantity,
R.transaction_id as InvoiceNo,
R.published as Status,
E.event_date AS SERVICEDATE,
Concat('Ad-Hoc Booking:',E.title) AS DESCRIPTION,
R.number_registrants AS QUANTITY,
FORMAT(R.amount / R.number_registrants,2) AS RATE,
R.amount AS AMOUNT,
C.category_id as CLASS,
Concat(Group_Concat(V.field_value SEPARATOR ', '),'. ',R.comment) as Memo
FROM dcea4_eb_events E
LEFT JOIN dcea4_eb_registrants R ON R.event_id = E.id
LEFT JOIN dcea4_eb_field_values V ON V.registrant_id = R.id
LEFT JOIN dcea4_eb_event_categories C ON C.event_id = R.event_id
WHERE 1=1
AND V.field_id IN(14,26,27,15)
AND R.published <> 2 /*Including this line omits Cancelled Invoices */
AND R.published IS NOT NULL
AND (R.published = 1 OR R.payment_method = "os_offline")
AND (R.register_date >= CURDATE() - INTERVAL 14 DAY)
GROUP BY E.event_date, E.title, R.id, R.first_name, R.last_name, R.email,R.register_date, R.amount, R.comment
ORDER BY R.register_date DESC, R.transaction_id
This produces output like this
I'm using the following query to try to group together the rows with a common transaction_ID (rows two and three in the last picture) - I add group_concat on the columns I want to aggregate and change the Group By to be the transaction_id
SELECT
R.id as ID,
E.event_date as ServiceDate,
E.event_date - INTERVAL 1 DAY as DueDate,
Concat('Ad-Hoc Booking:',E.title) as ItemProductService,
Concat(R.first_name, ' ',R.last_name) as Customer,
R.first_name as FirstName,
R.last_name as LastName,
R.email,
R.register_date as InvoiceDate,
R.amount as ItemAmount,
R.comment,
R.number_registrants as ItemQuantity,
R.transaction_id as InvoiceNo,
R.published as Status,
Group_ConCat( E.event_date) AS SERVICEDATE,
Group_ConCat( Concat('Ad-Hoc Booking:',E.title)) AS DESCRIPTION,
Group_ConCat( R.number_registrants) AS QUANTITY,
Group_ConCat( FORMAT(R.amount / R.number_registrants,2)) AS RATE2,
Group_ConCat( R.amount) AS AMOUNT,
Group_ConCat( C.category_id) as CLASS,
Concat(Group_Concat(V.field_value SEPARATOR ', '),'. ',R.comment) as Memo
FROM dcea4_eb_events E
LEFT JOIN dcea4_eb_registrants R ON R.event_id = E.id
LEFT JOIN dcea4_eb_field_values V ON V.registrant_id = R.id
LEFT JOIN dcea4_eb_event_categories C ON C.event_id = R.event_id
WHERE 1=1
AND V.field_id IN(14,26,27,15)
AND R.published <> 2 /*Including this line omits Cancelled Invoices */
AND R.published IS NOT NULL
AND (R.published = 1 OR R.payment_method = "os_offline")
AND (R.register_date >= CURDATE() - INTERVAL 14 DAY)
GROUP BY R.transaction_id
ORDER BY R.register_date DESC, R.transaction_id
But this produces this output
It seems to be multiplying the rows. The Quantity column in the first row should just be 1 and in the second row it should be 2,1 .
I've tried using Group_Concat with DISTINCT but this doesn't work because often the values being concatenated are the same (e.g. the price for two events being booked are both the same) and the query only returns one value e.g. 10 and not 10, 10. The latter being what I need.
I'm guessing the issue is around the way the tables are joined but I'm struggling to work out how to get what I need.
Pointers in the right direction most appreciated.
You seem determined to go in what seems to me to be the wrong direction, so here's a gentle nudge down that hill...
Consider the following...
CREATE TABLE users
(user_id SERIAL PRIMARY KEY
,username VARCHAR(12) UNIQUE
);
INSERT INTO users VALUES
(101,'John'),(102,'Paul'),(103,'George'),(104,'Ringo');
DROP TABLE IF EXISTS sales;
CREATE TABLE sales
(sale_id SERIAL PRIMARY KEY
,purchaser_id INT NOT NULL
,item_code CHAR(1) NOT NULL
,quantity INT NOT NULL
);
INSERT INTO sales VALUES
( 1,101,'A',1),
( 2,103,'A',2),
( 3,103,'A',3),
( 4,104,'A',1),
( 5,104,'A',2),
( 6,104,'A',3),
( 7,103,'B',2),
( 8,103,'B',2),
( 9,104,'B',3),
(10,103,'B',2),
(11,104,'B',2),
(12,104,'B',1);
SELECT u.*
, x.sale_ids
, x.item_codes
, x.quantities
FROM users u
LEFT
JOIN
( SELECT purchaser_id
, GROUP_CONCAT(sale_id ORDER BY sale_id) sale_ids
, GROUP_CONCAT(item_code ORDER BY sale_id) item_codes
, GROUP_CONCAT(quantity ORDER BY sale_id) quantities
FROM sales
GROUP
BY purchaser_id
) x
ON x.purchaser_id = u.user_id;
+---------+----------+---------------+-------------+-------------+
| user_id | username | sale_ids | item_codes | quantities |
+---------+----------+---------------+-------------+-------------+
| 101 | John | 1 | A | 1 |
| 102 | Paul | NULL | NULL | NULL |
| 103 | George | 2,3,7,8,10 | A,A,B,B,B | 2,3,2,2,2 |
| 104 | Ringo | 4,5,6,9,11,12 | A,A,A,B,B,B | 1,2,3,3,2,1 |
+---------+----------+---------------+-------------+-------------+

MYSQL Can I find the highest value product in a specific category

I have an assignment where I am working with a product database and I need to find the highest priced phone at each store location where the carrier is Sprint. I want to find the phone name and ID.
I.E I want to find the phone.name and phone.id of each Sprint phone store.carrier = "Sprint" where item.price is the greatest of its store.
I have tried a variety of SQL commands sorting by Max(price) and then trying to take distinct values by brand.
Relations:
store(id, name, carrier)
phone(id, name, location, price)
Where
store.id is the primary key of store.
product.id is the primary key of product.
product.location is the foreign key that references store.id.
I have tried
SELECT distinct phone.name, phone.id
FROM phone
JOIN store ON phone.location = store.id
WHERE store.carrier = 'sprint'
ORDER BY price DESC LIMIT 1;
and have also tried
SELECT distinct phone.name, phone.id
FROM phone
JOIN store ON phone.location = store.id
WHERE store.carrier = 'sprint'
AND phone.price >= (SELECT MAX
FROM (SELECT MAX(price)
FROM phone
INNER JOIN store ON phone.location = store.id
GROUP BY location))
GROUP BY phone.id;
I expect an output like:
+-----+--------------------------+
| ID | NAME |
+-----+--------------------------+
| 12 | iPhone XS MAX |
| 97 | Samsung Galaxy S10 Plus |
| 143 | iPhone XS MAX |
| 163 | Google Pixel 3XL |
| 194 | iPhone XS MAX |
+-----+--------------------------+
Instead, I get either a list of all the Sprint phones, or just the most valuable phone overall.
To get top 1 rows, you can use an recursive iteration within the subquery. And then use all row ranking values equals 1 to filter out in the main query :
SELECT q.id phone_id, q.name phone_name, q.store_id
FROM
(
SELECT p.id, p.name, p.location as store_id, p.price,
#rn := IF(#store = p.location, #rn + 1, 1) AS phone_rank,
#store := p.location as sto
FROM phone p
JOIN (SELECT #store := 1, #rn := 0) as q_iter
WHERE EXISTS ( SELECT 1 FROM store s WHERE s.id = p.location and s.carrier ='sprint' )
ORDER BY p.location, p.price DESC
) q
WHERE phone_rank = 1
ORDER BY q.store_id;
Demo

SQL - Select distinct column without excluding rows

I'm running a query that aggregates sales information by either category or subcategory within some date range. I was asked to add budget information to it for a report that displays the information fetched by this query.
SELECT
DATE_FORMAT(B.TxnDate, "%Y-%m") AS FormattedTxnDate,
SUM(B.Quantity) AS QuantitySum,
SUM(B.Quantity * B.Amount) AS Revenue,
SUM(CASE WHEN B.AverageCost = 0 THEN (B.Quantity * C.PurchaseCost) ELSE (B.Quantity *
B.AverageCost) END) AS COGS,
A.CustomerRefFullName,
SUM(D.Budget_2018) AS Budget,
D.Brand, D.Category, D.Subcategory, D.ProductManager, C.VendorRefFullName
FROM
qb_invoice_info A, qb_invoice_line_info B, qb_item_info C, qb_item_group D
WHERE
A.TxnID = B.TxnID
AND
B.Item_ListID = C.ListID
AND
C.Parent_ListID = D.ListID
AND
(C.Type = "Inventory" OR C.Type = "InventoryAssembly")
AND
B.TxnDate BETWEEN ? AND ?
GROUP
BY D.Category, YEAR(B.TxnDate), QUARTER(B.TxnDate)
ORDER
BY D.Category ASC, YEAR(B.TxnDate) ASC, QUARTER(B.TxnDate) ASC
Every subcategory has its own budget amount. The problem is that some subcategories share all of the same information except for their unique IDs. A few records might look like this within the qb_item_group table.
qb_item_group
id | Category | Subcategory | Budget
------------------------------------
1A | Lights | DMX | 4000
1B | Lights | DMX | 4000
1C | Lights | DMX | 4000
2A | Lights | Flash | 5000
3A | Lights | Bulbs | 1000
In this case, the total budget for lights would be 10,000 because we ignore two of the DMX budgets. I tried SUM(DISTINCT D.Budget_2018 AS Budget earlier today but it failed as I expected because it's only adding unique budget values. How can I adapt the query I have above so that I can retrieve all sales records by either category or subcategory but still get a total budget that is the sum of all unique subcategories under the parent category?
SELECT
DATE_FORMAT(B.TxnDate, "%Y-%m") AS FormattedTxnDate,
SUM(B.Quantity) AS QuantitySum,
SUM(B.Quantity * B.Amount) AS Revenue,
SUM(
CASE
WHEN B.AverageCost = 0 THEN (B.Quantity * C.PurchaseCost)
ELSE (B.Quantity * B.AverageCost)
END
) AS COGS,
A.CustomerRefFullName,
COALESCE(category_budgets.budget, 0) AS budget,
D.Brand,
D.Category,
D.Subcategory,
D.ProductManager,
C.VendorRefFullName
FROM
qb_invoice_info A,
qb_invoice_line_info B,
qb_item_info C,
qb_item_group D
LEFT JOIN
(
SELECT
a.category,
SUM(a.budget) as budget
FROM
(
SELECT DISTINCT
category
budget
FROM
budgets
) a
) category_budgets
ON
category_budgets.category = D.category
WHERE
A.TxnID = B.TxnID
AND
B.Item_ListID = C.ListID
AND
C.Parent_ListID = D.ListID
AND
(C.Type = "Inventory" OR C.Type = "InventoryAssembly")
AND
B.TxnDate BETWEEN ? AND ?
GROUP BY
D.Category, YEAR(B.TxnDate), QUARTER(B.TxnDate)
ORDER BY
D.Category ASC, YEAR(B.TxnDate) ASC, QUARTER(B.TxnDate) ASC
;
You can left join with the sum of distinct categories and budgets. This will give you all of your output rows desired but will also give you $0 budgets for categories that don't have entries in the budgets table. Good luck!

Select MAX value with restriction to rows

I have 3 tables:
matchdays:
matchday_id | season_id | userid | points | matchday
----------------------------------------------------
1 | 1 | 1 | 33 | 1
2 | 1 | 2 | 45 | 1
etc
players
userid | username
-----------------
1 | user1
2 | user2
etc.
seasons
seasons_id | title | userid
----------------------------
1 | 2011 | 3
2 | 2012 | 10
3 | 2013 | 5
My query:
SELECT s.title, p.username, SUM(points) FROM matchdays m
INNER JOIN players p ON p.userid = m.userid
INNER JOIN seasons s ON m.userid = s.userid
group by s.season_id
This results in (example!):
title | username | SUM(points)
------------------------------
2011 | user3 | 3744
2012 | user10 | 3457
2013 | user5 | 3888
What it should look like is a table with the winner (max points) of every season. Right now, the title and username is correct, but the sum of the points is way too high. I couldn't figure out what sum is calculated. Ideally, the sum is the addition of every matchday of a season for every user.
Your main issue is that you group by seasons only. Thus your SUM is running on all points over a season, regardless of the player.
The whole approach is wrong anyway. The "flaw" with userid in the season table is your biggest issue, and you seem to know it.
I will explain you how to calculate your rankings in the database one time for all, and to have them at your disposal at all times, which will save you a lot of headaches, and obviously save some CPU and loading times as well.
Start by creating a new table "Rankings":
CREATE table rankings (season_id INT, userid INT, points INT, rank INT)
If you have a lot of players, index all columns but points
Then, populate the table for each season:
This is a oneshot operation to run each time a season has ended.
So for the time being, you will have to run it several times for each season.
The key here is to compute the rank of each player for the season, which is a must-have that will be super-handy for later. Because MySQL doesnt have a window function for that, we have to use an old trick : incrementing a counter.
I decompose.
This will compute the points of a season, and provide the ranking for that season:
SELECT season_id, userid, SUM(points) as points
FROM matchdays
WHERE season_id = 1
GROUP BY season_id, userid
ORDER BY points DESC
Now we adapt this query to add a rank column :
SELECT
season_id, userid, points,
#curRank := #curRank + 1 AS rank
FROM
(
SELECT season_id, userid, SUM(points) as points
FROM matchdays
WHERE season_id = 1
GROUP BY season_id, userid
) T,
(
SELECT #curRank := 0
) R
ORDER BY T.points DESC
That's it.
Now we can INSERT the results of this computation into our ranking table, to store it once for good :
INSERT INTO rankings
SELECT
season_id, userid, points,
#curRank := #curRank + 1 AS rank
FROM
(
SELECT season_id, userid, SUM(points) as points
FROM matchdays
WHERE season_id = 1
GROUP BY season_id, userid
) T,
(
SELECT #curRank := 0
) R
ORDER BY T.points DESC
Change the season_id = 1 and repeat for each season.
Save this query somewhere, and in the future, run it once each time a season has ended.
Now you have a proper database-computed ranking and a nice ranking table that you can query whenever you want.
You want the winner for each season ? As simple as that:
SELECT S.title, P.username, R.points
FROM Ranking R
INNER JOIN seasons S ON R.season_id=S.season_id
INNER JOIN players P ON R.userid=P.userid
WHERE R.rank = 1
You will discover over the time that you can do a lot of different things very simply with your ranking table.
You're join is wrong, try something like:
SELECT s.title, p.username, SUM(m.points) as points FROM matchdays m
JOIN players p ON p.userid = m.userid
JOIN seasons s ON m.season_id = s.season_id
group by s.season_id, p.userid
ORDER by points DESC;
As pointed out, userid does'nt belong/is not needed in 'seasons' table.

Compare data using the year in a query

I have a data and they are recorded by each year, I am trying to compare two years( the past year and the current year) data within one mysql query
Below are my tables
Cost Items
| cid | items |
| 1 | A |
| 2 | B |
Cost
| cid | amount | year |
| 1 | 10 | 1 |
| 1 | 20 | 2 |
| 1 | 30 | 1 |
This is the result I am expecting when i want to compare the year 1 and year 2. Year 1 is the past year and year 2 is the current year
Results
items | pastCost | currentCost |
A | 10 | 20 |
A | 30 | 0 |
However the below query is what i used by gives a strange answer.
SELECT
IFNULL(ps.`amount`, '0') as pastCost
IFNULL(cs.`amount`, '0') as currentCost
FROM
`Cost Items` b
LEFT JOIN
`Cost` ps
ON
b.cID=ps.cID
AND
ps.Year = 1
LEFT JOIN
`Cost` cu
ON
b.cID=cu.cID
AND
cu.Year =2
This is the result i get from my query
items | pastCost | currentCost |
A | 10 | 20 |
A | 30 | 20 |
Please what am i doing wrong? Thanks for helping.
I'm missing something about your query; the SQL text shown can't produce that result.
There is no source for the items column in the SELECT list, and there is no table aliased as cs. (Looks like the expression in the SELECT list would need to be cu.amount
Aside from that, the results being returned look exactly like what we'd expect. Each row returned from year=2 is being matched with each row returned from year=1. If there were three rows for year=1 and two rows for year=2, we'd get six rows back... each row for year=1 "matched" with each row for year=2.
If (cid, year) tuple was UNIQUE in Cost, then this query would return a result similar to what you expect.
SELECT b.items
, IFNULL(ps.amount, '0') AS pastCost
, IFNULL(cu.amount, '0') AS currentCost
FROM `Cost Items` b
LEFT
JOIN `Cost` ps
ON ps.cid = b.cid
AND ps.Year = 1
LEFT
JOIN `Cost` cu
ON cu.cid = b.cid
AND cu.Year = 2
Since (cid, year) is not unique, you need some additional column to "match" a single row for year=1 with a single row for year=2.
Without some other column in the table, we could use an inline view to generate a value. I can illustrate how we can make MySQL return a resultset like the one you show, one way that could be done, but I don't think this is really the solution to whatever problem you are trying to solve:
SELECT b.items
, IFNULL(MAX(IF(a.year=1,a.amount,NULL)),0) AS pastCost
, IFNULL(MAX(IF(a.year=2,a.amount,NULL)),0) AS currentCost
FROM `Cost Items` b
LEFT
JOIN ( SELECT #rn := IF(c.cid=#p_cid AND c.year=#p_year,#rn+1,1) AS `rn`
, #p_cid := c.cid AS `cid`
, #p_year := c.year AS `year`
, c.amount
FROM (SELECT #p_cid := NULL, #p_year := NULL, #rn := 0) i
JOIN `Cost` c
ON c.year IN (1,2)
ORDER BY c.cid, c.year, c.amount
) a
ON a.cid = b.cid
GROUP
BY b.cid
, a.rn
A query something like that would return a resultset that looks like the one you are expecting. But again, I strongly suspect that this is not really the resultset you are really looking for.
EDIT
OP leaves comment with vaguely nebulous report of observed behavior: "the above solution doesnt work"
Well then, let's check it out... create a SQL Fiddle with some tables so we can test the query...
SQL Fiddle here http://sqlfiddle.com/#!9/e3d7e/1
create table `Cost Items` (cid int unsigned, items varchar(5));
insert into `Cost Items` (cid, items) values (1,'A'),(2,'B');
create table `Cost` (cid int unsigned, amount int, year int);
insert into `Cost` (cid, amount, year) VALUES (1,10,1),(1,20,2),(1,30,1);
And when we run the query, we get a syntax error. There's closing paren missing in the expressions in the SELECT list, easy enough to fix.
SELECT b.items
, IFNULL(MAX(IF(a.year=1,a.amount,NULL)),0) AS pastCost
, IFNULL(MAX(IF(a.year=2,a.amount,NULL)),0) AS currentCost
FROM `Cost Items` b
LEFT
JOIN ( SELECT #rn := IF(c.cid=#p_cid AND c.year=#p_year,#rn+1,1) AS `rn`
, #p_cid := c.cid AS `cid`
, #p_year := c.year AS `year`
, c.amount
FROM (SELECT #p_cid := NULL, #p_year := NULL, #rn := 0) i
JOIN `Cost` c
ON c.year IN (1,2)
ORDER BY c.cid, c.year, c.amount
) a
ON a.cid = b.cid
GROUP
BY b.cid
, a.rn
Returns:
items pastCost currentCost
------ -------- -----------
A 10 20
A 30 0
B 0 0