count product for different discount range - mysql

I have Products table with discount like
productid discount
1 12
2 22
3 32
4 45
5 55
6 57
7 63
8 72
9 85
i want sql query for product count where discount 10%-100% or discount 20%-100% or discount 30%-100% or discount 40%-100% so on .. discount range
My sql query is
SELECT Count(product_id) AS product_count,
Substring_index(value_range, '-', 1) AS start,
Substring_index(value_range, '-', -1) AS END
FROM (SELECT product_id,
discount,
CASE
WHEN discount BETWEEN 80 AND 90 THEN '80-90'
WHEN discount BETWEEN 70 AND 90 THEN '70-90'
WHEN discount BETWEEN 60 AND 90 THEN '60-90'
WHEN discount BETWEEN 50 AND 90 THEN '50-90'
WHEN discount BETWEEN 40 AND 90 THEN '40-90'
WHEN discount BETWEEN 30 AND 90 THEN '30-90'
WHEN discount BETWEEN 20 AND 90 THEN '20-90'
WHEN discount BETWEEN 10 AND 90 THEN '10-90'
END AS value_range
FROM products) AS T2
GROUP BY value_range
ORDER BY Cast(start AS UNSIGNED) ASC
but it not giving desire result
expected result is
discount_range countproduct
10%-100% 9
20%-100% 8
30%-100% 7
40%-100% 6.. so on
Here is My output.

First you need create a table for your ranges:
CREATE TABLE Ranges
(`start` int, `end` int)
;
INSERT INTO Ranges
(`start`, `end`)
VALUES
(10, 100),
(20, 100),
(30, 100),
(40, 100),
(50, 100),
(60, 100),
(70, 100),
(80, 100),
(90, 100)
;
Then just found on what ranges each product discount is part of:
SELECT `start`, `end`, `productid`, `discount`
FROM ranges
LEFT JOIN products
ON products.discount between `start` and `end`
Then just count it:
SQL DEMO
SELECT `start`, `end`, COUNT(`productid`)
FROM (
SELECT `start`, `end`, `productid`, `discount`
FROM ranges
LEFT JOIN products
ON products.discount between `start` and `end`
) t
GROUP BY `start`, `end`
ORDER BY `start`
OUTPUT
| start | end | COUNT(`productid`) |
|-------|-----|--------------------|
| 10 | 100 | 9 |
| 20 | 100 | 8 |
| 30 | 100 | 7 |
| 40 | 100 | 6 |
| 50 | 100 | 5 |
| 60 | 100 | 3 |
| 70 | 100 | 2 |
| 80 | 100 | 1 |
| 90 | 100 | 0 |

You need a query that counts the products inside the query that groups by range:
select
concat(t.floorvalue, '%-90%') discount_range,
t.countproduct
from (
select
floor(p.discount / 10) * 10 floorvalue,
(select count(*) from products where discount >= floorvalue) countproduct
from products p
group by floorvalue
) t
order by discount_range
See the demo.
Results:
| discount_range | countproduct |
| -------------- | ------------ |
| 10%-90% | 9 |
| 20%-90% | 8 |
| 30%-90% | 7 |
| 40%-90% | 6 |
| 50%-90% | 5 |
| 60%-90% | 3 |
| 70%-90% | 2 |
| 80%-90% | 1 |

try this one
select
x.bin,
x.bin_count,
sum(y.bin_count) as cumulative_count
from
( select
floor(discount / 10) as lower_bound,
concat('[', floor(discount / 10) * 10, ' - ', floor(discount / 10) * 10 + 10, ')') as bin,
count(*) as bin_count
from
t1
group by
1, 2) as x
join ( select
floor(discount / 10) as lower_bound,
concat('[', floor(discount / 10) * 10, ' - ', floor(discount / 10) * 10 + 10, ')') as bin,
count(*) as bin_count
from
t1
group by
1, 2) as y on x.lower_bound >= y.lower_bound
group by
1, 2
one more
select
x.bin,
x.bin_count,
concat('[', min(y.lower_bound), ' - ', max(y.upper_bound), ')') as cumulative_bin,
sum(y.bin_count) as cumulative_count
from
( select
floor(discount / 10) * 10 as lower_bound,
floor(discount / 10) * 10 + 10 as upper_bound,
concat('[', floor(discount / 10) * 10, ' - ', floor(discount / 10) * 10 + 10, ')') as bin,
count(*) as bin_count
from
t1
group by
1, 2, 3) as x
join ( select
floor(discount / 10) * 10 as lower_bound,
floor(discount / 10) * 10 + 10 as upper_bound,
concat('[', floor(discount / 10) * 10, ' - ', floor(discount / 10) * 10 + 10, ')') as bin,
count(*) as bin_count
from
t1
group by
1, 2, 3) as y on x.lower_bound >= y.lower_bound-- order by 1, 5
where
x.lower_bound >= 10
and y.lower_bound >= 10
group by
x.bin

