Mysql query - Count items grouping by year and including "sub-counts" - mysql

I have a table "events" like this
id | user_id | date | is_important
---------------------------------------------------
1 | 3 | 01/02/2012 | 0
1 | 3 | 01/02/2012 | 1
1 | 3 | 01/02/2011 | 1
1 | 3 | 01/02/2011 | 1
1 | 3 | 01/02/2011 | 0
Basically, what I need to get is this:
(for the user_id=3)
year | count | count_importants
--------------------------------------------
2012 | 2 | 1
2011 | 3 | 2
I've tried this:
SELECT YEAR(e1.date) as year,COUNT(e1.id) as count_total, aux.count_importants
FROM events e1
LEFT JOIN
(
SELECT YEAR(e2.date) as year2,COUNT(e2.id) as count_importants
FROM `events` e2
WHERE e2.user_id=18
AND e2.is_important = 1
GROUP BY year2
) AS aux ON aux.year2 = e1.year
WHERE e1.user_id=18
GROUP BY year
But mysql gives me an error
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'aux ON aux.year2 = e1.year WHERE e1.user_id=18 GROUP BY year LIMIT 0, 30' at line 10
And i've run out of ideas to make this query u_Uº. Is it possible to do this using only one query??
Thanks in advance

Edit: I think I over-complicated things. Can't you just do this in a simple query?
SELECT
YEAR(`year`) AS `year`,
COUNT(`id`) AS `count`,
SUM(`is_important`) AS `count_importants`
FROM `events`
WHERE user_id = 18
GROUP BY YEAR(`year`)
Here's the big solution that adds summaries :)
Consider using MySQL GROUP BY ROLLUP. This will basically do a similar job to a normal GROUP BY, but will add rows for the summaries too.
In the example below, you see two records for Finland in 2000, for £1500 and £100, and then a row with the NULL product with the combined value of £1600. It also adds NULL rollup rows for each dimension grouped by.
From the manual:
SELECT year, country, product, SUM(profit)
FROM sales
GROUP BY year, country, product WITH ROLLUP
+------+---------+------------+-------------+
| year | country | product | SUM(profit) |
+------+---------+------------+-------------+
| 2000 | Finland | Computer | 1500 |
| 2000 | Finland | Phone | 100 |
| 2000 | Finland | NULL | 1600 |
| 2000 | India | Calculator | 150 |
| 2000 | India | Computer | 1200 |
| 2000 | India | NULL | 1350 |
| 2000 | USA | Calculator | 75 |
| 2000 | USA | Computer | 1500 |
| 2000 | USA | NULL | 1575 |
| 2000 | NULL | NULL | 4525 |
| 2001 | Finland | Phone | 10 |
| 2001 | Finland | NULL | 10 |
| 2001 | USA | Calculator | 50 |
| 2001 | USA | Computer | 2700 |
| 2001 | USA | TV | 250 |
| 2001 | USA | NULL | 3000 |
| 2001 | NULL | NULL | 3010 |
| NULL | NULL | NULL | 7535 |
+------+---------+------------+-------------+
Here's an example the specifically matches your situation:
SELECT year(`date`) AS `year`, COUNT(`id`) AS `count`, SUM(`is_important`) AS `count_importants`
FROM new_table
GROUP BY year(`date`) WITH ROLLUP;

The alias year - year(e1.date) AS year is not visible in JOIN ON clause. Try to use this condition -
...
LEFT JOIN
(
...
) ON aux.year2 = year(e1.date) -- e1.year --> year(e1.date)
...

Related

How to find elements on one column for values in other columns having no more than 3 gap in SQL

