Rank in subquery - mysql

I'm looking to report the best seller by total sales price using the table below:
Sales table:
seller_id | quantity | price
| 1 | 2 | 2000 |
| 1 | 1 | 800 |
| 2 | 1 | 800 |
| 3 2 | 2800 |
The result need to be 1 & 3 (a table with 1 column "seller_id")since both sellers with id 1 and 3 sold products with the most total price of 2800
I tried to write a query using rank.. in a subquery, I will have something like:
SELECT
sum(price), rank() over (order by price desc )
FROM
sales
group by seller_id
Then, I need to select the sum(price) with rank=1, but I don't know how to write that..
Then, outside of the subquery, I want to select just the seller_id.
How do we do these?

Use RANKING function for retrieving seller_id based on highest price. RANK() function serialize the position with a GAP. If two values in same position then third value position after this to value will 3 not 2.
-- MySQL (v5.8)
SELECT t.seller_id, t.total_price
FROM (SELECT seller_id, SUM(price) total_price
, RANK() OVER (ORDER BY SUM(price) DESC) rank_price
FROM sales
GROUP BY seller_id) t
WHERE t.rank_price = 1
DENSE_RANK() function serialize the position without a GAP. If top 5 price needed in future then it'll better.
-- MySQL (v5.8)
SELECT t.seller_id, t.total_price
FROM (SELECT seller_id, SUM(price) total_price
, DENSE_RANK() OVER (ORDER BY SUM(price) DESC) rank_price
FROM sales
GROUP BY seller_id) t
WHERE t.rank_price = 1
Please check from url https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=90c756fa6d3ab25914721cfe40df5e9a

Related

Use Group By against two separate columns in SQL to compute a new column

I have a mysql table called transactions which looks as follows:
|---------|--------------|--------------|--------------------------|
|order_id |customer_name | brand_name | order_time_stamp |
|---------|--------------|--------------|--------------------------|
| 1 | Jack | Pepsi | 2019-02-23 20:02:21.550. |
|---------|--------------|--------------|--------------------------|
| 2 | Dorothy | Fanta | 2019-02-23 20:03:21.550. |
|---------|--------------|--------------|--------------------------|
| 3 | Dorothy | Fanta | 2019-02-23 20:04:21.550. |
|---------|--------------|--------------|--------------------------|
| 4 | Jack | Fanta | 2019-02-23 20:05:21.550. |
|---------|--------------|--------------|--------------------------|
As is evident, this is a table that captures every order at an online store with the order_id being the primary key. What I am trying to capture is the number of additional orders grouped by brand_name as follows:
enter code here
|------------|--------------------|
| brand_name | additional orders |
|------------|--------------------|
| Pepsi | 0 |
|------------|--------------------|
| Fanta | 1 |
|------------|--------------------|
However, additional orders are defined on a customer level and are defined as the sum of all orders after the first order by a customer.
My strategy to do this was was to use the rank() function as follows:
select rank() over( partition by customer_name order by order_time_stamp) as rank
from transactions
This creates an additional column which creates a rank per customer. However, I am not sure how to now group this on a brand_level and get the output as I have shown
You can use row_number() to rank the orders per customer, then filter on "additional" orders (that is, every order that whose rank is greater than 1), then aggregate by brand_name:
select brand_name, count(*) no_additional_orders
from (
select
t.*,
row_number() over(partition by customer_name order by order_time_stamp) rn
from transactions t
) t
where rn > 1
group by brand_name
If you want to also take in account brands that have no additional order, then you can move the filtering logic to the aggregate function:
select brand_name, sum(rn > 1) no_additional_orders
from (
select t.*, row_number() over(partition by customer_name order by order_time_stamp) rn
from transactions t
) t
group by brand_name
Your data is rather confusing. I think you want everything after the earliest timestamp, not the earliest order. This is a subtle difference, but important:
select brand_name,
sum(order_time_stamp > min_ots)
from (select t.*, min(order_time_stamp) over (partition by customer_name) as min_ots
from t
) t
group by brand_name;
You can do something similar with rank() as well:
select brand_name,
sum(seqnum > 1)
from (select t.*,
rank() over (partition by customer_name order by order_time_stamp) as seqnum
from t
) t
group by brand_name;
You want to sum all the orders of each customer_name per brand_name except 1, because you don't want in the sum each customer's 1st order.
You can do it with by subtracting from the total number of orders the number of distinct customers that ordered the product which is equal to the number of 1st orders of each customer:
select brand_name,
count(*) - count(distinct customer_name) additional_orders
from transactions
group by brand_name
See the demo.
Results:
> brand_name | additional_orders
> :--------- | ----------------:
> Pepsi | 0
> Fanta | 1