If you don't want to create a second table, try building off of this:
select "10%-100%" as discount_range, (select count(*) from products where discount between 10 and 100) as product_count
union
select "20%-100%" as discount_range, (select count(*) from products where discount between 20 and 100) as product_count
union
select "30%-100%" as discount_range, (select count(*) from products where discount between 30 and 100) as product_count
union
select "40%-100%" as discount_range, (select count(*) from products where discount between 40 and 100) as product_count

Related

put order by in right place mysql

i have query like this
SELECT COALESCE(p.name, 'total') AS `product name`,
SUM(omd.quantity) AS `Qty(kg)`,
SUM(omd.quantity) / any_value(total_sum) * 100 AS `Qty(%)`,
COUNT(om.id) AS `COunt Order`,
COUNT(om.id) / any_value(total_count) * 100 AS `Count Order(%)`
FROM order_match om
INNER JOIN order_match_detail omd
ON om.id = omd.order_match_id
INNER JOIN product p
on omd.product_id = p.id
INNER JOIN (select SUM(omd1.quantity) total_sum,
count(om1.id) total_count
FROM order_match om1
INNER JOIN
order_match_detail omd1
ON om1.id = omd1.order_match_id
where om1.order_status_id in
(4, 5, 6, 8)) totals
where om.order_status_id in (4, 5, 6, 8)
group by p.name with rollup;
and after running that query the result was this (this is just dummy)
+--------------+---------+--------+-------------+-----------------+
| Product Name | Qty(kg) | Qty(%) | COunt Order | Count Order (%) |
+--------------+---------+--------+-------------+-----------------+
| Product A | 20 | 20 | 10 | 10 |
| Product B | 30 | 30 | 10 | 10 |
| Product C | 45 | 45 | 30 | 30 |
| Product D | 5 | 5 | 50 | 50 |
| TOtal | 100 | 100 | 100 | 100 |
+--------------+---------+--------+-------------+-----------------+
i want to put order by and order based on the Qty(kg)
the expected result was this
+--------------+---------+--------+-------------+-----------------+
| Product Name | Qty(kg) | Qty(%) | COunt Order | Count Order (%) |
+--------------+---------+--------+-------------+-----------------+
| Product C | 45 | 45 | 30 | 30 |
| Product B | 30 | 30 | 10 | 10 |
| Product A | 20 | 20 | 10 | 10 |
| Product D | 5 | 5 | 50 | 50 |
| TOtal | 100 | 100 | 100 | 100 |
+--------------+---------+--------+-------------+-----------------+
so this is my query after i put order by
SELECT COALESCE(p.name, 'total') AS `product name`,
SUM(omd.quantity) AS `Qty(kg)`,
SUM(omd.quantity) / any_value(total_sum) * 100 AS `Qty(%)`,
COUNT(om.id) AS `COunt Order`,
COUNT(om.id) / any_value(total_count) * 100 AS `Count Order(%)`
FROM order_match om
INNER JOIN order_match_detail omd
ON om.id = omd.order_match_id
INNER JOIN product p
on omd.product_id = p.id
INNER JOIN (select SUM(omd1.quantity) total_sum,
count(om1.id) total_count
FROM order_match om1
INNER JOIN
order_match_detail omd1
ON om1.id = omd1.order_match_id
where om1.order_status_id in
(4, 5, 6, 8)) totals
where om.order_status_id in (4, 5, 6, 8)
group by p.name with rollup
order by omd.quantity DESC;
but after i run it, the notification just like this
Error COde :1221. Incorrect usage of CUBE/R0LLUP and ORDER BY
so which part i should edit or add so it can be ordered by what i want
Do not add ORDER BY after GROUP BY WITH ROLLUP. Convert your query to subquery (or CTE if its version is 8+) and sort its output in outer query storing rollup result last:
SELECT *
FROM ( SELECT COALESCE(p.name, 'Total') AS `product name`,
SUM(omd.quantity) AS `Qty(kg)`,
SUM(omd.quantity) / ANY_VALUE(total_sum) * 100 AS `Qty(%)`,
COUNT(om.id) AS `Count Order`,
COUNT(om.id) / ANY_VALUE(total_count) * 100 AS `Count Order(%)`
FROM order_match AS om
INNER JOIN order_match_detail AS omd ON om.id = omd.order_match_id
INNER JOIN product AS p ON omd.product_id = p.id
CROSS JOIN ( SELECT SUM(omd1.quantity) AS total_sum,
COUNT(om1.id) AS total_count
FROM order_match AS om1
INNER JOIN order_match_detail AS omd1 ON om1.id = omd1.order_match_id
WHERE om1.order_status_id IN (4, 5, 6, 8)
) AS totals
WHERE om.order_status_id IN (4, 5, 6, 8)
GROUP BY p.name WITH ROLLUP
) AS subquery
ORDER BY `product name`='Total', `Qty(kg)` DESC;

