I have problems in makeing a PIVOT Table in MySQL. I have following table. (it is a reduced demo table. The real one has 4000 stocks for f.e 10 tradedates and 20 measurement values.)
CREATE TABLE `levermann` (
`RecNum` bigint(20) NOT NULL AUTO_INCREMENT,
`Tradedate` date DEFAULT NULL,
`Stock_Short` varchar(50) DEFAULT NULL,
`Country` varchar(2) DEFAULT NULL,
`LScore2` int(11) DEFAULT NULL,
`MarketCAPUSD` bigint(20) DEFAULT NULL,
PRIMARY KEY (`RecNum`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of levermann
-- ----------------------------
INSERT INTO `levermann` VALUES ('8099', '2018-05-23', 'ANDR.VI', 'VI', '-9', '5109518494');
INSERT INTO `levermann` VALUES ('8100', '2018-05-23', 'BWO.VI', 'VI', '-7', '4241189324');
INSERT INTO `levermann` VALUES ('8101', '2018-05-23', 'CAI.VI', 'VI', '-7', '3222135865');
INSERT INTO `levermann` VALUES ('8102', '2018-05-09', 'CWI.VI', 'VI', '-8', null);
INSERT INTO `levermann` VALUES ('8103', '2018-05-23', 'EBS.VI', 'VI', '-7', '18317742129');
INSERT INTO `levermann` VALUES ('8104', '2018-05-23', 'FLU.VI', 'VI', '-8', '3176359049');
INSERT INTO `levermann` VALUES ('8105', '2018-05-23', 'IIA.VI', 'VI', '-8', '2767477473');
INSERT INTO `levermann` VALUES ('8106', '2018-05-23', 'LNZ.VI', 'VI', '-9', '3027507195');
The output should be a table where each STOCKCODE (f.e. ANDR.VI) of these 8 should be a column with one selectable measurement value (f.e. LScore2) grouped by tradedate (= row).
I have found this Exmapl in MYSQL but I do not understand it completely. More over I have done this:
SELECT
tradedate,
GROUP_CONCAT(stock_short) as STOCKCODE
FROM
levermann
GROUP BY
Tradedate;
But here the stockcodes are in one cell and not in the header.
Here is an image of an example of the wanted output. The total number of columns are about 4000 (max columns in table do not exceed 4096). And the tradedates (= rows) are about 350 days / years for 2 years in total.
It sheems clear that the columns should be created dynamically and can not hardcoded by AS statements.
Is there any solution for this difficult question?
THANKS a lot.
Update:
I think on a dynamically running statement like this..
SET #sql = NULL;
SELECT GROUP_CONCAT(concat(LScore2,' AS `LScore_',Stock_Short,'`')) into
#sql from levermann;
SET #sql = CONCAT('SELECT tradedate, ', #sql, '
FROM levermann
GROUP BY tradedate');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
But I do not know how I can create the string:
LScore AS LScore_ANDR.VI, LScore AS LScore_BWO.VI, ...
I really in doubt but it might be done in a way in MySQL.
Of course I can make a php script too. But I would like to learn how it is done in MySQL.
Update 2: I think I could make it. I am not quite sure if it is correct, but it is dynamically created. In MySQL
I think I have got it:
SET SESSION group_concat_max_len = ##max_allowed_packet;
SET #sql = NULL;
SELECT GROUP_CONCAT(concat('MAX(CASE Stock_Short WHEN \'',Stock_Short,'\' THEN \'',LScore2,'\' END) AS `LScore_',Stock_Short,'`')) into #sql from levermanndemo where country = 'VI';
SET #sql = CONCAT('SELECT tradedate, ', #sql, '
FROM levermanndemo
GROUP BY tradedate');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
OUTPUT:
Update: Unfortunatly it makes troubel if the table looks like this.
I tried this solution:
SET SESSION group_concat_max_len = ##max_allowed_packet;
SET #sql = NULL;
SELECT GROUP_CONCAT( DISTINCT concat('MAX(CASE WHEN p.Stock_Short =
\'',f.Stock_Short,'\' AND `Tradedate` = \'', f.tradedate,'\' THEN
\'',f.LScore2,'\' ELSE NULL END) AS `LScore_',f.Stock_Short,'`'))
into #sql from levermanndemo f ;
SET #sql = CONCAT('SELECT p.tradedate, ', #sql, ' FROM levermanndemo p
GROUP BY p.tradedate');
#SELECT #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
The output is not desired - and wrong. There are now 2 columns with the same STOCK (BWO.VI and BWO.VI1) , I would like to merge these two columns together. But how can it be done?
In SQL, you can't write any query that expands columns dynamically based on the data values it finds once it starts reading data. All the columns must be fixed in the query's select-list at the time you prepare the query -- before it reads any data.
This means you must know all the distinct values, and you need to produce a select-list with one column for each value you want to include in the pivot.
You can solve this by doing two queries: one to fetch all the distinct stock values:
SELECT DISTINCT Stock_Short FROM levermann
Then based on that result, format a looooong SQL query with on expression for each stock value. Start with the column you know you want that is not one of the dynamic stock-related columns:
SELECT Tradedate,
Then for each row in the result from the firs query, append a column like:
MAX(CASE Stock_Short WHEN <value> THEN LScore2 END) AS <alias>,
Then finally append the end of the query:
FROM levermann
GROUP BY Tradedate;
My recommendation since you have 4000 distinct stock values is that you should just fetch the data from the database as-is, and use application code to present a pivoted display. That is, loop over the result of the SQL query, all 4000 rows (not columns), and arrange it into an object in your application space. Then format that object however you want for display.
If the list of StockCodes is fixed can one not simply fix the columns of the new table and then filter for the data in the original table to copy into the new table?
At least this would be the process when doing it manually in Excel for instance.
Related
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;
I have a value pair table that I want to use to create a member table
Based on Taryns answer to this question
MySQL pivot table query with dynamic columns
I have this code that creates selects the data, which works fine
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(CASE WHEN wpdg_usermeta.meta_key = ''',
meta_key,
''' THEN wpdg_usermeta.meta_value END) `',
meta_key, '`'
)
) INTO #sql
FROM
wpdg_usermeta
WHERE
wpdg_usermeta.meta_key like "member_%"
;
SET #sql = CONCAT('SELECT user_id, ', #sql, '
FROM wpdg_usermeta
GROUP BY user_id');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
So, my question is - does anyone know how I could alter this to run an INSERT instead of a SELECT so that I could populate the new member table?
You want to create a new table with the results of the dynamic query. I think the simplest approach is to use the create table ... as select ... syntax.
This requires very few changes to your code, and allow you to create the table on the fly based on the results of the query:
SET #sql = CONCAT(
'CREATE TABLE member AS SELECT user_id, ',
#sql,
' FROM wpdg_usermeta GROUP BY user_id'
);
Note that the datatypes of the new table are inferred from the query's metadata; this might, or might no, to exactly what you want. You can check the documentation for more detaiils.
Is there a way to do the following in mysql?
SET #studios = ('Disney', 'Warner Bros.', 'Fox');
SELECT * FROM movies WHERE provider IN #studios;
When I try doing the above I get the error:
Operand should contain 1 column(s)
The error is coming from your initial assignment. You cannot assign lists to variables.
The only way of doing this in MySQL is to either create a temp table to hold the values, and then do ... IN (SELECT someVal FROM thatTemp), or to dynamically create the query with the values directly in the query string.
Example temp table creation:
CREATE TEMPORARY TABLE `someTemp` ( someVal VARCHAR(16) );
INSERT INTO `someTemp` (someVal) VALUES ('a'), ('b'), ('c');
SELECT * FROM myTable WHERE myField IN (SELECT someVal FROM someTemp);
DELETE TEMPORARY TABLE `someTemp`;
Alternatively, there is also FIND_IN_SET, which could be used like this:
SET #list = 'a,b,c';
SELECT * FROM myTable WHERE FIND_IN_SET(myField, #list) <> 0;
but this method probably has extremely poor performance (and may not be useable if your "myField" values may contain commas).
It is not possible to set a tuple/list/array in a user-defined variable in MySQL. You can use Dynamic SQL for the same:
-- we use single quotes two times to escape it
SET #studios = '(''Disney'', ''Warner Bros.'', ''Fox'')';
-- generate the query string
SET #query = CONCAT('SELECT * FROM movies WHERE provider IN ', #studios);
-- prepare the query
PREPARE stmt FROM #query;
-- execute it
EXECUTE stmt;
-- deallocate it
DEALLOCATE PREPARE stmt;
You could concatenate your list to a string, and use FIND_IN_SET as your criteria. Might not be super efficient, but makes the code quite easy to read and maintain.
Looks like this:
SET #studios = CONCAT_WS(',',
'Disney',
'Warner Bros.',
'Fox'
);
SELECT * FROM movies
WHERE FIND_IN_SET(provider, #studios) <> 0;
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
I've put together a simple stored procedure in which two parameters are passed through to make it more dynamic. I've done this with a prepared statement in the "First Two Digits and Count of Records" section.
What I'm not sure of is if I can make the SET vTotalFT section dynamic with a prepared statement as well.
At the moment I have to hard-code the table names and fields. I want my vTotalFT variable to be assigned based on a prepared dynamic SQL statement, but I'm not sure of the syntax. The idea is that when I call my procedure, I could tell it which table and which field to use for the analysis.
CREATE PROCEDURE `sp_benfords_ft_digits_analysis`(vTable varchar(255), vField varchar(255))
SQL SECURITY INVOKER
BEGIN
-- Variables
DECLARE vTotalFT int(11);
-- Removes existing table
DROP TABLE IF EXISTS analysis_benfords_ft_digits;
-- Builds base analysis table
CREATE TABLE analysis_benfords_ft_digits
(
ID int(11) NOT NULL AUTO_INCREMENT,
FT_Digits int(11),
Count_of_Records int(11),
Actual decimal(18,3),
Benfords decimal(18,3),
Difference Decimal(18,3),
AbsDiff decimal(18,3),
Zstat decimal(18,3),
PRIMARY KEY (ID),
KEY id_id (ID)
);
-- First Two Digits and Count of Records
SET #s = concat('INSERT INTO analysis_benfords_ft_digits
(FT_Digits,Count_of_Records)
select substring(cast(',vField,' as char(50)),1,2) as FT_Digits, count(*) as Count_of_Records
from ',vTable,'
where ',vField,' >= 10
group by 1');
prepare stmt from #s;
execute stmt;
deallocate prepare stmt;
SET vTotalFT = (select sum(Count_of_Records) from
(select substring(cast(Gross_Amount as char(50)),1,2) as FT_Digits, count(*) as Count_of_Records
from supplier_invoice_headers
where Gross_Amount >= 10
group by 1) a);
-- Actual
UPDATE analysis_benfords_ft_digits
SET Actual = Count_of_Records / vTotalFT;
-- Benfords
UPDATE analysis_benfords_ft_digits
SET Benfords = Log(1 + (1 / FT_Digits)) / Log(10);
-- Difference
UPDATE analysis_benfords_ft_digits
SET Difference = Actual - Benfords;
-- AbsDiff
UPDATE analysis_benfords_ft_digits
SET AbsDiff = abs(Difference);
-- ZStat
UPDATE analysis_benfords_ft_digits
SET ZStat = cast((ABS(Actual-Benfords)-IF((1/(2*vTotalFT))<ABS(Actual-Benfords),(1/(2*vTotalFT)),0))/(SQRT(Benfords*(1-Benfords)/vTotalFT)) as decimal(18,3));
First, to use dynamic table/column names, you'll need to use a string/Prepared Statement like your first query for #s. Next, to get the return-value from COUNT() inside of the query you'll need to use SELECT .. INTO #vTotalFT.
The following should be all you need:
SET #vTotalFTquery = CONCAT('(select sum(Count_of_Records) INTO #vTotalFT from
(select substring(cast(', vField, ' as char(50)),1,2) as FT_Digits, count(*) as Count_of_Records
from ', vTable, '
where ', vField, ' >= 10
group by 1) a);');
PREPARE stmt FROM #vTotalFTquery;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Please note: the variable name has changed from vTotalFT to #vTotalFT. It doesn't seem to work without the #. And also, the variable #vTotalFT won't work when declared outside of/before the query, so if you encounter an error or empty results that could be a cause.
SELECT CONCAT (
'SELECT DATE(PunchDateTime) as day , '
,GROUP_CONCAT('GROUP_CONCAT(IF(PunchEvent=', QUOTE(PunchEvent), ',PunchDateTime,NULL))
AS `', REPLACE(PunchEvent, '`', '``'), '`')
,'
FROM tbl_punch
GROUP BY DATE(PunchDateTime)
ORDER BY PunchDateTime ASC
'
)
INTO #sql
FROM (
SELECT DISTINCT PunchEvent
FROM tbl_punch
) t;
PREPARE stmt
FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;