Select highest value based on aggregate function - SQL - mysql

I have a GiftSales table, it contains the id of the item (giftId), and the category of that item (categoryId)
I need to get the best selling item for each category.
Right now my query looks like this
SELECT giftId, categoryId, COUNT(giftId) as Total
FROM GiftSales
GROUP BY giftId, categoryId
And its giving me
==================================
|| giftId || categoryId || Total||
==================================
|| 1 || 1 || 8 ||
==================================
|| 2 || 1 || 5 ||
==================================
|| 23 || 2 || 12 ||
==================================
I need to only show the highest value per each category, so basically, the table shouldn't contain the second item.

I'd recommend using a window function, and dense_rank can be helpful when looking at top selling products by category as you may want to include any ties.
Schema (MySQL v8.0)
CREATE TABLE IDs (
`gift_id` INTEGER,
`category_id` INTEGER
);
INSERT INTO IDs
(`gift_id`, `category_id`)
VALUES
('1', '1'),
('1', '1'),
('1', '1'),
('1', '1'),
('1', '1'),
('1', '1'),
('1', '1'),
('1', '1'),
('2', '1'),
('2', '1'),
('2', '1'),
('2', '1'),
('2', '1');
Query #1
select a.category_id,a.gift_id,a.total from (
select
category_id,
gift_id,
count(gift_id) as total,
dense_rank() over (partition by category_id order by count(gift_id) desc) as ranking
from IDs group by 1,2) as a where ranking = 1;
category_id
gift_id
total
1
1
8
View on DB Fiddle

Use a window function such as MAX OVER per category:
select giftid, categoryid, total
from
(
select
giftid,
categoryid,
count(*) as total,
max(count(*)) over (partition by categoryid) as category_max
from giftsales
group by giftid, categoryid
) aggregated
where total = category_max;

SELECT DISTINCT categoryId, MAX(Total) as total FROM(
SELECT giftId, categoryId, COUNT(giftId) as Total FROM GiftSales GROUP BY giftId, categoryId
) AS T GROUP BY giftId, categoryId;
I got it working by using the distinct with the categoryId and since you need the total by category I removed the giftId and everything worked fine :) I used a playground to test this and the playground can be found here -> https://www.db-fiddle.com/f/qsGLKUZyos2ZKTftykkazd/0

Related

Query for any person has any account of type x

Imagine I have two tables, Person and Account, a person can have accounts (type 1 and/or 2).
I'd like to get a list of people who have at least one type 1 account, and also get a list of people who don't have a type 1 account. I'm using Query #1 and #2 for this respectively but I think I'm doing something is wrong because the results do not match.
Schema (MySQL v5.7)
CREATE TABLE Person (
`PersonId` INTEGER,
`Name` VARCHAR(5)
);
INSERT INTO Person
(`PersonId`, `Name`)
VALUES
('1', 'Leo'),
('2', 'Natan'),
('3', 'Vera'),
('4', 'Julio'),
('5', 'Mary');
CREATE TABLE Accounts (
`AccountId` INTEGER,
`PersonId` INTEGER,
`Type` INTEGER
);
INSERT INTO Accounts
(`AccountId`, `PersonId`, `Type`)
VALUES
('1', '1', '0'),
('2', '1', '1'),
('3', '2', '0'),
('4', '2', '0'),
('5', '3', '1'),
('6', '4', '0'),
('7', '1', '0'),
('8', '2', '0');
Query #1
SELECT * FROM Person AS PD
LEFT JOIN Accounts AS AC ON AC.PersonId = PD.PersonId
WHERE AC.Type = 1;
PersonId
Name
AccountId
PersonId
Type
1
Leo
2
1
1
3
Vera
5
3
1
Query #2
SELECT * FROM Person AS PD
LEFT JOIN Accounts AS AC ON AC.PersonId = PD.PersonId
WHERE AC.Type = 0;
PersonId
Name
AccountId
PersonId
Type
1
Leo
1
1
0
1
Leo
7
1
0
2
Natan
3
2
0
2
Natan
4
2
0
2
Natan
8
2
0
4
Julio
6
4
0
View on DB Fiddle
EXISTS and NOT EXISTS are the more suitable solutions for this requirement:
-- Account type = 1
SELECT p.* FROM Person AS p
WHERE EXISTS (
SELECT *
FROM Accounts AS a
WHERE a.PersonId = p.PersonId AND a.Type = 1
);
-- No type 1 account
SELECT p.* FROM Person AS p
WHERE NOT EXISTS (
SELECT *
FROM Accounts AS a
WHERE a.PersonId = p.PersonId AND a.Type = 1
);
See the demo.

MySQL-Count consective number

Write a SQL query to find number position as well number and consective number count
CREATE TABLE Logs (
`Id` INTEGER,
`Num` INTEGER
);
INSERT INTO Logs
(`Id`, `Num`)
VALUES
('1', '1'),
('2', '1'),
('3', '1'),
('4', '2'),
('5', '1'),
('6', '2'),
('7', '2');
Prefere Return
StartId Num Count
1 1 3
4 2 1
5 1 1
6 2 2
and also can i get any suggestion which function can be use with case function in MySQL Function
Looking at your data and expected results, I believe your expectations are inconsistent, eg you can either have 1 and 6 or 3 and 7.
What you need to do is group the data by successive num values and aggregate the results.
with gp as (
select *,
Row_Number() over(order by id)
- Row_Number() over(partition by num order by id) g
from logs
)
select Min(id) Id,
num, Count(*) Count
from gp
group by g, num
order by id