group by consecutive period

Hi,I have a column as below
+--------+--------+
| day | amount|
+--------+---------
| 2 | 2 |
| 1 | 3 |
| 1 | 4 |
| 2 | 2 |
| 3 | 3 |
| 4 | 3 |
| 5 | 6 |
| 6 | 6 |
+--------+--------+
now I want something like this sum day 1- day3 as row one , sum day2-day4 as row 2, and so on.
+--------+--------+
| day | amount|
+--------+---------
| 1-3 | 14 |
| 2-4 | 10 |
| 3-5 | 12 |
| 4-6 | 15 |
+--------+--------+
Could you offer any one help ,thanks!
I would just use a correlated subquery:
select day, day + 2 as end_day,
(select sum(amount)
from t t2
where t2.day in (t.day, t.day + 1, t.day + 2)
) as amount
from (select distinct day from t) t;
This returns rows for all days, not limited to the last 4. If you really want that limit, then you can use:
select day, day + 2 as end_day,
(select sum(amount)
from t t2
where t2.day in (t.day, t.day + 1, t.day + 2)
) as amount
from (select distinct day
from t
order by day
offset 1 limit 99999999
) t
order by day;
Way 1:
Simply use UNION ALL:
SELECT '1 - 3' [Day], SUM(Amount)Amount FROM Your_Table WHERE Day BETWEEN 1 AND 3
UNION ALL
SELECT '2 - 4', SUM(Amount) FROM Your_Table WHERE Day BETWEEN 2 AND 4
UNION ALL
SELECT '3 - 5', SUM(Amount) FROM Your_Table WHERE Day BETWEEN 3 AND 5
UNION ALL
SELECT '4 - 6', SUM(Amount) FROM Your_Table WHERE Day BETWEEN 4 AND 6
Way 2:
You have to create a table with date range and JOIN the Table.
CREATE TABLE Tab1 (Day INT, Amount INT)
INSERT INTO Tab1 VALUES( 2 ,2 )
,(1, 3)
,(1, 4)
,(2, 2)
,(3, 3)
,(4, 3)
,(5, 6)
,(6, 6)
CREATE TABLE Tab2 (DateRange VARCHAR(10), StartDate INT, EndDate INT)
INSERT INTO Tab2 VALUES ('1 - 3',1,3)
,('2 - 4',2,4)
,('3 - 5',3,5)
,('4 - 6',4,6)
SELECT T2.DateRange,SUM(T1.Amount) Amount
FROM Tab1 T1
JOIN Tab2 T2 ON T1.Day BETWEEN T2.StartDate AND T2.EndDate
GROUP BY T2.DateRange
OutPut:
Day Amount
1 - 3 14
2 - 4 10
3 - 5 12
4 - 6 15
You can use integer division in order to calculate 'days buckets' and group by each bucket:
SELECT (day - 1) DIV 3 AS bucket, SUM(amount) AS total
FROM mytable
GROUP BY (day - 1) DIV 3;
Output:
bucket total
-------------
0 14
1 15
Demo here
To get the bucket string you can use:
SELECT concat(3 * ((day - 1) DIV 3 + 1) - 2, ' - ',
3 * ((day - 1) DIV 3 + 1)) AS bucket,
SUM(amount) AS total
FROM mytable
GROUP BY (day - 1) DIV 3
order by day;
Output:
bucket total
--------------
1 - 3 14
4 - 6 15
Note: The query works only for consecutive non-overlapping intervals.