How to get sum of quantity of products from a specific date?

TABLE 1 Sales
id | date
=================
1 | 10 july
2 | 10 july
3 | 20 july
TABLE 2 Products
sale_id | product_name | quantity
====================================
1 | cold drink | 2
1 | Samosa | 3
1 | Burger | 1
2 | Burger | 4
3 | Shwarma | 1
I want to get product name and their quantity sold on a specific date
for example
on july 10 there two sales with id 1,2 and product sold on that date are
cold drink 2
samosa 3
Burger 5 (1 sold in sale 1 and 4 sold in sale 2)
How do i do that in mysql?
i have tried using
Select product_name, SUM(quantity) From Products GROUP BY product_name
but this query give me all the sum i want sum from a specific date , how do i relate these two tables.
If you want data for all days then use this
SELECT products.product_name, sales.date, sum(products.quantity)
FROM products LEFT JOIN sales
ON products.sale_id = sales.id group by products.product_name, sales.date;
If you want the data on date specific the you can use this:
SELECT products.product_name, sales.date, sum(products.quantity)
FROM products LEFT JOIN sales
ON products.sale_id = sales.id WHERE sales.date = '10 july' group by products.product_name, sales.date;
Demo
There is at least two solutions for this.
You can use subselect:
SELECT
product_name,
SUM(quantity) sum,
date
FROM Products
JOIN (
SELECT * FROM Sales WHERE date = '2019-07-10'
) s ON s.id = Products.sale_id
GROUP BY product_name, date
ORDER BY sum;
Result on this SQLFiddle.
You can use HAVING for date matching:
SELECT
product_name,
SUM(quantity) sum,
date
FROM Products
JOIN Sales ON id = sale_id
GROUP BY product_name, date
HAVING date = '2019-07-10'
ORDER BY sum;
Result on this SQLFiddle.

MySql value and sum(value) in same row without group