I have an sql view say emp_table which looks like the following:
+----------+----------+------+
| actor_id | movie_id | year |
+----------+----------+------+
| 2 | 280088 | 2002 |
| 2 | 396232 | 2000 |
| 3 | 376687 | 2000 |
| 4 | 336265 | 2001 |
| 5 | 135644 | 1953 |
| 6 | 12083 | 1996 |
| 7 | 252053 | 1993 |
| 7 | 402635 | 1992 |
| 7 | 409592 | 1995 |
| 8 | 101866 | 2000 |
| 9 | 336265 | 2001 |
| 10 | 12148 | 2000 |
| 11 | 80189 | 2001 |
| 12 | 12148 | 2000 |
| 13 | 80189 | 2001 |
| 14 | 70079 | 1982 |
| 15 | 12148 | 2000 |
| 16 | 242675 | 1991 |
| 17 | 105231 | 1993 |
| 17 | 242453 | 1988 |
+----------+----------+------+
... and so on. I need to find all the actor_id who never had a career gap of more than 3 year. Meaning I need to calculate all the actors for whom if I calculate the number of unique years they acted in a movie, and then sort it, then the maximum consecutive difference between the year would never be more than 3 years.
Please help me with this sql query. I have tried sql self join but couldn't think more about it.
All the SQL code is for MySQL only.
Note You can consider that there is only one combination of actor_id and movie_id.
Expected Result
+----------+----------+
| actor_id | max_gap |
+----------+----------+
| 2 | 2 |
| 3 | 0 |
| 4 | 0 |
| 5 | 0 |
| 6 | 0 |
| 7 | 2 |
| . | . |
| . | . |
| . | . |
| 17 | 5 |
+----------+----------+
And so on
Note 2: Sorry for so many changes in the output. This is the final version and no more change after this.
With MySQL 8 and MariaDB 10.2 you can use the window function LEAD() to get the next consecutive playing year for an actor (or LAG() for the last one). Then you just need to get the max difference in the outer query.
with tmp as (
select
actor_id,
year,
lead(year) over (partition by actor_id order by year) as year_lead
from emp_table e
)
select actor_id, coalesce(max(year_lead - year), 0) as max_gap
from tmp
group by actor_id
having max_gap <= 3;
Demo: https://www.db-fiddle.com/f/cWChT2TqLuRT8bW1zcM9G2/0
I initially started with an anti-join approach, but then changed it upon seeing your requirement for the max gap.
The approach below begins with a subquery which itself uses a correlated subquery to compute the forward looking year gap, for every actor record and year. It then aggregates by actor and asserts that a gap greater than 3 years never occurs.
SELECT actor_id, MAX(gap) AS max_gap
FROM
(
SELECT
e1.actor_id,
ABS(e1.year - COALESCE((SELECT e2.year FROM emp_table e2
WHERE e2.actor_id = e1.actor_id AND e2.year > e1.year
ORDER BY e2.year LIMIT 1), e1.year)) AS gap
FROM emp_table e1
) t
GROUP BY
actor_id
HAVING
MAX(gap) <= 3;
Note that the call to COALESCE is very necessary, because of the edge case of an actor's most recent year. In this case, there is no forward looking year, but we want to discount this year.
A self join of the table and the group by actor_id:
select
e1.actor_id, max(coalesce(e2.year, e1.year) - e1.year) max_gap
from emp_table e1 left join emp_table e2
on
e2.actor_id = e1.actor_id
and
e2.year = (
select min(year) from emp_table where actor_id = e1.actor_id and year > e1.year
)
group by e1.actor_id
having max_gap <= 3
See the demo

Calculate sum for group records in MySQL