Most recent date with maximum score

I have the table definition below.
CREATE TABLE IF NOT EXISTS ranking (
user_id int(11) unsigned NOT NULL,
create_date date NOT NULL,
score double(8,2),
PRIMARY KEY (user_id, create_date)
)
insert into ranking (user_id, create_date, score) values
(1, '2017-03-01', 100),
(1, '2017-03-02', 90),
(1, '2017-03-03', 80),
(1, '2017-03-04', 100),
(1, '2017-03-05', 90),
(2, '2017-03-01', 90),
(2, '2017-03-02', 80),
(2, '2017-03-03', 100),
(2, '2017-03-5', 100),
(3, '2017-03-01', 80),
(3, '2017-03-02', 100),
(3, '2017-03-03', 90),
(3, '2017-03-6', 100);
select * from ranking;
user_id | create_date | score
1 | 2017-03-01 | 100
1 | 2017-03-02 | 90
1 | 2017-03-03 | 80
1 | 2017-03-04 | 100
1 | 2017-03-05 | 90
2 | 2017-03-01 | 90
2 | 2017-03-02 | 80
2 | 2017-03-03 | 100
2 | 2017-03-05 | 100
3 | 2017-03-01 | 80
3 | 2017-03-02 | 100
3 | 2017-03-03 | 90
3 | 2017-03-06 | 100
What I want is for each user_id, get the most recent create_date on which the score is maximum. For example, in the example above, for user_id = 1, when create_date = 2017-03-01 and create_date = 2017-03-04, the maximum score is 100, but I just want the most recent date with the maximum score, i.e., create_date = 2017-03-04 and score = 100. The query result is as follows:
user_id | create_date | score
1 | 2017-03-04 | 100
2 | 2017-03-05 | 100
3 | 2017-03-06 | 100
Below is my solution, which returns the expected result but I believe there exist better solutions.
SELECT a.* from
(
SELECT s1.user_id , s1.create_date, s1.score FROM ranking AS s1
INNER JOIN
(SELECT user_id , FORMAT(max(score), 0) as best_score FROM ranking GROUP BY user_id ) AS s2
ON s1.user_id = s2.user_id AND s1.score = s2.best_score
) a
NATURAL LEFT JOIN
(
SELECT s1.user_id , s1.create_date, s1.score FROM ranking AS s1
INNER JOIN
(
SELECT user_id , create_date, score FROM ranking
) s2
WHERE s1.user_id = s2.user_id AND s1.score = s2.score AND s1.create_date < s2.create_date
) b
WHERE b.user_id IS NULL;
Can someone provide better solutions? Thanks.
SELECT t1.user_id,
MAX(t1.create_date) AS max_date,
t2.max_score
FROM ranking t1
INNER JOIN
(
SELECT user_id, MAX(score) AS max_score
FROM ranking
GROUP BY user_id
) t2
ON t1.user_id = t2.user_id AND
t1.score = t2.max_score
GROUP BY t1.user_id
Output:
Demo here:
Rextester
Try this:
select user_id, max(create_date),max(score) from ranking GROUP BY user_id
result:
1 2017-03-04 100.00
2 2017-03-05 100.00
3 2017-03-06 100.00
or
select user_id, max(create_date),cast(max(score) as UNSIGNED) as maxscore from ranking GROUP BY user_id
result:
1 2017-03-04 100
2 2017-03-05 100
3 2017-03-06 100
Try this query -
SELECT r1.* FROM ranking r1
JOIN (SELECT user_id, MAX(score) max_score FROM ranking GROUP BY user_id) r2
ON r1.user_id = r2.user_id AND r1.score = r2.max_score
JOIN (SELECT user_id, score, MAX(create_date) max_create_date FROM ranking GROUP BY user_id, score) r3
ON r1.user_id = r3.user_id AND r1.score = r3.score AND r1.create_date = r3.max_create_date;
1 04-Mar-17 100
2 05-Mar-17 100
3 06-Mar-17 100

