Select number of customers in some interval - mysql

I have 2 rows like in example below.
I want to select how many customers I have in some intervals of consumption. Example: 1000-3000, 3000-5000 ...
This is my sql query:
SELECT t.consumption, COUNT(DISTINCT t.id) as numberOfCustomers <br>
FROM(
SELECT customer.id, SUM(order.value) AS consumption
FROM order <br>
INNER JOIN customer ON order.customerId = customer.id
GROUP BY customer.id) AS t
GROUP BY t.consumption

You can create a range with this function:
CONCAT((FLOOR(t.consumption/2000)) * 2000, '-',
((FLOOR(t.consumption/2000)) * 2000) + 1999) -- the step is 2000
and then make your sum group by this range

Related

Querying Customers who have rented a movie at least once every week or in the Weekend

I have a DB for movie_rental. The Tables I have are for :
Customer Level:
Primary key: Customer_id(INT)
first_name(VARCHAR)
last_name(VARCHAR)
Movie Level:
Primary key: Film_id(INT)
title(VARCHAR)
category(VARCHAR)
Rental Level:
Primary key: Rental_id(INT).
The other columns in this table are:
Rental_date(DATETIME)
customer_id(INT)
film_id(INT)
payment_date(DATETIME)
amount(DECIMAL(5,2))
Now the question is to Create a master list of customers categorized by the following:
Regulars, who rent at least once a week
Weekenders, for whom most of their rentals come on Saturday and Sundays
I am not looking for the code here but the logic to approach this problem. Have tried quite a number of ways but was not able to form the logic as to how I can look up for a customer id in each week. The code I tried is as follows:
select
r.customer_id
, concat(c.first_name, ' ', c.last_name) as Customer_Name
, dayname(r.rental_date) as day_of_rental
, case
when dayname(r.rental_date) in ('Monday','Tuesday','Wednesday','Thursday','Friday')
then 'Regulars'
else 'Weekenders'
end as Customer_Category
from rental r
inner join customer c on r.customer_id = c.customer_id;
I know it is not correct but I am not able to think beyond this.
First, you don't need the customer table for this. You can add that in after you have the classification.
To solve the problem, you need the following information:
The total number of rentals.
The total number of weeks with a rental.
The total number of weeks overall or with no rental.
The total number of rentals on weekend days.
You can obtain this information using aggregation:
select r.customer_id,
count(*) as num_rentals,
count(distinct yearweek(rental_date)) as num_weeks,
(to_days(max(rental_date)) - to_days(min(rental_date)) ) / 7 as num_weeks_overall,
sum(dayname(r.rental_date) in ('Saturday', 'Sunday')) as weekend_rentals
from rental r
group by r.customer_id;
Now, your question is a bit vague on thresholds and what to do if someone only rents on weekends but does so every week. So, I'll just make arbitrary assumptions for the final categorization:
select r.customer_id,
(case when num_weeks > 10 and
num_weeks >= num_weeks_overall * 0.9
then 'Regular' -- at least 10 weeks and rents in 90% of the weeks
when weekend_rentals >= 0.8 * num_rentals
then 'Weekender' -- 80% of rentals are on the weekend'
else 'Hoi Polloi'
end) as category
from (select r.customer_id,
count(*) as num_rentals,
count(distinct yearweek(rental_date)) as num_weeks,
(to_days(max(rental_date)) - to_days(min(rental_date)) ) / 7 as num_weeks_overall,
sum(dayname(r.rental_date) in ('Saturday', 'Sunday')) as weekend_rentals
from rental r
group by r.customer_id
) r;
The problem with the current approach is that every rental of every customer will be treated separately. I am assuming a customer might rent more than once and so, we will need to aggregate all rental data for a customer to calculate the category.
So to create the master table, you have mentioned in the logic that weekenders are customers "for whom most of their rentals come on Saturday and Sundays", whereas regulars are customers who rent at least once a week.
2 questions:-
What is the logic for "most" for weekenders?
Are these two categories mutually exclusive? From the statement it does not seem so, because a customer might rent only on a Saturday or a Sunday.
I have tried a solution in Oracle SQL dialect (working but performance can be improved) with the logic being thus: If the customer has rented more on weekdays than on weekends, the customer is a Regular, else a Weekender. This query can be modified based on the answers to the above questions.
select
c.customer_id,
c.first_name || ' ' || c.last_name as Customer_Name,
case
when r.reg_count>r.we_count then 'Regulars'
else 'Weekenders'
end as Customer_Category
from customer c
inner join
(select customer_id, count(case when trim(to_char(rental_date, 'DAY')) in ('MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY') then 1 end) as reg_count,
count(case when trim(to_char(rental_date, 'DAY')) in ('SATURDAY','SUNDAY') then 1 end) as we_count
from rental group by customer_id) r on r.customer_id=c.customer_id;
Updated query based on clarity given in comment:-
select
c.customer_id,
c.first_name || ' ' || c.last_name as Customer_Name,
case when rg.cnt>0 then 1 else 0 end as REGULAR,
case when we.cnt>0 then 1 else 0 end as WEEKENDER
from customer c
left outer join
(select customer_id, count(rental_id) cnt from rental where trim(to_char(rental_date, 'DAY')) in ('MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY') group by customer_id) rg on rg.customer_id=c.customer_id
left outer join
(select customer_id, count(rental_id) cnt from rental where trim(to_char(rental_date, 'DAY')) in ('SATURDAY','SUNDAY') group by customer_id) we on we.customer_id=c.customer_id;
Test Data :
insert into customer values (1, 'nonsensical', 'coder');
insert into rental values(1, 1, sysdate, 1, sysdate, 500);
insert into customer values (2, 'foo', 'bar');
insert into rental values(2, 2, sysdate-5, 2, sysdate-5, 800); [Current day is Friday]
Query Output (first query):
CUSTOMER_ID CUSTOMER_NAME CUSTOMER_CATEGORY
1 nonsensical coder Regulars
2 foo bar Weekenders
Query Output (second query):
CUSTOMER_ID CUSTOMER_NAME REGULAR WEEKENDER
1 nonsensical coder 0 1
2 foo bar 1 0
This is a study of cohorts. First find the minimal expression of each group:
# Weekday regulars
SELECT
customer_id
FROM rental
WHERE WEEKDAY(`date`) < 5 # 0-4 are weekdays
# Weekend warriors
SELECT
customer_id
FROM rental
WHERE WEEKDAY(`date`) > 4 # 5 and 6 are weekends
Now we know how to get a listing of customers who have rented on weekdays and weekends, inclusive. These queries only actually tell us that these were customers who visited on a day in the given series, hence we need to make some judgements.
Let's introduce a periodicity, which then allows us to gain thresholds. We'll need aggregation too, so we're going to count the weeks that are distinctly knowable by grouping to the rental.customer_id.
# Weekday regulars
SELECT
customer_id
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(`date`) < 5
GROUP BY customer_id
# Weekend warriors
SELECT
customer_id
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(`date`) > 4
GROUP BY customer_id
We also need a determinant period:
FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS weeks_in_period
Put those together:
# Weekday regulars
SELECT
customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(`date`) < 5
CROSS JOIN (
SELECT FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS total_weeks
) AS period
GROUP BY customer_id
# Weekend warriors
SELECT
customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
CROSS JOIN (
SELECT FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS total_weeks
) AS period
WHERE WEEKDAY(`date`) > 4
GROUP BY customer_id
So now we can introduce our threshold accumulator per cohort.
# Weekday regulars
SELECT
customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(`date`) < 5
CROSS JOIN (
SELECT FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS total_weeks
) AS period
GROUP BY customer_id
HAVING total_weeks = weeks_as_customer
# Weekend warriors
SELECT
customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(`date`)) AS weeks_as_customer
FROM rental
CROSS JOIN (
SELECT FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS total_weeks
) AS period
WHERE WEEKDAY(`date`) > 4
GROUP BY customer_id
HAVING total_weeks = weeks_as_customer
Then we can use these to subquery our master list.
SELECT
customer.customer_id
, CONCAT(customer.first_name, ' ', customer.last_name) as customer_name
, CASE
WHEN regulars.customer_id IS NOT NULL THEN 'regular'
WHEN weekenders.customer_id IS NOT NULL THEN 'weekender'
ELSE NULL
AS category
FROM customer
CROSS JOIN (
SELECT FLOOR(DATEDIFF(DATE(NOW()), '2019-01-01') / 7) AS total_weeks
) AS period
LEFT JOIN (
SELECT
rental.customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(rental.`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(rental.`date`) < 5
GROUP BY rental.customer_id
HAVING total_weeks = weeks_as_customer
) AS regulars ON customer.customer_id = regulars.customer_id
LEFT JOIN (
SELECT
rental.customer_id
, period.total_weeks
, COUNT(DISTINCT YEARWEEK(rental.`date`)) AS weeks_as_customer
FROM rental
WHERE WEEKDAY(rental.`date`) > 4
GROUP BY rental.customer_id
HAVING total_weeks = weeks_as_customer
) AS weekenders ON customer.customer_id = weekenders.customer_id
HAVING category IS NOT NULL
There is some ambiguity as far as whether cross-cohorts are to be left out (regulars who missed a week because they rented on the weekend-only at least once, for instance). You would need to work this type of inclusivity/exclusivity question out.
This would involve going back to the cohort-specific queries to introduce and tune the queries to explain that degree of further comprehension, and/or add other cohort cross-cutting subqueries that can be combined in other ways to establish better and/or more comprehensions at the top view.
However, I think what I've provided matches reasonably with what you've provided given this caveat.

Compute 2 subqueries then group by date

I have this Table
I want to run subqueries first then add them together grouped by date
Expected Result should be like this:
I am running this query
(
SELECT DATE_FORMAT(dd1.modified_datetime,'%Y-%m-%d') as date, (v1+v2) as value FROM
(SELECT modified_datetime, Sum(data->"$.amount") as v1
FROM transactions
GROUP BY modified_datetime) as dd1 ,
(SELECT modified_datetime, MAX(data->"$.amount") as v2
FROM transactions
GROUP BY modified_datetime) as dd2
GROUP BY dd1.modified_datetime, value
)
and getting this result:
Use JOIN between subqueries and every next one:
(SELECT modified_datetime, Sum(data->"$.amount") as v1
FROM transactions
GROUP BY modified_datetime) as dd1 JOIN
(SELECT modified_datetime, MAX(data->"$.amount") as v2
FROM transactions
GROUP BY modified_datetime) as dd2 ON dd1.modified_datetime=dd2.modified_datetime
If I followed you correctly, you can use union all and aggregation:
select date_format(dt, '%Y-%m-%d') dt_day, sum(amount) value
from (
select modified_datetime dt, data ->> '$.amount' amount from transactions
union all
select created_datetime, data ->> '$.amount' from transactions
) t
group by dt_day
order by dt_day

MySql - Selecting MAX & MIN and returning the corresponding rows

I trying to get the last 6 months of the min and max of prices in my table and display them as a group by months. My query is not returning the corresponding rows values, such as the date time for when the max price was or min..
I want to select the min & max prices and the date time they both occurred and the rest of the data for that row...
(the reason why i have concat for report_term, as i need to print this with the dataset when displaying results. e.g. February 2018 -> ...., January 2018 -> ...)
SELECT metal_price_id, CONCAT(MONTHNAME(metal_price_datetime), ' ', YEAR(metal_price_datetime)) AS report_term, max(metal_price) as highest_gold_price, metal_price_datetime FROM metal_prices_v2
WHERE metal_id = 1
AND DATEDIFF(NOW(), metal_price_datetime) BETWEEN 0 AND 180
GROUP BY report_term
ORDER BY metal_price_datetime DESC
I have made an example, extract from my DB:
http://sqlfiddle.com/#!9/617bcb2/4/0
My desired result would be to see the min and max prices grouped by month, date of min, date of max.. and all in the last 6 months.
thanks
UPDATE.
The below code works, but it returns back rows from beyond the 180 days specified. I have just checked, and it is because it joining by the price which may be duplicated a number of times during the years.... see: http://sqlfiddle.com/#!9/5f501b/1
You could use twice inner join on the subselect for min and max
select a.metal_price_datetime
, t1.highest_gold_price
, t1.report_term
, t2.lowest_gold_price
,t2.metal_price_datetime
from metal_prices_v2 a
inner join (
SELECT CONCAT(MONTHNAME(metal_price_datetime), ' ', YEAR(metal_price_datetime)) AS report_term
, max(metal_price) as highest_gold_price
from metal_prices_v2
WHERE metal_id = 1
AND DATEDIFF(NOW(), metal_price_datetime) BETWEEN 0 AND 180
GROUP BY report_term
) t1 on t1.highest_gold_price = a.metal_price
inner join (
select a.metal_price_datetime
, t.lowest_gold_price
, t.report_term
from metal_prices_v2 a
inner join (
SELECT CONCAT(MONTHNAME(metal_price_datetime), ' ', YEAR(metal_price_datetime)) AS report_term
, min(metal_price) as lowest_gold_price
from metal_prices_v2
WHERE metal_id = 1
AND DATEDIFF(NOW(), metal_price_datetime) BETWEEN 0 AND 180
GROUP BY report_term
) t on t.lowest_gold_price = a.metal_price
) t2 on t2.report_term = t1.report_term
simplified version of what you should do so you can learn the working process.
You need calculate the min() max() of the periods you need. That is your first brick on this building.
you have tableA, you calculate min() lets call it R1
SELECT group_field, min() as min_value
FROM TableA
GROUP BY group_field
same for max() call it R2
SELECT group_field, max() as max_value
FROM TableA
GROUP BY group_field
Now you need to bring all the data from original fields so you join each result with your original table
We call those T1 and T2:
SELECT tableA.group_field, tableA.value, tableA.date
FROM tableA
JOIN ( ... .. ) as R1
ON tableA.group_field = R1.group_field
AND tableA.value = R1.min_value
SELECT tableA.group_field, tableA.value, tableA.date
FROM tableA
JOIN ( ... .. ) as R2
ON tableA.group_field = R2.group_field
AND tableA.value = R2.max_value
Now we join T1 and T2.
SELECT *
FROM ( .... ) as T1
JOIN ( .... ) as T2
ON t1.group_field = t2.group_field
So the idea is if you can do a brick, you do the next one. Then you also can add filters like last 6 months or something else you need.
In this case the group_field is the CONCAT() value

making a query for stock/price trend in mysql

SQL Fiddle
Table scheme:
CREATE TABLE company
(`company_id` int,`name` varchar(30))
;
INSERT INTO company
(`company_id`,`name`)
VALUES
(1,"Company A"),
(2,"Company B")
;
CREATE TABLE price
(`company_id` int,`price` int,`time` timestamp)
;
INSERT INTO price
(`company_id`,`price`,`time`)
VALUES
(1,50,'2015-02-21 02:34:40'),
(2,60,'2015-02-21 02:35:40'),
(1,70,'2015-02-21 05:34:40'),
(2,120,'2015-02-21 05:35:40'),
(1,150,'2015-02-22 02:34:40'),
(2,130,'2015-02-22 02:35:40'),
(1,170,'2015-02-22 05:34:40'),
(2,190,'2015-02-22 05:35:40')
I'm using Cron Jobs to fetch company prices. In concatenating the price history for each company, how can I make sure that only the last one in each day is included? In this case, I want all of the price records around 05:30am concatenated.
This is the result I'm trying to get (I have used Date(time) to only get the dates from the timestamps):
COMPANY_ID PRICE TIME
1 70|170 2015-02-21|2015-02-22
2 120|190 2015-02-21|2015-02-22
I have tried the following query but it doesn't work. The prices don't correspond to the dates and I don't know how to exclude all of the 2:30 am records before applying the Group_concat function.
SELECT company_id,price,trend_date FROM
(
SELECT company_id, GROUP_CONCAT(price SEPARATOR'|') AS price,
GROUP_CONCAT(trend_date SEPARATOR'|') AS trend_date
FROM
(
SELECT company_id,price,
DATE(time) AS trend_date
FROM price
ORDER BY time ASC
)x1
GROUP BY company_id
)t1
Can anyone show me how to get the desired result?
Ok, so this should work as intended:
SELECT p.company_id,
GROUP_CONCAT(price SEPARATOR '|') as price,
GROUP_CONCAT(PriceDate SEPARATOR '|') as trend_date
FROM price as p
INNER JOIN (SELECT company_id,
DATE(`time`) as PriceDate,
MAX(`time`) as MaxTime
FROM price
GROUP BY company_id,
DATE(`time`)) as t
ON p.company_id = t.company_id
AND p.`time` = t.MaxTime
GROUP BY p.company_id
Here is the modified sqlfiddle.
This is a bit unorthodox but I think it solves your problem:
SELECT company_id,
GROUP_CONCAT(price SEPARATOR'|'),
GROUP_CONCAT(trend_date SEPARATOR'|')
FROM (
SELECT *
FROM (
SELECT company_id,
DATE(`time`) `trend_date`,
price
FROM price
ORDER BY `time` DESC
) AS a
GROUP BY company_id, `trend_date`
) AS b
GROUP BY company_id

SQL Sum cumulative and non-cumulative in same query

Hi I was wondering if there is a way to get a cumulative and non-cumulative total in the same query. I have a table with following fields:
Department, SalesPerson, fin_month, activity, cost
What I would like is have two sums, one that would give a monthly total for salesperson, and another giving a year to date total. I am having a problem setting two different where criteria to get it to work.
Many Thanks
Would something like this help?
SELECT
*
FROM
(
SELECT
Department, SalesPerson
, SUM(fin_month) SalesPerson_Sum
FROM
[TABLE_NAME]
GROUP BY Department, SalesPerson
) a
INNER JOIN
(
SELECT
Department
, SUM(fin_month) AS Department_Sum
FROM
[TABLE_NAME]
GROuP BY
Department
) b
ON
a.Department = b.Department
This solution uses CTEs, recursion, and ranking to obtain cumulative totals for every fin_month per SalesPerson in every Department based on the corresponding monthly totals.
;WITH
monthlytotals AS (
SELECT
Department,
SalesPerson,
fin_month,
MonthlyTotal = SUM(cost),
rn = ROW_NUMBER() OVER (PARTITION BY Department, SalesPerson
ORDER BY fin_month)
FROM atable
GROUP BY Department, SalesPerson, fin_month
),
alltotals AS (
SELECT
Department,
SalesPerson,
fin_month,
MonthlyTotal,
CumulativeTotal = MonthlyTotal,
rn
FROM monthlytotals
WHERE rn = 1
UNION ALL
SELECT
m.Department,
m.SalesPerson,
m.fin_month,
m.MonthlyTotal,
CumulativeTotal = a.CumulativeTotals + m.MonthlyTotal,
m.rn
FROM monthlytotals m
INNER JOIN alltotals a
ON m.Department = a.Department
AND m.SalesPerson = a.SalesPerson
AND m.rn = a.rn + 1
)
SELECT
Department,
SalesPerson,
fin_month,
MonthlyTotal,
CumulativeTotal
FROM alltotals