Mysql transpose complex sql query table dynamically - mysql

I have a table with two columns, following is the schema:
create table scan( `application_name` varchar(255) NOT NULL, `defect_type` varchar(255) NOT NULL);
And the data is populated accordingly. The table stores data for "Application" and its corresponding "Defect Type". I want to perform following 2 actions on this table:
Get the Top 3 "Defect Type" of a particular application in terms of percent.
Transpose the output from above where the values in "Defect Type" (defect_type) become the columns and its corresponding percent (finalPercent) as the its value.
I am able to achieve 1, following is the SQL Fiddle:
SQLFiddle
However, I am not able to transpose the output as per the requirement. Ideally there should be a single row as follows after both 1 & 2 together:
application_name | CrossSide | CSS | XML
A | 33.33 | 33.33 | 16.67
Thank you!

You can build a dynamic pivot query with group_concat and then execute it:
set #sql = null;
set #total = (select count(*) as totalcount from scan where application_name = "a");
select group_concat(distinct
concat(
'round(((count(case when defect_type = ''',
defect_type,
''' then application_name end)/ #total ) * 100 ), 2) as `',
defect_type, '`'
)
) into #sql
from ( select defect_type
from scan
where application_name = "a"
group by defect_type
order by count(defect_type) desc
limit 3) t;
set #sql = concat(
'select application_name, ', #sql,
' from scan
where application_name = "a"
group by application_name'
);
prepare stmt from #sql;
execute stmt;
deallocate prepare stmt;
SQLFiddle

Related

How to pivot columns to rows in MySQL for N number of columns without using Union all

I already went through the details in the link (Mysql Convert Column to row (Pivot table )). As the number of Columns is high and using union all on all of them would be time taking. I decided to use the last resolution in the given link. I was able to run the query the results were:
The issue is the acct getting included as data and also I want to create a table from the result . So Can these entries be excluded and how can I create a table from the results? (new to SQL)
The Code:
SET SESSION group_concat_max_len = 92160;
SET #target_schema='rd';
SET #target_table='pbc_gl';
SET #target_where='`acct`';
SELECT
GROUP_CONCAT(qry SEPARATOR ' UNION ALL ')
INTO #sql
FROM (
SELECT
CONCAT('SELECT `acct`,', QUOTE(COLUMN_NAME), ' AS `Business_Unit`,`', COLUMN_NAME, '` AS `value` FROM `', #target_table, '` WHERE ', #target_where) qry
FROM (
SELECT `COLUMN_NAME`
FROM `INFORMATION_SCHEMA`.`COLUMNS`
WHERE `TABLE_SCHEMA`=#target_schema
AND `TABLE_NAME`=#target_table
) AS `A`
) AS `B` ;
PREPARE s FROM #sql;
EXECUTE s;
DEALLOCATE PREPARE s;

SQL/MYSQL /Transpose table / Columns into rows and rows sum as column with new header

i have a table like this
A B C
1 4 7
2 5 8
3 6 9
And i want result like this
Columns Values
A sum(A) = 6
B sum(B) = 15
C sum(C) = 24
Its simple in Excel sheets but im stuck in MySql
Appreciate the help
Thanks
-- SeasonType,Sacks,SacksYards are columns
select SeasonType,
MAX(IF(SeasonType = '1', Sacks, null)) AS 'Q1',
MAX (IF(SeasonType = '1', SacksYards, null)) AS 'Q2'
from t3 GROUP BY SeasonType
-- union all attempt column sacks,sacksyards table --
-- fantasydefencegame
select 'Sacks' as Sacks, 'SackYards' as SackYards, 0 as SortOrder
union all
select Sum(Sacks) total from fantasydefensegame
union
select Sum(SackYards) from fantasydefensegame
union
select sum(PointsAllowed) from fantasydefensegame
group by SeasonType
select sum(Sacks) sacks from t3
union all
select sum(SackYards) sackyards from t3 group by SeasonType
**-- Another rough Attempt on Temp table**
Select sum(Sacks),sum(Sackyards) from t5
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(case when Season = '2009' ''',
Season,
''' then field_value end) ',
Season
)
) INTO #sql
FROM
t5;
SET #sql = CONCAT('SELECT Sacks, ', #sql, '
FROM t5
GROUP BY Season');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
This should fairly give you some ideas. Supposing we are using a test database named testdb and your original table is named test which has 3 columns i.e a,b,c . The three rows in the table are just like what you provided before. Next we can proceed to create a stored procedure. Note: The reason behind using a prepared statement to get the sum value for each column is due to the rules that column names have to be hardcoded , which can not be replaced with variables. e.g select sum(a) from test; can not be written as select sum(#column_name) from test;. By using a prepared statement, we can hardcode the column name dynamically.
delimiter //
drop procedure if exists table_sum//
create procedure table_sum (db_name varchar(20),tb_name varchar(20))
begin
declare col_name varchar(10);
declare fin bool default false;
declare c cursor for select column_name from information_schema.columns where table_schema=db_name and table_name=tb_name;
declare continue handler for not found set fin=true;
drop temporary table if exists result_tb;
create temporary table result_tb (`Columns` varchar(10),`Values` varchar(25));
open c;
lp:loop
fetch c into col_name;
if fin=true then
leave lp;
end if;
set #stmt=concat('select sum(',col_name,') into #sum from test ;');
prepare stmt from #stmt;
execute stmt;
deallocate prepare stmt;
set #val=concat('sum(',col_name,') = ',#sum);
insert result_tb values(col_name,#val);
end loop lp;
close c;
select * from result_tb;
end//
delimiter ;
Finally we call the procedure to get the desired output:
call table_sum('testdb','test');
I would avoid prepared statements and dynamic sql unless I really need it. And I would use such powerful tool when I need to generalize on a value that increases, on a large set of columns.
In your specific case of the shared columns, you could use a simple approach that does the union of the three columns with their respective sum.
SELECT 'A' AS `Columns`,
SUM(A) AS `Values`
FROM tab
UNION
SELECT 'B' AS `Columns`,
SUM(B) AS `Values`
FROM tab
UNION
SELECT 'C' AS `Columns`,
SUM(C) AS `Values`
FROM tab
Check the demo here.

Error on dynamic column headers with mySQL

This question in regarding mySQL.
I am reading data from an .csv file that looks like this
original table
------------------------------------
Id 2018M01 2018M02 2018M03
------------------------------------
EMEA 3 1 4
ASPAC 4 5 4
ASPAC 1 2 1
expected result
---------------------
ID Month Qty
---------------------
EMEA 2018M01 3
EMEA 2018M02 1
EMEA 2018M03 4
ASPAC 2018M01 4
.......
The months column header are dynamic, that is each month there will be new months and old months will be removed. However the total number of columns will remain the same.
Hence whenever the month columns headers change I would like the SQL code to dynamically read and provide correct results without me having to manually change several parts of the code.
I have written the following code; Code is to unpivot the month columns. However I tested the code by manually making changes to the .csv file headers by changing 2018M03 to 2018M04, and rerunning the SQL code, but it still seems to print the old data . What am I doing wrong ?
Thank you. I am fairly new to SQL.
DROP TABLE IF EXISTS book;
CREATE TABLE book (
ID VARCHARACTER(10),
2018M01 decimal(4,2),
2018M02 decimal(4,2),
2018M03 decimal(4,2)
);
LOAD DATA LOCAL INFILE '/Users/blytonpereira/Desktop/Book1.csv' REPLACE INTO TABLE book
FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' IGNORE 1 LINES;
DESCRIBE book;
SELECT ID, '2018M01' AS month, 2018M01 AS qty from book
UNION ALL
SELECT ID, '2018M02' AS month, 2018M02 AS qty from book
UNION ALL
SELECT ID, '2018M03' AS month, 2018M03 AS qty from book;
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'select ID, ''',
c.column_name,
''' AS month, ',
c.column_name,
' as qty
from book
where ',
c.column_name,
' > 0'
) SEPARATOR ' UNION ALL '
) INTO #sql
FROM information_schema.columns c
where c.table_name = 'book'
and c.column_name not in ('id')
order by c.ordinal_position;
SET #sql
= CONCAT('select id, month, qty
from
(', #sql, ') x order by id');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
);
** Updated answer:
This solution is dynamic, whatever columns you have in table, it will populate them and extract information in required format. For sure columns have to have something in common, like they're all begins with "2018", you can change that as needed in Query.
SELECT
GROUP_CONCAT(
CONCAT(
'SELECT id, ''', COLUMN_NAME, ''' as month, ', COLUMN_NAME, ' as QTY FROM t1 ') SEPARATOR ' UNION ALL ')
FROM
`INFORMATION_SCHEMA`.`COLUMNS`
WHERE
`COLUMN_NAME` LIKE '2018%'
INTO #sql;
SET #query = CONCAT('select id, month, QTY from (' , #sql , ') x order by id;');
SELECT #query;
PREPARE stmt FROM #query;
EXECUTE stmt;
**Note: the query has 2 outputs, first is the prepared concatenated query string (just to know what it looks like before run), and the other is the actual data. If you want only actual data you can comment (SELECT #query;) or remove it.

Limit the columns displayed in a MYSQL pivot table

The Question:
How do I limit the number of columns displayed/produced on a MYSQL pivot table?
My Setup:
I have a table named "updates" that looks like the following:
I have the following snippet of query (This is only part of the query, the whole thing only adds more columns from other tables but this is the only section that gets pivoted):
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(Date = ''',
Date,
''', Description, NULL)) AS ',
CONCAT("'",Date_Format(Date, '%d/%m/%Y'),"'")
)
)INTO #sql
FROM updates;
SET #sql = CONCAT('SELECT Action, ', #sql, ' FROM updates GROUP BY Action');
PREPARE stmt FROM #sql;
EXECUTE stmt;
The result of this query is as follows:
As you can see, this pivots the table as intended with the dates as columns. However, there is potential for these updates (to actions) to become very long before they are "closed" and not displayed. Therefore, I would like to limit the outcome to the latest 3 updates. BUT..Not per action as this would potentially still give me a lot of updates in the pivot table.
I would like to have the most recent 3 dates from the updates table with all updates for each date keeping this pivot format.
Example: The outcome table above would look the same but with the exception of the columns titled "02/10/2016" and "04/10/2016".
Thanks in advance for any assistance or advise.
For anyone else trying to solve this issue, I managed to use the following query to produce the desired results:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(Date = ''',
Date,
''', Description, NULL)) AS ',
CONCAT("'",Date_Format(Date, '%d/%m/%Y'),"'")
) ORDER BY Date ASC
) INTO #sql
FROM (
SELECT * FROM updates
GROUP BY Date
ORDER BY Date DESC
LIMIT 2)
AS updates;
SET #sql = CONCAT('SELECT Action, ', #sql, ' FROM updates GROUP BY Action');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

joined table rows into columns with column titles from original rows

I have three MYSQL tables, simplified, with following columns:
ModuleEntries (MDE_ID)
ModuleFields (MDF_ID, MDF_Label)
ModuleEntryFieldValues (MFV_ID, MFV_MDE_ID, MFV_MDF_ID, MFV_Value)
As you can see from the simplified column list, ModuleEntryFieldValues is the table which states that "for an entry, this field has this value".
Now, I would like to retrieve this information in one "flat" recordset. I have managed to get things working, but not entirely to what i want.
With the help of a different SO article, I managed to get the dynamic, variable amount of columns to work, by using a cursor. declare finish int default 0;
declare fldid int;
declare str varchar(10000) default 'SELECT *,';
declare curs cursor for select MDF_ID from ModuleFields group by MDF_ID;
declare continue handler for not found set finish = 1;
open curs;
my_loop:loop
fetch curs into fldid;
if finish = 1 then
leave my_loop;
end if;
set str = concat(str, 'max(case when MFV_MDF_ID = ',fldid,' then MFV_Value else null end) as field_',fldid,',');
end loop;
close curs;
set str = substr(str,1,char_length(str)-1);
set #str = concat(str, ' from ModuleEntries LEFT join ModuleEntryFieldValues ON MDE_ID = MDF_MDE_ID GROUP BY MDE_ID');
prepare stmt from #str;
execute stmt;
deallocate prepare stmt;
What this code doesn't do, is allow me to put the column values of MDF_Label as the actual column headers of the rows. Now, the above code gives me "field_1, field_2, ..."
Is it possible to join these three tables, and have the MDF_Label as column header for the rows that are now columns in the joined table ?
I want this...
ModuleEntries | ModuleFields | ModuleEntryFieldValues
------------- | ------------------ | -----------------------------------
MDE_ID | MDF_ID - MDF_Label | MFV_MDE_ID - MFV_MDF_ID - MFV_Value
1 | 1 - Height | 1 - 1 - 120cms
| 2 - Width | 1 - 2 - 30cms
| 3 - Weight |
into this
Recordset
---------
MDE_ID - Height - Width - Weight
1 - 120cms - 30cms - null
I hope my question was clear enough. If not please comment and I will give more information where needed, if I can.
I'd just use GROUP_CONCAT() instead of cursors to generate the prepared statement:
SELECT CONCAT(
' SELECT MDE_ID,'
, GROUP_CONCAT(
't', MDF_ID, '.MFV_Value AS `', REPLACE(MDF_Label, '`', '``'), '`'
)
,' FROM ModuleEntries '
, GROUP_CONCAT(
'LEFT JOIN ModuleEntryFieldValues AS t', MDF_ID, '
ON t', MDF_ID, '.MFV_MDE_ID = ModuleEntries.MDE_ID
AND t', MDF_ID, '.MFV_MDF_ID = ', MDF_ID
SEPARATOR ' ')
) INTO #qry FROM ModuleFields;
Save for whitespace editing to make it more readable, with your sample data #qry would then contain:
SELECT MDE_ID,
t1.MFV_Value AS `Height`,
t2.MFV_Value AS `Width`,
t3.MFV_Value AS `Weight`
FROM ModuleEntries
LEFT JOIN ModuleEntryFieldValues AS t1
ON t1.MFV_MDE_ID = ModuleEntries.MDE_ID AND t1.MFV_MDF_ID = 1
LEFT JOIN ModuleEntryFieldValues AS t2
ON t2.MFV_MDE_ID = ModuleEntries.MDE_ID AND t2.MFV_MDF_ID = 2
LEFT JOIN ModuleEntryFieldValues AS t3
ON t3.MFV_MDE_ID = ModuleEntries.MDE_ID AND t3.MFV_MDF_ID = 3
One can then prepare and execute that statement:
PREPARE stmt FROM #qry;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #qry = NULL;
Which yields the following results:
MDE_ID Height Width Weight
1 120cms 30cms (null)
See it on sqlfiddle.