How to count rows by date in mysql and show dynamic headers - mysql

I need to show count of each sources(type colum) and show according to date range
Currently my data is as following
id type date
11 air 2019-10-21 10:00:01
21 air 2019-10-21 10:00:02
31 air 2019-10-21 10:01:03
41 air 2019-10-21 11:01:03
51 air 2019-10-21 12:01:03
61 water 2019-10-21 12:01:03
71 water 2019-10-21 15:01:04
81 water 2019-10-22 16:01:03
81 sea 2019-10-22 16:01:03
81 sea 2019-10-24 16:01:03
I want to achive output like the following
type 2019-10-21 | 2019-10-22
air 5 0
water 2 1
sea 0 1
and also give range in between 2019-10-21 and 2019-10-21
i have tried this query
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'count(case when type = ',
'type',
' then type else null end) AS ''',
date(date), ''''
)
) INTO #sql
FROM content where date(`date`) between '2021-02-08' AND '2021-02-10';
SET #sql = CONCAT('SELECT type, ', #sql, '
FROM content
GROUP BY type limit 2');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
but it give wrong results and counts the total
type 2021-02-08 2021-02-09 2021-02-10
sea 3 3 3
air 5316 5316 5316

Related

LEFT JOIN 11 times in the same table [duplicate]

Question 1: I have a table with the below structure and data:
app_id transaction_id mobile_no node_id customer_attribute entered_value
100 111 9999999999 1 Q1 2
100 111 9999999999 2 Q2 1
100 111 9999999999 3 Q3 4
100 111 9999999999 4 Q4 3
100 111 9999999999 5 Q5 2
100 222 8888888888 4 Q4 1
100 222 8888888888 3 Q3 2
100 222 8888888888 2 Q2 1
100 222 8888888888 1 Q1 3
100 222 8888888888 5 Q5 4
I want to display these records in the below format:
app_id | transaction_id | mobile | Q1 | Q2 | Q3 | Q4 | Q5 |
100 | 111 | 9999999999 | 2 | 1 | 4 | 3 | 2 |
100 | 222 | 8888888888 | 3 | 1 | 2 | 1 | 4 |
I know I need to use crosstab/pivot query to get this display. For this I tried it based on the limited knowledge that I have about it. Following is my query:
SELECT app_id, transaction_id, mobile_no,
(CASE node_id WHEN 1 THEN entered_value ELSE '' END) AS user_input1,
(CASE node_id WHEN 2 THEN entered_value ELSE '' END) AS user_input2,
(CASE node_id WHEN 3 THEN entered_value ELSE '' END) AS user_input3,
(CASE node_id WHEN 4 THEN entered_value ELSE '' END) AS user_input4,
(CASE node_id WHEN 5 THEN entered_value ELSE '' END) AS user_input5
FROM trn_user_log
GROUP BY app_id, transaction_id, mobile_no, node_id
And based on this query I got the below display:
app_id transaction_id mobile_no user_input1 user_input2 user_input3 user_input4 user_input5
100 111 9999999999 2
100 111 9999999999 1
100 111 9999999999 4
100 111 9999999999 3
100 111 9999999999 2
100 222 8888888888 3
100 222 8888888888 1
100 222 8888888888 2
100 222 8888888888 1
100 222 8888888888 4
Can anyone help me with the proper changes that I need to make to my query to get the records in one single row and not multiple rows as above.
Question 2: Also is there a way to get the value of a particular field as the NAME of the column. As you can see above I have user_input1, user_input2,... as the header. Instead of that I want to have the values in customer_attribute as the header of the columns.
For this I checked NAME_CONST(name,value) as below:
SELECT app_id, transaction_id, mobile_no,
NAME_CONST(customer_attribute, (CASE node_id WHEN 1 THEN entered_value ELSE '' END))
FROM trn_user_log
But it gives an error
Error Code : 1210 Incorrect arguments to NAME_CONST
Help required.
While #John's static answer works great, if you have an unknown number of columns that you want to transform, I would consider using prepared statements to get the results:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'GROUP_CONCAT((CASE node_id when ',
node_id,
' then entered_value else NULL END)) AS user_input',
node_id
)
) INTO #sql
FROM trn_user_log;
SET #sql = CONCAT('SELECT app_id, transaction_id, mobile_no, ', #sql, '
FROM trn_user_log
GROUP BY app_id, transaction_id, mobile_no');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
see SQL Fiddle with Demo
As far as your second, please clarify what you are trying to do it is not clear.
Add GROUP_CONCAT in your CASE clause
SELECT app_id, transaction_id, mobile_no,
GROUP_CONCAT((CASE node_id WHEN 1 THEN entered_value ELSE NULL END)) AS user_input1,
GROUP_CONCAT((CASE node_id WHEN 2 THEN entered_value ELSE NULL END)) AS user_input2,
GROUP_CONCAT((CASE node_id WHEN 3 THEN entered_value ELSE NULL END)) AS user_input3,
GROUP_CONCAT((CASE node_id WHEN 4 THEN entered_value ELSE NULL END)) AS user_input4,
GROUP_CONCAT((CASE node_id WHEN 5 THEN entered_value ELSE NULL END)) AS user_input5
FROM trn_user_log
GROUP BY app_id, transaction_id, mobile_no
SQLFiddle Demo
#DarkKnightFan, this was a very helpful question for a task I was working. I went ahead and modified the solution from #bluefin to solve your second question. The following code produces your originally requested format with the value of customer_attribute as the resulting column headings in the cross-tab.
The relevant change was to change:
' then entered_value else NULL END)) AS user_input',
node_id
To this:
' then entered_value else NULL END)) AS ''',
customer_attribute,''''
The full code:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'GROUP_CONCAT((CASE node_id when ',
node_id,
' then entered_value else NULL END)) AS ''',
customer_attribute,''''
)
) INTO #sql
FROM trn_user_log;
SET #sql = CONCAT('SELECT app_id, transaction_id, mobile_no, ', #sql, '
FROM trn_user_log
GROUP BY app_id, transaction_id, mobile_no');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Also, for other users browsing this issue, if you have a lot of values that you are trying to cross-tab, you may run into error because GROUP_CONCAT() has a default max length of 1024 characters. To increase put this at the start of your prepared statement:
SET SESSION group_concat_max_len = value; -- replace value with an int

Tables with dynamic names as fields

I have a table which contains a field that when we add a letter to it it becomes a table name, so far i can only fetch all from the first table and then do each table_name at once. So i'm looking for a way to all of the above in one query.
Queries
select table_name from main
select date,data from X$table_name order by date desc limit 1
main
table_name name
000001 X1
000002 X2
000003 X3
000004 X4
000005 X5
Table X000001
id date data
5 2016-12-05 data_02_05
4 2016-12-04 data_02_04
3 2016-12-03 data_02_03
2 2016-12-02 data_02_02
1 2016-12-01 data_02_01
Table X000002
id date data
5 2016-12-05 data_02_05
4 2016-12-04 data_02_04
3 2016-12-03 data_02_03
2 2016-12-02 data_02_02
1 2016-12-01 data_02_01
Expected result
table_name date data
000001 2016-12-05 data_01_05
000002 2016-12-05 data_02_05
000003 2016-12-05 data_03_05
000004 2016-12-05 data_04_05
000005 2016-12-05 data_05_05
you need to use a dynamic query or prepared statement for that purpose.
declare #tab_name varchar(100);
set #tab_name := (select table_name from main);
SET #t1 := CONCAT("select `date`,data from X",#tab_name," order by `date` desc limit 1");
PREPARE stmt3 FROM #t1;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;

MySQL pivot row to column dynamically

i have table:day
id | jour
1 Lundi 01 Août 2016
2 Mardi 02 Août 2016
3 Mercredi 03 Août 2016
4 Jeudi 04 Août 2016
there will be 50 rows i want output like:
Lundi 01 Août 2016 Mardi 02 Août 2016 Mercredi 03 Août 2016
1 2 3
you can do it easy with this queries: first generate the query and then execute it as prepared statement.
SET SESSION group_concat_max_len = 1000000;
SELECT
CONCAT('SELECT ',
GROUP_CONCAT(
CONCAT (
"MAX(IF(`jour` = '",jour,'\',id,NULL)) AS `',jour,'`'
)
),
' FROM `day`'
) INTO #SQL
FROM
( SELECT *
FROM `day`
ORDER BY id
LIMIT 1,2
) AS data;
select #SQL; -- see the query only for debug
PREPARE stmt FROM #SQL;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
sample
MariaDB [yourSchema]> select * from day;
+----+------------------------+
| id | jour |
+----+------------------------+
| 1 | Lundi 01 Août 2016 |
| 2 | Mardi 02 Août 2016 |
| 3 | Mercredi 03 Août 2016 |
| 4 | Jeudi 04 Août 2016 |
+----+------------------------+
4 rows in set (0.00 sec)
MariaDB [yourSchema]> SELECT
-> CONCAT('SELECT ',
-> GROUP_CONCAT(
-> CONCAT (
-> "MAX(IF(`jour` = '",jour,'\',id,NULL)) AS `',jour,'`'
-> )
-> ),
-> ' FROM `day`'
-> ) INTO #SQL
-> FROM `day`;
Query OK, 1 row affected (0.00 sec)
MariaDB [yourSchema]> PREPARE stmt FROM #SQL;
Query OK, 0 rows affected (0.00 sec)
Statement prepared
MariaDB [yourSchema]> EXECUTE stmt;
+---------------------+---------------------+------------------------+---------------------+
| Lundi 01 Août 2016 | Mardi 02 Août 2016 | Mercredi 03 Août 2016 | Jeudi 04 Août 2016 |
+---------------------+---------------------+------------------------+---------------------+
| 1 | 2 | 3 | 4 |
+---------------------+---------------------+------------------------+---------------------+
1 row in set (0.00 sec)
MariaDB [yourSchema]> DEALLOCATE PREPARE stmt;
Query OK, 0 rows affected (0.00 sec)
MariaDB [yourSchema]>

MySQL - Combining multiple selects from same table into one result table with a group by

I have a table of meter readings which holds a meterNo, readingValue and readingDate.
I want to show the total sum of readingValue per meterNo per year.
For example:
SELECT meterNo, SUM(readingValue) '2009' FROM readings
WHERE readingDate >= '2009-01-01' AND readingDate <= '2009-12-31'
GROUP BY meterNo;
Will display the table:
meterNo 2009
--------------
4 50
5 5
12 30
13 63
18 18
26 484
27 21
28 510
29 28
Which is what I want, it shows the total reading value for each meter in 2009. However, I need the result table to display a column for each year. So it would be something like this:
meterNo 2009 2010 2011 2012
--------------------------------
1 20 35 50
2 45 35
3 50 60
4 50 35 20 30
5 5 10 10 15
6 30
And so on...
I can reuse the where part of the query:
WHERE readingDate >= '2009-01-01' AND readingDate <= '2009-12-31'
And just change the date for each year, butt how do I display each WHERE result as its own column in the one result table?
MySQL does not have a PIVOT function but you can convert the rows of data into columns using an aggregate function with a CASE expression.
If you have a limited number of years, then you can hard-code the query:
select meterNo,
sum(case when year(readingDate) = 2009 then readingValue else 0 end) `2009`,
sum(case when year(readingDate) = 2010 then readingValue else 0 end) `2010`,
sum(case when year(readingDate) = 2011 then readingValue else 0 end) `2011`,
sum(case when year(readingDate) = 2012 then readingValue else 0 end) `2012`,
sum(case when year(readingDate) = 2013 then readingValue else 0 end) `2013`
from readings
group by meterno;
See SQL Fiddle with Demo
But if you are going to have an unknown number of values or what the query to adjust as new years are added to the database, then you can use a prepared statement to generate dynamic SQL:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'sum(CASE WHEN year(readingDate) = ',
year(readingDate),
' THEN readingValue else 0 END) AS `',
year(readingDate), '`'
)
) INTO #sql
FROM readings;
SET #sql
= CONCAT('SELECT meterno, ', #sql, '
from readings
group by meterno');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo. Both give the result:
| METERNO | 2009 | 2010 | 2012 | 2013 | 2011 |
----------------------------------------------
| 1 | 90 | 180 | 0 | 90 | 90 |
| 2 | 50 | 0 | 90 | 0 | 0 |
| 3 | 80 | 40 | 90 | 90 | 0 |
As a side note, if you want null to display in the rows without values instead of the zeros, then you can remove the else 0 (see Demo)
You can use CASE WHEN to do this. It allows you to sum multiple values based on multiple conditions. In your example, you could do something like this (I've simplified my example because I don't have access to your actual data):
SELECT meterNo,
sum(CASE WHEN readingDate = 2009 THEN readingValue END) as '2009',
sum(CASE WHEN readingDate = 2010 THEN readingValue END) as '2010',
sum(CASE WHEN readingDate = 2011 THEN readingValue END) as '2011',
sum(CASE WHEN readingDate = 2012 THEN readingValue END) as '2012'
FROM readings
GROUP BY meterNo
You need to switch up the conditions with your own--sum(CASE WHEN readingDate >= '2009-01-01' AND readingDate <= '2009-12-31' THEN readingValue END), etc--but it should work. Here's a working example that I did on sqlFiddle: http://sqlfiddle.com/#!2/6990e/29. Output is five columns with sums of each meterNo.
You could do something like this:
SELECT
meterNo, SUM(readingValue) AS `total`, YEAR(readingDate)
FROM
readings
GROUP BY
meterNo, YEAR(readingDate);
result:
METERNO TOTAL YEAR(READINGDATE)
1 90 2009
1 90 2010
2 50 2009
3 80 2009
3 40 2010
Also note how you can use YEAR function instead of the where clause you have.
SEE FIDDLE

mysql pivot/crosstab query

Question 1: I have a table with the below structure and data:
app_id transaction_id mobile_no node_id customer_attribute entered_value
100 111 9999999999 1 Q1 2
100 111 9999999999 2 Q2 1
100 111 9999999999 3 Q3 4
100 111 9999999999 4 Q4 3
100 111 9999999999 5 Q5 2
100 222 8888888888 4 Q4 1
100 222 8888888888 3 Q3 2
100 222 8888888888 2 Q2 1
100 222 8888888888 1 Q1 3
100 222 8888888888 5 Q5 4
I want to display these records in the below format:
app_id | transaction_id | mobile | Q1 | Q2 | Q3 | Q4 | Q5 |
100 | 111 | 9999999999 | 2 | 1 | 4 | 3 | 2 |
100 | 222 | 8888888888 | 3 | 1 | 2 | 1 | 4 |
I know I need to use crosstab/pivot query to get this display. For this I tried it based on the limited knowledge that I have about it. Following is my query:
SELECT app_id, transaction_id, mobile_no,
(CASE node_id WHEN 1 THEN entered_value ELSE '' END) AS user_input1,
(CASE node_id WHEN 2 THEN entered_value ELSE '' END) AS user_input2,
(CASE node_id WHEN 3 THEN entered_value ELSE '' END) AS user_input3,
(CASE node_id WHEN 4 THEN entered_value ELSE '' END) AS user_input4,
(CASE node_id WHEN 5 THEN entered_value ELSE '' END) AS user_input5
FROM trn_user_log
GROUP BY app_id, transaction_id, mobile_no, node_id
And based on this query I got the below display:
app_id transaction_id mobile_no user_input1 user_input2 user_input3 user_input4 user_input5
100 111 9999999999 2
100 111 9999999999 1
100 111 9999999999 4
100 111 9999999999 3
100 111 9999999999 2
100 222 8888888888 3
100 222 8888888888 1
100 222 8888888888 2
100 222 8888888888 1
100 222 8888888888 4
Can anyone help me with the proper changes that I need to make to my query to get the records in one single row and not multiple rows as above.
Question 2: Also is there a way to get the value of a particular field as the NAME of the column. As you can see above I have user_input1, user_input2,... as the header. Instead of that I want to have the values in customer_attribute as the header of the columns.
For this I checked NAME_CONST(name,value) as below:
SELECT app_id, transaction_id, mobile_no,
NAME_CONST(customer_attribute, (CASE node_id WHEN 1 THEN entered_value ELSE '' END))
FROM trn_user_log
But it gives an error
Error Code : 1210 Incorrect arguments to NAME_CONST
Help required.
While #John's static answer works great, if you have an unknown number of columns that you want to transform, I would consider using prepared statements to get the results:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'GROUP_CONCAT((CASE node_id when ',
node_id,
' then entered_value else NULL END)) AS user_input',
node_id
)
) INTO #sql
FROM trn_user_log;
SET #sql = CONCAT('SELECT app_id, transaction_id, mobile_no, ', #sql, '
FROM trn_user_log
GROUP BY app_id, transaction_id, mobile_no');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
see SQL Fiddle with Demo
As far as your second, please clarify what you are trying to do it is not clear.
Add GROUP_CONCAT in your CASE clause
SELECT app_id, transaction_id, mobile_no,
GROUP_CONCAT((CASE node_id WHEN 1 THEN entered_value ELSE NULL END)) AS user_input1,
GROUP_CONCAT((CASE node_id WHEN 2 THEN entered_value ELSE NULL END)) AS user_input2,
GROUP_CONCAT((CASE node_id WHEN 3 THEN entered_value ELSE NULL END)) AS user_input3,
GROUP_CONCAT((CASE node_id WHEN 4 THEN entered_value ELSE NULL END)) AS user_input4,
GROUP_CONCAT((CASE node_id WHEN 5 THEN entered_value ELSE NULL END)) AS user_input5
FROM trn_user_log
GROUP BY app_id, transaction_id, mobile_no
SQLFiddle Demo
#DarkKnightFan, this was a very helpful question for a task I was working. I went ahead and modified the solution from #bluefin to solve your second question. The following code produces your originally requested format with the value of customer_attribute as the resulting column headings in the cross-tab.
The relevant change was to change:
' then entered_value else NULL END)) AS user_input',
node_id
To this:
' then entered_value else NULL END)) AS ''',
customer_attribute,''''
The full code:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'GROUP_CONCAT((CASE node_id when ',
node_id,
' then entered_value else NULL END)) AS ''',
customer_attribute,''''
)
) INTO #sql
FROM trn_user_log;
SET #sql = CONCAT('SELECT app_id, transaction_id, mobile_no, ', #sql, '
FROM trn_user_log
GROUP BY app_id, transaction_id, mobile_no');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Also, for other users browsing this issue, if you have a lot of values that you are trying to cross-tab, you may run into error because GROUP_CONCAT() has a default max length of 1024 characters. To increase put this at the start of your prepared statement:
SET SESSION group_concat_max_len = value; -- replace value with an int