Dynamic pivot query using MySQL - mysql

I have the following table with the details:
Example:
CREATE TABLE Table1
(`PK` int, `Name` varchar(3), `Subject` varchar(9), `Grade` varchar(1));
INSERT INTO Table1
(`PK`, `Name`, `Subject`, `Grade`)
VALUES
(1, 'Bob', 'Math', 'A'),
(2, 'Bob', 'History', 'B'),
(3, 'Bob', 'Language', 'C'),
(4, 'Bob', 'Biology', 'D'),
(5, 'Sue', 'History', 'C'),
(6, 'Sue', 'Math', 'A'),
(7, 'Sue', 'Music', 'A'),
(8, 'Sue', 'Geography', 'C');
Now I want to write a store procedure through which I want to pivot the table.
Attempt
DELIMITER $$
create PROCEDURE sptest1(IN nm varchar(50),IN sub varchar(50))
begin
SET #sql = NULL;
SELECT GROUP_CONCAT(DISTINCT
CONCAT('MAX(CASE WHEN ', nm ,' = ''', nm,
''' THEN grade END) `', nm, '`'))
INTO #sql
FROM table1;
SET #sql = CONCAT('SELECT ', sub ,',', #sql, '
FROM table1
GROUP BY ', sub ,'');
select #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
Calling Function:
call sptest1('name','subject')
But getting wrong result:
subject name
----------------
Biology null
Geography null
History null
Language null
Math null
Music null
Expected Result:
subject Bob Sue
-------------------------
Biology D null
Geography null C
History B C
Language C null
Math A A
Music null A

You can rewrite you dynamic sql query as
SET #sql = NULL;
SELECT GROUP_CONCAT( DISTINCT
CONCAT('MAX(CASE WHEN `Name` = ''',
`Name`,
''' THEN grade END) ',
`Name`)
)
INTO #sql
FROM table1;
SET #sql = CONCAT('SELECT `Subject`, ', #sql, ' FROM table1 GROUP BY `Subject`');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Demo
In your stored procedure you have to hard code your name column for you dynamic sql if you pass it as parameter then you can use case to check for column, for example
MAX(CASE WHEN ', nm ,' = ''', nm, ..
Above part of procedure will evaluated as MAX(CASE WHEN name = 'name' .. because variable nm contains 'name' it will not recognize it as column
DROP PROCEDURE `sptest1`;
DELIMITER $$
CREATE
PROCEDURE `sib`.`sptest1`(IN nm VARCHAR(50),IN sub VARCHAR(50))
BEGIN
SET #sql = NULL;
SELECT GROUP_CONCAT( DISTINCT
CONCAT('MAX(CASE WHEN ',nm,' = ''',CASE WHEN nm = 'name' THEN `Name` END,''' THEN grade END) ',CASE WHEN nm = 'name' THEN `Name` END)
)
INTO #sql
FROM table1;
SET #sql = CONCAT('SELECT ', sub ,',', #sql, ' FROM table1 GROUP BY ', sub ,'');
/*select #sql; */
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
Demo

Related

Dynamically transpose rows into columns in MySQL

I have a simple entity attribut value table with 3 columns 1) policyholder_locator; 2) field_name; 3) field_value. Sample data as follows:
policyholder_locator,field_name,field_value
1,first_name,john
1,phone,8888888888
1,email,john#gmail.com
2,first_name,jane
2,phone,7777777777
2,email,jane#gmail.com
3,first_name,joe
3,phone,6666666666
3,email,joe#gmail.com
I would ideally like to use the following GROUP_CONCAT and prepared statements code, but I keep getting the following error when running "An SQLException was provoked by the following failure: java.lang.ArrayIndexOutOfBoundsException".
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(CASE WHEN field_name = ''',
field_name,
''' THEN field_value END) ',
field_name
)
) INTO #sql
FROM policyholder_fields;
SET #sql = CONCAT('SELECT policyholder_locator, ',#sql, '
FROM policyholder_fields
GROUP BY policyholder_locator');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
I could go with code that's not dynamic like below, but would prefer a dynamic option.
SELECT policyholder_locator,
GROUP_CONCAT(IF(field_name = 'phone', field_value, NULL)) AS phone,
GROUP_CONCAT(IF(field_name = 'email', field_value, NULL)) AS email,
GROUP_CONCAT(IF(field_name = 'first_name', field_value, NULL)) AS first_name
FROM policyholder_fields
GROUP BY policyholder_locator
Is there an obvious issue with my prepared statements?

return null in mysql procedure with dynamic query

can someone please tell me what I am missing I have invested 4 hours and watch lots of article can't figure out why is showing return null
mysql mysql procedure is as follows.
create procedure proc_studymaterial_listing(
get_exam_id int(11)
)
begin
set #query1 = concat( "select c.content_type,
c.title,
c.status,
c.created_on,
c.pdf_is_downloadable,
a.content_id,
a.expiry_date,
exam_Name,
fun_subject_name(a.subject_id) as subject_name,
fun_subject_name(a.chapter_id) as chapter_name
from tbl_studymaterial_exam_chapter_map as a
join tbl_exam_master as b on a.exam_id= b.exam_ID
join tbl_studymaterial as c on c.id= a.content_id
where c.status=1 " );
if( get_exam_id = 0 )
then
set #exam_id = " and 1=1 ";
else
set #exam_id = concat( ' and b.exam_ID = " ', #get_exam_id , '"' );
end if;
SET #final_query = CONCAT ( #query1,#exam_id );
PREPARE stmt FROM #final_query ;
# select stmt;
# EXECUTE stmt;
# DEALLOCATE PREPARE stmt;
end;
User-defined variable #get_exam_id doesn't appear to be assigned a value anywhere. If that's not assigned a non-NULL value, then the result of evaluating this line:
set #exam_id = concat( ' and b.exam_ID = " ', #get_exam_id , '"' );
is that #exam_id will be NULL. And that will cascade into the next CONCAT, which will also evaluate to NULL.
User-defined variables start with an # at sign character.
Those are distinct and separate from procedure arguments and variables, which do not start with an # at sign character.
Looks like maybe the intent was to reference the local variable get_exam_id, not a user-defined variable.
FOLLOWUP
Personally, if I had to write the body of the procedure, I'd do it something like this:
DELIMITER $$
CREATE PROCEDURE proc_studymaterial_listing( get_exam_id INT(11))
BEGIN
SET #sql = CONCAT( 'SELECT c.content_type'
,'\n' ,' , c.title'
,'\n' ,' , c.status'
,'\n' ,' , c.created_on'
,'\n' ,' , c.pdf_is_downloadable'
,'\n' ,' , a.content_id'
,'\n' ,' , a.expiry_date'
,'\n' ,' , b.exam_name'
,'\n' ,' , fun_subject_name(a.subject_id) AS subject_name'
,'\n' ,' , fun_subject_name(a.chapter_id) AS chapter_name'
,'\n' ,' FROM tbl_studymaterial_exam_chapter_map a'
,'\n' ,' JOIN tbl_exam_master b'
,'\n' ,' ON b.exam_id = a.exam_id'
,'\n' ,' JOIN tbl_studymaterial c'
,'\n' ,' ON c.id = a.content_id'
,'\n' ,' WHERE c.status = 1'
,'\n'
,CASE
WHEN get_exam_id <> 0
THEN ' AND b.exam_id = ? '
ELSE ''
END
,'\n' ,' ORDER BY subject_name, chapter_name, exam_name'
);
PREPARE stmt FROM #sql;
IF (get_exam_id <> 0) THEN
SET #id = get_exam_id;
EXECUTE stmt USING #id;
SET #id = '';
ELSE
EXECUTE stmt;
END IF;
DEALLOCATE PREPARE stmt;
SET #sql = '';
END;
$$
DELIMITER ;

Dynamically count values in column, grouped by months

I'm trying to get a single mySQL query that returns the count of unique values, grouped by months.
I have a table created based on data similar to this:
CREATE TABLE `animals` (
`timestamp` datetime NOT NULL,
`animal` tinytext NOT NULL,
`comment` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `animals` (`timestamp`, `animal`, `comment`) VALUES
('2019-06-03 09:09:00', 'dog', 'good'),
('2019-06-03 12:00:00', 'cat', ''),
('2019-06-03 19:00:00', 'cat', ''),
('2019-07-04 09:00:00', 'cat', ''),
('2019-07-04 12:00:00', 'cat', 'feisty'),
('2019-07-04 18:51:00', 'dog', ''),
('2019-08-05 09:00:00', 'cat', ''),
('2019-08-05 12:00:00', 'cat', ''),
('2019-08-05 19:00:00', 'cat', ''),
('2019-09-06 09:00:00', 'cat', ' evil'),
('2019-09-06 12:00:00', 'cat', ''),
('2019-09-06 19:00:00', 'cat', '')
I've managed to write a query that at least gives me the count per month (as long as it is more than zero), but the query just returns the count for "cat", "dog" or anything I explicitly ask for.
My goal is to get a response similar to the following:
month | dog | cat
-------------------
2019-06 | 1 | 2
2019-07 | 1 | 2
2019-08 | 0 | 3
2019-09 | 0 | 3
How do I writhe such a query?
Is it possible to write a query that automatically counts any new values in the animal column too?
Thanks
You can use the following code, to get flexible columns from the animal column
, that does the counting for you.
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'Sum(`animal` = ''',
col,
''') as `',
col, '`')
) INTO #sql
FROM
(
select animal col
from animals
)d;
SET #sql = CONCAT('SELECT date_format(`timestamp`, "%Y-%m") `month`, ', #sql, '
from `animals`
group by `month`
order by `month`');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Se dbfiddle example https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=09d0f26087d66452fde1a22e91de7b3a
You can have
SELECT date_format(`timestamp`, '%Y-%m') AS month, animal, COUNT(*) as count
FROM animals
GROUP BY 1, 2
but this won't give you dynamically more columns. For more columns, I guess you need to build a dynamic SQL command looping over the distinct animals you have. If you really need this, you should consider to build that gives you the SQL string or directly the result.
You want conditional aggregation:
select
date_format(`timestamp`, '%Y-%m') `month`,
sum(`animal` = 'dog') dog,
sum(`animal` = 'cat') cat
from `animals`
group by `month`
order by `month`
Demo on DB Fiddle:
month | dog | cat
:------ | --: | --:
2019-06 | 1 | 2
2019-07 | 1 | 2
2019-08 | 0 | 3
2019-09 | 0 | 3
If you want to handle dynamically the column list, then you have to go for dynamic sql:
set #sql = null;
select
concat(
'select ',
'date_format(`timestamp`, ''%Y-%m'') `month`, ',
group_concat(
concat(
'sum(`animal` = ''',
`animal`,
''') ',
`animal`
)
order by `animal`
separator ', '
),
' from `animals` ',
'group by `month` '
'order by `month`'
)
into #sql
from (
select distinct `animal` from `animals`
) t;
select #sql;
prepare stmt from #sql;
execute stmt;
deallocate prepare stmt;

Mysql query to pull data in the form of matrix

I have data in database with three columns id, topic, and subtopic. Something like this,
CREATE TABLE Table2 (`id` int, `topic` varchar(5), `subtopic` varchar(6));
INSERT INTO Table2 (`id`, `topic`, `subtopic`) VALUES
(1, 'place', 'paris'),
(1, 'group', 'A'),
(1, 'group', 'B'),
(2, 'place', 'us'),
(2, 'group', 'C'),
(3, 'group', 'A'),
(3, 'water', 'salt'),
(4, 'water', 'sweet'),
(4, 'world', 'ep'),
(5, 'place', 'venus'),
(5, 'place', 'paris'),
(5, 'group', 'A');
I want to output the result matrix place vs group from topic. Something like this.
Paris|US|Venus
A 2 |0 | 1
B 1 |0 | 0
C 0 |1 | 0
Idea is to pick up all the values of 'Group' (A,B,C) and 'Places' (paris,us,venus) in subtopic column. And then find number of co-occurrences with such condition.
Any idea how to solve in MySql?
You will need to join on your table twice to get the group and places, then you can use an aggregate function with a CASE expression to convert the rows into columns:
select g.subtopic as `group`,
sum(case when p.subtopic = 'Paris' then 1 else 0 end) Paris,
sum(case when p.subtopic = 'US' then 1 else 0 end) US,
sum(case when p.subtopic = 'Venus' then 1 else 0 end) Venus
from table2 g
left join table2 p
on g.id = p.id
and p.topic = 'place'
where g.topic = 'group'
group by g.subtopic;
See SQL Fiddle with Demo.
If you are going to have unknown values for the subtopic, then you can use a prepared statement and dynamic SQL:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'sum(case when p.subtopic = ''',
subtopic,
''' then 1 else 0 end) as `',
subtopic, '`')
) INTO #sql
FROM table2
where topic = 'place';
SET #sql = CONCAT('SELECT g.subtopic as `group`, ', #sql, '
from table2 g
left join table2 p
on g.id = p.id
and p.topic = ''place''
where g.topic = ''group''
group by g.subtopic');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo

How to get the output in mysql the way i need is give below

i have one table with a single column having three values a,b and c.
Current snapshot of the table is
Table name:- tblTest
Values
tblColumn
a
a
a
b
b
b
b
c
c
i need to get the output exactly as
A B C
3 4 2
select sum(tblColumn = 'a') as A,
sum(tblColumn = 'b') as B,
sum(tblColumn = 'b') as C
from tblTest
SQL Fiddle Example
SELECT SUM(CASE WHEN colName = 'a' THEN 1 ELSE 0 END) as A,
SUM(CASE WHEN colName = 'b' THEN 1 ELSE 0 END) as B,
SUM(CASE WHEN colName = 'c' THEN 1 ELSE 0 END) as C
FROM tableName
another technique is by using PreparedStatement, this is very good if you have multiple unknown number group of values, eg, a,b,c,d,e,f,g,h,...
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'SUM(IF(tblColumn = ''',
tblColumn,
''', 1, 0)) AS ',
tblColumn
)
) INTO #sql
FROM
Table1;
SET #sql = CONCAT('SELECT ', #sql, ' FROM Table1');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SQLFiddle Demo
Try this
select tblColumn, Count(*) from tblTest group by tblColumn