I have this table of orders
| ORDER_ID | PRODUCT | CUSTOMER | QTY | DATE
---------------------------------------------
| 1 | shoes | Nick | 1 | 01/01/2016
| 2 | shirts | Nick | 5 | 02/02/2016
| 3 | shoes | Paul | 10 | 03/03/2016
| 4 | shirts | Paul | 20 | 04/04/2016
So, How can I achieve this report result with ONE Select Statement?
| Date_of_Order | Customer | Quantity | PRODUCT_TOTAL_SALES |
-----------------------------------------------------------------
| 01/01/2016 | Nick | 1 | shoes : 11 |
| 02/02/2016 | Nick | 10 | shirts : 25 |
| 03/03/2016 | Paul | 5 | shoes : 11 |
| 04/04/2016 | Paul | 20 | shirts : 25 |
I know how to use concat(column1, ' ', column2) to create a combined column but I haven't succeed to add a sum for a grouped item there. When I try with left join I get the sum for a product ...BUT its always the whole sum and its not related to the dates of the order so when I try to filter the results on my query for a certain period I still get 11 for shoes and 25 for shirts...
You can group by multiple columns and get the sum for the smallest group.
If you want the daily sales, then instead of GROUP BY product use GROUP BY product, date
SELECT
o.`date` AS Date_of_Order,
SUM(o.qty) as Total_Quantity,
CONCAT(o.product, ':', SUM(o.qty))
FROM
orders o
GROUP BY product, `date`
ORDER BY `date`
Simple additional SELECT from same table can do that for entire period:
SELECT
o.`date` AS Date_of_Order,
o.Customer,
o.qty as Quantity,
(SELECT
CONCAT(oo.product, ':', SUM(oo.qty))
FROM
orders oo
WHERE
oo.product = o.product
) PRODUCT_TOTAL_SALES
FROM
orders o
Output:
+---------------+----------+----------+---------------------+
| Date_of_Order | Customer | Quantity | PRODUCT_TOTAL_SALES |
+---------------+----------+----------+---------------------+
| 01/01/2016 | Nick | 1 | shoes:11 |
| 02/02/2016 | Nick | 5 | shirts:25 |
| 03/03/2016 | Paul | 10 | shoes:11 |
| 04/04/2016 | Paul | 20 | shirts:25 |
+---------------+----------+----------+---------------------+
4 rows in set
If you want to filter by certain period, you must include it in both:
SELECT
o.`date` AS Date_of_Order,
o.Customer,
o.qty as Quantity,
(SELECT
CONCAT(oo.product, ':', sum(oo.qty))
FROM
orders oo
WHERE
oo.product = o.product
AND STR_TO_DATE(oo.`date`,'%d/%m/%Y') BETWEEN '2016-01-01' AND '2016-03-03'
) PRODUCT_TOTAL_SALES
FROM
orders o
WHERE
STR_TO_DATE(o.`date`,'%d/%m/%Y') BETWEEN '2016-01-01' AND '2016-03-03'
Output:
+---------------+----------+----------+---------------------+
| Date_of_Order | customer | Quantity | PRODUCT_TOTAL_SALES |
+---------------+----------+----------+---------------------+
| 01/01/2016 | Nick | 1 | shoes:11 |
| 02/02/2016 | Nick | 5 | shirts:5 |
| 03/03/2016 | Paul | 10 | shoes:11 |
+---------------+----------+----------+---------------------+
3 rows in set

MySQL select a row from a daterange excluding the year