I want to get the current row quantity with COUNT(*) and the total row quantity over all rows as column in every row (needed for a report trying to avoid scripting it outside the sql).
I can't use SUM(qty) because i don't want to group my result by reasons and when i use a parameter with := i only get the total qty in the last row.
My current Query looks something like
SET #sumTotal:=0;
SELECT reason, qty, (#sumTotal := #sumTotal + qty) AS total_qty
FROM
(
SELECT reason, COUNT(*) AS qty
FROM someTable
--Imagine a huge amount of joins here
GROUP BY someTableId
)base
The table someTable looks like
----------------------
projectid | reason
----------------------
1 | reason11
1 | reason12
2 | reason21
2 | reason22
2 | reason23
3 | reason31
.
.
.
3 | reason35
----------------------
The result should look something like
----------------------------
reason | qty | totalqty
----------------------------
reason1 | 2 | 10
reason2 | 3 | 10
reason3 | 5 | 10
----------------------------
Am i maybe thinking in the wrong direction and there is a easy way to fix this?
Use auxiliary SELECT query to count the number of someTable rows.
SELECT reason, qty, total_qty
FROM
(
SELECT reason, COUNT(*) AS qty
FROM someTable
GROUP BY someTableId
),
(
SELECT count(*) AS total_qty
FROM someTable
)
This produces a Cartesian product between the derived tables of subqueries. Second subquery will consist of single row with total quantity. Thus, the total quantity will be added to the first subquery.

MAX() and SUM() in one query

I have this table
dept | amount | price
1 | 2 | 20
3 | 2 | 50
4 | 3 | 10
2 | 5 | 20
1 | 1 | 15
4 | 1 | 30
4 | 6 | 5
2 | 7 | 7
1 | 1 | 24
2 | 5 | 12
dept is de department number
amount is how many of a product is sold.
price is how much the price of the product is
How can I found the dept, that has got the most money from selling their products.
I have this:
SELECT dept, SUM( amount * price ) AS total
FROM table
GROUP BY dept
I need it to return the dept with the highest total.
I can't use MAX( SUM( amount * price ) ), so how do I do this?
Oh yeah. It's a school assignment and I may not use LIMIT or ORDER BY
Without using LIMIT you can try using HAVING:
SELECT dept,SUM(amount * price) AS total
FROM tab1
GROUP BY dept
HAVING SUM(amount * price) = (
SELECT MAX(total)
FROM (
SELECT SUM(amount * price) AS total
FROM tab1
GROUP BY dept
) a
)
sqlfiddle demo
If you do not want to use ORDER and LIMIT. This is a solution ( Tested)
SELECT dept, SUM( amount * price ) AS total
FROM table
GROUP BY dept
HAVING SUM( amount * price ) = ( SELECT MAX(A.total)
FROM
(
SELECT dept, SUM( amount * price ) AS total
FROM table
GROUP BY dept
) A
)
This will give you the department with the highest total:
select top 1 dept, sum(amount * price)
from table
group by dept
order by sum(amount * price) desc
You can sort by the total descending and take the first entry
SELECT dept, SUM( amount * price ) AS total
FROM table
GROUP BY dept
order by total desc
limit 1

Selecting the most recent, lowest price from multiple vendors for an inventory item

I’m fairly proficient at SQL, however this question has had myself stumped for quite a while now. In the most basic sense, there are simply two tables:
Items
+----+--------+
| id | title |
+----+--------+
| 1 | socks |
| 2 | banana |
| 3 | watch |
| 4 | box |
| 5 | shoe |
+----+--------+
...and the prices table:
Prices
+---------+-----------+-------+------------+
| item_id | vendor_id | price | created_at |
+---------+-----------+-------+------------+
| 1 | 1 | 5.99 | Today |
| 1 | 2 | 4.99 | Today |
| 2 | 1 | 6.99 | Today |
| 2 | 2 | 6.99 | Today |
| 1 | 1 | 3.99 | Yesterday |
| 1 | 1 | 4.99 | Yesterday |
| 2 | 1 | 6.99 | Yesterday |
| 2 | 2 | 6.99 | Yesterday |
+---------+-----------+-------+------------+
(Please note: created_at is actually a timestamp, the words “Today” and “Yesterday” were provided merely to quickly convey the concept).
My goal is to get a simple result back containing the inventory item associated with the most recent, lowest price, including the reference to the vendor_id who is providing said price.
However, I find the stumbling block appears to be the sheer number of requirements for the statement (or statements) to handle:
Each item has multiple vendors, so we need to determine which price between all the vendors for each item is the lowest
New prices for the items get appended regularly, therefore we only want to consider the most recent price for each item for each vendor
We want to roll all that up into a single result, one item per row which includes the item, price and vendor
It seems simple, but I’ve found this problem to be uncanningly difficult.
As a note, I’m using Postgres, so all the fanciness it provides is available for use (ie: window functions).
Much simpler with DISTINCT ON in Postgres:
Current price per item for each vendor
SELECT DISTINCT ON (p.item_id, p.vendor_id)
i.title, p.price, p.vendor_id
FROM prices p
JOIN items i ON i.id = p.item_id
ORDER BY p.item_id, p.vendor_id, p.created_at DESC;
Optimal vendor for each item
SELECT DISTINCT ON (item_id)
i.title, p.price, p.vendor_id -- add more columns as you need
FROM (
SELECT DISTINCT ON (item_id, vendor_id)
item_id, price, vendor_id -- add more columns as you need
FROM prices p
ORDER BY item_id, vendor_id, created_at DESC
) p
JOIN items i ON i.id = p.item_id
ORDER BY item_id, price;
->SQLfiddle demo
Detailed explanation:
Select first row in each GROUP BY group?
Try this
CREATE TABLE #Prices ( Iid INT, Vid INT, Price Money, Created DateTime)
INSERT INTO #Prices
SELECT 1, 1, 5.99 ,GETDATE() UNION
SELECT 1, 2, 4.99 ,GETDATE() UNION
SELECT 2, 1, 6.99 ,GETDATE() UNION
SELECT 2, 2, 6.99 ,GETDATE() UNION
SELECT 1, 1, 3.99 ,GETDATE()-1 UNION
SELECT 1, 2, 4.99 ,GETDATE()-1 UNION
SELECT 2, 1, 6.99 ,GETDATE()-1 UNION
SELECT 2, 2, 6.99 ,GETDATE()-1
WITH CTE AS
(
SELECT
MyPriority = ROW_NUMBER() OVER ( partition by Iid, Vid ORDER BY Created DESC, Price ASC)
, Iid
, Vid
, price
, Created
FROM #Prices
)
SELECT * FROM CTE WHERE MyPriority = 1
It's also possible to do this with windowed functions, it will work on SQL Server version > 2005:
with cte1 as (
select
*,
row_number() over(partition by vendor_id, item_id order by created_at desc) as row_num
from prices
), cte2 as (
select
*,
row_number() over(partition by item_id order by price asc) as row_num2
from cte1
where row_num = 1
)
select i.title, c.price, c.vendor_id
from cte2 as c
inner join items as i on i.id = c.item_id
where c.row_num2 = 1;
sql fiddle demo(Thanks Erwin)