Tracking Increasing or decreasing prices in MySQL

I tried to write a query that returns the id, product, price, and change columns. The change column should follow this logic. If the item price has increased it should write positive and if it has decreased negative depending on the product and excluding the first initial product price. The last product price should be taken into consideration.
This is how the result should look like.
id product price change
1 apple 1
2 apple 1.5 positive
3 apple 3 positive
4 melon 4
5 melon 3 negative
6 apple 2 negative
I have tried to use Case When statement but failed.
select
p.id,
p.product,
p.price,
CASE
WHEN p.product = p.product AND p.price > p.price THEN 'Positive'
WHEN p.product = p.product AND p.price > p.price THEN 'Negative'
END AS 'Change'
from products p
Create and insert statements
CREATE TABLE `products` (
`id` INT(11) NOT NULL,
`product` VARCHAR(50) NOT NULL COLLATE 'utf8_unicode_ci',
`price` DECIMAL(10,2) NOT NULL DEFAULT '0.00'
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;
INSERT INTO products (id, product, price)
VALUES (1, 'apple', 1);
INSERT INTO products (id, product, price)
VALUES (2, 'apple', 1.5);
INSERT INTO products (id, product, price)
VALUES (3, 'apple', 3);
INSERT INTO products (id, product, price)
VALUES (4, 'melon', 4);
INSERT INTO products (id, product, price)
VALUES (5, 'melon', 3);
INSERT INTO products (id, product, price)
VALUES (6, 'apple', 2);
Use lag():
select p.*,
(case when lag(price) over (partition by product order by id) < price
then 'negative'
when lag(price) over (partition by product order by id) > price
then 'positive'
end)
from products p;
In archaic versions of MySQL, you can use a correlated subquery:
select p.*
(case when prev_price < price
then 'negative'
when prev_price > price
then 'positive'
end)
from (select p.*,
(select p2.price
from product p2
where p2.product = p.product and p2.id < p.id
order by p2.id desc
limit 1
) as prev_price
from product p
) p;

How to select in mysql the ratio between the count of records and the count of records with same value of a field?

Let's say I have a table in a mysql database
(id,idWord,isCorrect)
------------------
('1','1', '0'),
('2','1', '1'),
('3','1', '1'),
('4','1', '1'),
('5','1', '0'),
('6','1', '1'),
('7','2', '0'),
('8','2', '0'),
('9','2', '1'),
('10','2', '1')
How may I select the ratio between the count of records having isCorrect=1 and the total count of records with same idWord? in this case would result in
('1','0.6') (-> 4/6)
('2','0.5') (-> 2/4)
I tried
SELECT
(SELECT COUNT(`isCorrect`) FROM `wordstyped` GROUP BY `idWord`,`isCorrect`)
/
(SELECT COUNT(`isCorrect`) FROM `wordstyped` GROUP BY `idWord`,`isCorrect` WHERE `isCorrect`=1)
but it doesn't work.
http://www.sqlfiddle.com/#!9/f36c58/1
When you use a SELECT as a value, it has to return just one row, you can't return all the grouped rows. You can do it with a single query that sums multiple things.
SELECT idWord, SUM(isCorrect = 1) / COUNT(*) AS ratio
FROM `wordstyped`
GROUP BY idWord
A comparison expression like isCorrect = 1 evaluates to 1 when it's true, 0 when false, so summing them counts the number of rows where the condition is true.
You can also use the AVG() function, since an average is just a total divided by the count.
SELECT idWord, AVG(isCorrect = 1) AS ratio
FROM `wordstyped`
GROUP BY idWord
Try this one:
SELECT
idWord,
SUM(CASE WHEN isCorrect = 1 THEN 1 ELSE 0 END) AS corrects,
COUNT(*) AS total,
SUM(CASE WHEN isCorrect = 1 THEN 1 ELSE 0 END) / COUNT(*) AS ratio
FROM
wordstyped
GROUP BY
idWord

MySQL insert values if condition is met

I am trying to insert into a table only if the qty has changed down in another table see example
INSERT INTO sales_items (sale_id, item_id, quantity_purchased, item_cost_price, item_unit_price)
VALUES ('1', '1546', '3', '10', '10')
WHEN (SELECT quantity FROM location_items WHERE location_id =4 AND item_id =1546) < 10;
You can do the following:
INSERT INTO sales_items
(sale_id, item_id, quantity_purchased, item_cost_price, item_unit_price)
VALUES
(SELECT '1', '1546', '3', '10', '10'
FROM location_items
WHERE location_id = 4
AND item_id = 1546
AND quantity < 10
);
Or, if you want to do it all in one query, including updates:
REPLACE INTO sales_items
(item_id, quantity_purchased, item_cost_price, item_unit_price)
VALUES
(SELECT item_id, ??, ??, ??
FROM location_items
WHERE quantity < 10
AND quantity > 0
);
...where you have to fill the ?? with references to columns holding the values for item_cost_price and item_unit_price, and you have a unique constraint on item_id
Not possible like that. An INSERT query cannot have a where clause, period.
You can, hover, do an insert select from:
INSERT INTO ...
SELECT ... FROM ... WHERE (...) < 10
If the SELECT finds no rows, then nothing gets inserted.