I'm trying to create a MySQL query to select the daily price from a table that is between a date range from another. I only want to use 'starting-ending' months and days from the table "seasons" and I want to pass the year dynamically to the query.
This is my query: (I'm giving it the Year to exclude the one on the table)
SELECT a.season, b.base_price
FROM seasons a
JOIN pricebyseason b ON a.id=b.season_id
WHERE b.prop_id='6' AND '2015-11-29' BETWEEN DATE_FORMAT(a.starting,'2015-%m-%d') AND DATE_FORMAT(a.ending,'2016-%m-%d')
ORDER BY b.base_price DESC
It works but not with all dates.
These are the tables:
seasons (these are static date values)
+----+--------------+------------+------------+
| id | season | starting | ending |
+----+--------------+------------+------------+
| 1 | Peak Season | 2015-12-11 | 2016-01-09 |
| 2 | High Season | 2015-11-27 | 2016-04-15 |
| 3 | Mid Season | 2015-04-16 | 2015-09-01 |
| 4 | Low Season | 2015-09-02 | 2015-11-26 |
| 5 | Spring Break | 2015-03-05 | 2015-03-21 |
+----+--------------+------------+------------+
pricebyseason
+----+---------+-----------+------------+
| id | prop_id | season_id | base_price |
+----+---------+-----------+------------+
| 1 | 6 | 1 | 950 |
| 2 | 6 | 2 | 750 |
| 3 | 6 | 3 | 450 |
| 4 | 6 | 4 | 400 |
| 5 | 6 | 5 | 760 |
+----+---------+-----------+------------+
What I want to achive is query the dialy price between checkin, checkout selection
I create this sqlfiddle: http://sqlfiddle.com/#!9/4a6f4
This is a previuos query that is not working either:
SELECT a.base_price,b.season,b.starting,b.ending
FROM pricebyseason a JOIN seasons b ON a.season_id=b.id
WHERE a.prop_id='6' AND
(DATE_FORMAT(b.starting,'%m-%d') <= '12-27' OR DATE_FORMAT(b.starting,'2016-%m-%d') >= '2015-12-27')
AND
(DATE_FORMAT(b.ending,'%m-%d') >= '12-27' OR DATE_FORMAT(b.ending,'2016-%m-%d') <= '2015-12-27')
ORDER BY base_price DESC
And here are some sample dates for each season: '2016-01-08','2015-12-27','2016-04-14','2015-11-29','2016-04-15','2015-09-01','2016-09-02','2015-11-26','2016-10-10','2016-03-18','2016-06-22','2015-06-15'
Thank a lot

MySQL: Print the name of the rows with top 3 values of the each column

+-----------------------------+------+------+------+
| State | 2006 | 2007 | 2008 |
+-----------------------------+------+------+------+
| Andaman and Nicobar Islands | 32 | 27 | 23 |
| Andhra Pradesh | 3824 | 2432 | 1591 |
| Arunachal Pradesh | 12 | 9 | 25 |
| Assam | 617 | 319 | 530 |
| Bihar | 1665 | 1949 | 1944 |
| Chandigarh | 0 | 5 | 4 |
| Chhattisgarh | 374 | 401 | 855 |
| Dadra and Nagar Haveli | 0 | 0 | 0 |
| Daman and Diu | 2 | 0 | 1 |
| Delhi | 0 | 0 | 0 |
| Goa | 72 | 1 | 42 |
| Gujarat | 2038 | 328 | 540 |
| Haryana | 350 | 520 | 427 |
| Himachal Pradesh | 323 | 214 | 34 |
I have the similar table, just with more number of rows. The columns depict the number of accidents for the given year. I need to print the top 3 states with most number of accidents EVERY YEAR. Is there a way to do it in one go?
Currently, I'm only able to do this year-wise:
SELECT State AS 'Accidents-2006'
FROM accidents
ORDER BY `2006`
DESC LIMIT 3;
And then repeating it for 2007 and 2008.
EDIT: I'm searching for an output like this:
+-----------------+-----------------+--------------------+
| 2006 | 2007 | 2008 |
+-----------------+-----------------+--------------------+
| Andhra Pradesh | Andhra Pradesh | Bihar |
| Gujarat | Bihar | Andhra Pradesh |
| Bihar | Haryana | Chhattisgarh |
You table has some serious normalization issues.
Having said that and in case restructuring your table schema is not an option for you, here's a way to get the required result set using variables:
SELECT `Y2006`, `Y2007`, `Y2008`
FROM (
SELECT State AS `Y2006`, #rn1:=#rn1+1 AS rn1
FROM mytable
CROSS JOIN (SELECT #rn1:=0) AS v
ORDER BY `2006` DESC LIMIT 3
) t1
LEFT JOIN (
SELECT State AS `Y2007`, #rn2:=#rn2+1 AS rn2
FROM mytable
CROSS JOIN (SELECT #rn2:=0) AS v
ORDER BY `2007` DESC LIMIT 3
) t2 ON t1.rn1 = t2.rn2
LEFT JOIN (
SELECT State AS `Y2008`, #rn3:=#rn3+1 AS rn3
FROM mytable
CROSS JOIN (SELECT #rn3:=0) AS v
ORDER BY `2008` DESC LIMIT 3
) t3 ON t2.rn2 = t3.rn3
Demo here
Note: Probably should be a comment but not enough Rep to comment.
Personally I would change it to be 3 (4 for an ID) column table consisting of:
State | Year | Accidents
Andaman and Nicobar Islands | 2006 | 32
Andaman and Nicobar Islands | 2007 | 27
This would be easier for future proofing the data as you would not need to edit the structure to add another year of data and will also help you arrange the data how you want it.

Select rows with alternate ordered field from another table

Given a *students_exam_rooms* table:
+------------+---------+---------+
| student_id | room_id | seat_no |
+------------+---------+---------+
| 1 | 30 | 1001 |
| 2 | 30 | 1002 |
| 3 | 31 | 2001 |
| 4 | 32 | 2002 |
| 5 | 33 | 3001 |
| 6 | 33 | 3002 |
| 7 | 34 | 4001 |
| 8 | 34 | 4002 |
+------------+---------+---------+
And *students_tbl*:
+------------+-------------+------+
| student_id | studen_name | year |
+------------+-------------+------+
| 1 | Eric | 1 |
| 2 | Mustafa | 1 |
| 3 | Michael | 2 |
| 4 | Andy | 2 |
| 5 | Rafael | 3 |
| 6 | Mark | 3 |
| 7 | Jack | 4 |
| 8 | peter | 4 |
+------------+-------------+------+
How can I select from *students_exam_rooms* ordering by *students_tbl.year* but with one after one like this:
+--------------+------+
| student_name | year |
+--------------+------+
| Eric | 1 |
| Michael | 2 |
| Rafael | 3 |
| Jack | 4 |
| Mustafa | 1 |
| Andy | 2 |
| Mark | 3 |
| Peter | 4 |
+--------------+------+
I'm assuming that you want to order by the "occurrence-count" of the year then the year, e.g. all the first-occurrences of all years first, sorted by year, then all second-occurrences of all years also sorted by year, and so on. That would be a perfect case for emulating other RDBMS' analytic / windowing functions:
select *
from (
select
s.studen_name,
s.year,
ser.*,
(
select 1 + count(*)
from students_tbl s2
where s.year = s2.year
and s.student_id > s2.student_id
) rank
from students_tbl s
JOIN students_exam_rooms ser
ON s.student_id = ser.student_id
) i_dont_really_want_to_name_this
order by rank, year
Here it is against a slightly tweaked version of JW's fiddle: http://www.sqlfiddle.com/#!2/27c91/1
Emulating Analytic (AKA Ranking) Functions with MySQL is a good article that gives more background and explanation.
try any of these below:
SELECT a.studen_name, a.year
FROM students_tbl a
INNER JOIN students_exam_rooms b
ON a.student_id = b.student_id
ORDER BY REVERSE(b.seat_no),
a.year
SQLFiddle Demo
by using Modulo
SELECT a.studen_name, a.year
FROM students_tbl a
INNER JOIN students_exam_rooms b
ON a.student_id = b.student_id
ORDER BY CASE WHEN MOD(b.seat_no, 2) <> 0 THEN 0 ELSE 1 END,
a.year
SQLFiddle Demo
Looks to me like you're trying to sort first by seat and then by year. Looking at your students_exam_rooms table, it looks like you started with a simple seat number and prepended year * 1000. So, if we omit the year, it looks like this:
> select * from fixed_students_exam_rooms;
+------------+---------+---------+
| student_id | room_id | seat_no |
+------------+---------+---------+
| 1 | 30 | 1 |
| 2 | 30 | 2 |
| 3 | 31 | 1 |
| 4 | 32 | 2 |
| 5 | 33 | 1 |
| 6 | 33 | 2 |
| 7 | 34 | 1 |
| 8 | 34 | 2 |
+------------+---------+---------+
And if you had that table, your query is simple:
select
student_name, year
from
modified_student_exame_rooms
left join students_tbl using (student_id)
order by
seat_no, year
;
Using the table as you currently have it, it's only slightly more complicated, assuming the "core seat number" doesn't excede 999.
select
student_name, year
from
modified_student_exame_rooms
left join students_tbl using (student_id)
order by
convert(substr(seat_no, 2), unsigned),
year
;