How to get biggest differences in column compared to last month?

I have two tables - Products and Prices
Every month table Prices is populated with new prices for each products. How can I get 5 products whose prices have the biggest incremental difference from last month prices?
table Products
id | name
1 | apples
2 | pears
3 | bananas
table Prices
id | price | product_id | created_at
1 | 10 | 1 | 2017-02-07 07:00:00
2 | 10 | 2 | 2017-02-07 07:00:00
3 | 15 | 3 | 2017-02-07 07:00:00
5 | 15 | 1 | 2017-03-07 07:00:00
6 | 20 | 2 | 2017-03-07 07:00:00
7 | 25 | 3 | 2017-03-07 07:00:00
The result would be to find out that
1. Bananas has prices by 15 higher (lastMonth: 15, now: 25)
2. Pears 2 has prices by 10 higher (lastMonth: 10, now: 20)
3. Apples has prices by 5 higher (lastMonth: 10, now: 15)
I was thinking something like this (uff I know this is terrible)
SELECT products.id, products.name, prices.beforePrice, prices.afterPrice, prices.difference
FROM products
INNER JOIN prices ON products.id = prices.product_id
WHERE
(
SELECT *biggest-difference*
FROM prices
WHERE *difference_between_last_2_months*
GROUP BY product_id
LIMIT 5
)
Create table/insert data
CREATE TABLE Products
(`id` INT, `name` VARCHAR(7))
;
INSERT INTO Products
(`id`, `name`)
VALUES
(1, 'apples'),
(2, 'pears'),
(3, 'bananas')
;
CREATE TABLE Prices
(`id` INT, `price` INT, `product_id` INT, `created_at` DATETIME)
;
INSERT INTO Prices
(`id`, `price`, `product_id`, `created_at`)
VALUES
(1, 10, 1, '2017-02-07 07:00:00'),
(2, 10, 2, '2017-02-07 07:00:00'),
(3, 15, 3, '2017-02-07 07:00:00'),
(5, 15, 1, '2017-03-07 07:00:00'),
(6, 20, 2, '2017-03-07 07:00:00'),
(7, 25, 3, '2017-03-07 07:00:00')
;
Query
SELECT
Products.id
, Products.name
, (current_month.price - last_month.price) AS difference
, (
CASE
WHEN last_month.price > current_month.price
THEN 'lower'
WHEN last_month.price < current_month.price
THEN 'higher'
END
) AS incremental
, last_month.price 'lastMonth'
, current_month.price 'now'
FROM (
SELECT
*
FROM
Prices
WHERE
MONTH(created_at) = MONTH((CURDATE() - INTERVAL 1 MONTH))
)
AS
last_month
INNER JOIN (
SELECT
*
FROM
Prices
WHERE
MONTH(created_at) = MONTH((CURDATE()))
)
AS
current_month
ON
last_month.product_id = current_month.product_id
INNER JOIN
Products
ON
last_month.product_id = Products.id
WHERE
last_month.price < current_month.price #incremental should be higher
ORDER BY
difference DESC
LIMIT 5
Result
id name difference incremental lastMonth now
------ ------- ---------- ----------- --------- --------
2 pears 10 higher 10 20
3 bananas 10 higher 15 25
1 apples 5 higher 10 15
You can use a proper joins based on fliterd select by month.
This should return the value you need ( you can add the literal string you need )
select p.name, m1.price as this_month, m2.price as prev_month, m2.price-m1.price as diff
from product
left join (
select price, product_id
from Prices
where month(created_at) = month(NOW())
and year(created_at) = year(NOW())
) m1 on m1.product_id = p.id
left join (
select price, product_id
from Prices
where month(created_at) = MONT(DATE_SUB(NOW(), INTERVAL 1 MONTH)
and year(created_at) = year(DATE_SUB(NOW(), INTERVAL 1 MONTH)
) m2 on m2.product_id = p.id
order by diff desc
limit 5

Get record in range of multiples of 5

i have a existing new week_table -
start_date end_date weekno ----------------------------------------------
1996-01-01 1996-01-05 1
1996-01-08 1996-01-12 2
1996-01-15 1996-01-19 3
1996-01-22 1996-01-26 4
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''till
1998-12-21 1998-12-26 156
i am trying to extract records with a count of 5 weeks in group. I am looking at results like
start_date end_date weekno_start weekno_end ----------------------------------------------
1996-01-01 1996-02-02 1 5
1996-02-05 1996-03-08 6 10
1996-03-11 1996-04-12 11 16
i do get the results but the weekno numbers keep running over the maximum week no in the database. for records over weekno 156 i get rows with null value.
How can i avoid the records with null and limit the view to the maximum week no
my current code is-
SELECT (t1.weekno * 5) - 4 AS start_id
,t3.start_date
,t4.end_date
,(t1.weekno * 5) AS end_id
FROM weekcon_table t1
LEFT JOIN weekcon_table t2 ON (t2.weekno = t1.weekno * 5)
LEFT JOIN weekcon_table t3 ON (t3.weekno = (t1.weekno * 5) - 4)
LEFT JOIN weekcon_table t4 ON (t4.weekno = (t1.weekno * 5))
Have you tried something like this:
select min(weekno) as `start_id`,
min(start_date) as `start_date`,
max(end_date) as `end_date`,
min(weekno) as `weekno_start`,
max(weekno) as `weekno_end`
from weekcon_table
group by ((weekno - 1) DIV 5)
order by ((weekno - 1) DIV 5) asc
Here is the output:
start_id start_date end_date weekno_start weekno_end
1 01/01/1996 26/01/1996 1 5
6 04/03/1996 24/02/1996 6 10
11 01/04/1996 30/03/1996 11 15
16 06/05/1996 27/04/1996 16 20
21 03/06/1996 25/05/1996 21 23
Record Count: 5; Execution Time: 1ms View Execution Plan link
I create two tables and asign a rank_id
the first one is for star_date ... will be each row weekno % 5 = 1
second table is for end_date ... will be each row weekno % 5 = 0 and also include the last date of all weeks.
Then join by rank_id
Sql Fiddle Demo In the demo you can change the select fields for * if want see what is happening
SELECT ini_range.start_date,
end_range.end_date,
ini_range.weekno,
end_range.weekno
FROM
(
SELECT r.* ,
(SELECT count(distinct r2.weekno)
FROM
(
SELECT *
FROM t_week
WHERE weekno % 5 = 1
) r2
WHERE r2.weekno <= r.weekno
) as rank
FROM
(
SELECT *
FROM t_week
WHERE weekno % 5 = 1
) r
) ini_range
JOIN
(
SELECT r.* ,
(SELECT count(distinct r2.weekno)
FROM
(
SELECT *
FROM t_week
WHERE weekno % 5 = 0
or weekno = (SELECT max(weekno) FROM t_week)
) r2
WHERE r2.weekno <= r.weekno
) as rank
FROM
(
SELECT *
FROM t_week
WHERE weekno % 5 = 0
or weekno = (SELECT max(weekno) FROM t_week)
) r
) end_range
ON ini_range.rank = end_range.rank
OUTPUT
| start_date | end_date | weekno | weekno |
|------------|------------|--------|--------|
| 01/01/1996 | 03/02/1996 | 1 | 5 |
| 05/02/1996 | 09/03/1996 | 6 | 10 |
| 11/03/1996 | 13/04/1996 | 11 | 15 |
| 15/04/1996 | 18/05/1996 | 16 | 20 |
| 20/05/1996 | 08/06/1996 | 21 | 23 | <- 23 is last week
and group only have
3 week instead of 5
I found another solution
SQL Fiddle Demo
SELECT *
FROM t_week w_ini
JOIN t_week w_end
ON w_ini.weekno = w_end.weekno + 4
OR w_ini.weekno + 5 > w_end.weekno
WHERE
w_ini.weekno % 5 = 1
and w_ini.weekno < w_end.weekno
and(
w_end.weekno % 5 = 0 or
w_end.weekno = (SELECT max(weekno) FROM t_week)
)