SQL Statement - SQL Matrix - mysql

For the following Table, is it possible, to create an SQL Statement to create a data Matrix or view?
Table:
TeamA|TeamB|Won|Lost
--------------------
A | B | 5 | 3
A | C | 2 | 4
A | D | 9 | 1
B | E | 5 | 5
C | A | 2 | 4
Result-Matrix:
| A | B | C | D | E
----------------------------
A | 0 | 2 | -2 | 8 | 0
B | 0 | 0 | 0 | 0 | 0
C | -2 | 0 | 0 | 0 | 0

There are two ways that you can pivot data in MySQL. If you know the values ahead of time (teams) then you will hard-code the values or you can use a prepared statement to generate dynamic sql.
A static version would be:
select TeamA,
max(case when TeamB = 'A' then won - lost else 0 end) as A,
max(case when TeamB = 'B' then won - lost else 0 end) as B,
max(case when TeamB = 'C' then won - lost else 0 end) as C,
max(case when TeamB = 'D' then won - lost else 0 end) as D,
max(case when TeamB = 'E' then won - lost else 0 end) as E
from yourtable
group by TeamA;
See SQL Fiddle with Demo
If you want to use a dynamic version with a prepared statement, the code would be:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(CASE WHEN TeamB = ''',
TeamB,
''' THEN won - lost else 0 END) AS `',
TeamB, '`'
)
) INTO #sql
from
(
select *
from yourtable
order by teamb
) x;
SET #sql
= CONCAT('SELECT TeamA, ', #sql, '
from yourtable
group by TeamA');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo.
Edit #1, after thinking about this I would actually do this a slight bit different. I would generate a true matrix os the data where the teams appeared in both the row and the column. To do this you would first use a UNION ALL query to get all teams in two columns:
select teama Team1, teamb Team2,
won-lost Total
from yourtable
union all
select teamb, teama,
won-lost
from yourtable
See SQL Fiddle with Demo. Once that is done, then you would pivot the data:
select Team1,
coalesce(max(case when Team2 = 'A' then Total end), 0) as A,
coalesce(max(case when Team2 = 'B' then Total end), 0) as B,
coalesce(max(case when Team2 = 'C' then Total end), 0) as C,
coalesce(max(case when Team2 = 'D' then Total end), 0) as D,
coalesce(max(case when Team2 = 'E' then Total end), 0) as E
from
(
select teama Team1, teamb Team2,
won-lost Total
from yourtable
union all
select teamb, teama,
won-lost
from yourtable
) src
group by Team1;
See SQL Fiddle with Demo. Which gives a more detailed result of:
| TEAM1 | A | B | C | D | E |
-------------------------------
| A | 0 | 2 | -2 | 8 | 0 |
| B | 2 | 0 | 0 | 0 | 0 |
| C | -2 | 0 | 0 | 0 | 0 |
| D | 8 | 0 | 0 | 0 | 0 |
| E | 0 | 0 | 0 | 0 | 0 |

You cannot create a SQL statement or view that has a variable number of columns. In standard SQL, you can pivot the data by doing something like:
select TeamA,
max(case when TeamB = 'A' then won - lost end) as A,
max(case when TeamB = 'B' then won - lost end) as B,
max(case when TeamB = 'C' then won - lost end) as C,
max(case when TeamB = 'D' then won - lost end) as D,
max(case when TeamB = 'E' then won - lost end) as E
from t
group by TeamA
order by 1
Some databases support a pivot statement.
To do this generically, you would have to create a SQL statement as a string, and then execute it (often called dynamic SQL). Such a statement could be produced by SQL, by a stored procedure, in Excel, or some other programming tool. It then would need to be executed.
Let me repeat: any given SQL statement has a pre-defined set of columns. You cannot vary the number of columns.

Related

How to Query Maximum Integer Value from Substring of Varchar Column with Condition

I want to get the total maximum number of column CODE which the maximum is defined by the last five digits from mybarcode column.
mybarcode | code | judge | create_date |
-------------+------+--------+-------------+
M71X400001 | 7 | pass |
M71X400002 | 7 | pass |
M71X400005 | 7 | pass |
M71X400010 | 7 | pass |
M81X400001 | 8 | pass |
M81X400002 | 8 | pass |
M81X400007 | 8 | pass |
M91X400001 | 9 | pass |
M91X400003 | 9 | pass |
```
Example:
>The maximum value of 7 from CODE column is 10 ( from M71X4'00010')
>The maximum value of 8 from CODE column is 7 ( from M81X4'00007')
>The maximum value of 9 from CODE column is 3 ( from M91X4'00003')
The result should be 10+7+3=20.
And want display in the result table below.
```
SELECT DAY,
SUM(CASE WHEN judge = 'pass' then 1 else 0 end) pass,
SUM(CASE WHEN judge = 'fail' then 1 else 0 end) fail
**??? as number**
from MYTABLE
where MONTH(create_date) = '04' and YEAR(create_date) = '2019'
GROUP BY DAY
Result Table
day | pass | fail | number |
--------+------+--------+----------+
1 | 9 | 0 | 20 |
2 | 9 | 0 | ?? |
3 | 9 | 0 | ?? |
I think you need to do group by two times. Please try below code -
For MySQL -
SELECT
DAY,
SUM(pass),
SUM(fail),
SUM(max_barcode)
FROM (
SELECT
DAY,
SUM(CASE WHEN judge = 'pass' then 1 else 0 end) pass,
SUM(CASE WHEN judge = 'fail' then 1 else 0 end) fail,
Code,
CAST(MAX(SUBSTRING(mybarcode, 5)) AS SIGNED) AS max_barcode
FROM MYTABLE
WHERE MONTH(create_date) = '%s' and YEAR(create_date) = '%s'
GROUP BY DAY, Code
) AS CTE
GROUP BY DAY;
FOR MS SQL Server -
;WITH CTE AS (
SELECT
DAY,
SUM(CASE WHEN judge = 'pass' then 1 else 0 end) pass,
SUM(CASE WHEN judge = 'fail' then 1 else 0 end) fail,
Code,
max_barcode = cast(max(right(mybarcode, 5)) as int)
FROM MYTABLE
WHERE MONTH(create_date) = '%s' and YEAR(create_date) = '%s'
GROUP BY DAY, Code
)
SELECT
DAY,
SUM(pass),
SUM(fail),
SUM(max_barcode)
FROM CTE
GROUP BY DAY;

mySQL 5.5 - pivot values to columns after two tables linked with UNION

I have a final issue that I can not get sorted with my sql code. Basically I want to pivot table and display in the different format to this one:
http://sqlfiddle.com/#!9/98436/1
What I try to do is to pivot LOC_ID and LOC_ID_b to get flat file. The problme is that to get the code working I group by LOC_ID and LOC_ID_b and not sure how to get around it.
The result I try to achieve is the following:
PN AAA q AAA c BBB q BBB c CCC q CCC c
A1 2 1
RRR 1 1
T1 1 1
HHH 3 3
You can use conditional aggregation. If you have a limited number of known loc_ids you could code like this
Select pn,
max(case when src = 'h' and loc_id = 'AAA' then val else 0 end) as AAA_qty,
max(case when src = 'r' and loc_id = 'AAA' then val else 0 end) as AAA_count,
max(case when src = 'h' and loc_id = 'BBB' then val else 0 end) as BBB_qty,
max(case when src = 'r' and loc_id = 'BBB' then val else 0 end) as BBB_count,
max(case when src = 'h' and loc_id = 'CCC' then val else 0 end) as CCC_qty,
max(case when src = 'r' and loc_id = 'CCC' then val else 0 end) as CCC_count
from (select 'h' as src, pn, loc_id, sum(qty) val from history group by src,pn,loc_id
union
select 'r' as src, pn, loc_id, count(*) val from rota group by src,pn,loc_id
) s
group by pn
order by pn;
If you don't know or don't want to be change your code when loc_ids are added or deleted you need dynamic sql
set #sql = (
select concat(
'Select pn,',
group_concat(
concat('max(case when src = ' ,char(39),'h',char(39),' and loc_id = ', char(39),loc_id,char(39), ' then val else 0 end) as ' ,
concat(loc_id,'_qty,')
,
'max(case when src = ' ,char(39),'r',char(39),' and loc_id = ', char(39),loc_id,char(39), ' then val else 0 end) as ' ,
concat(loc_id,'_count')
)
)
,' from ('
,
'select ', char(39),'h',char(39),' as src, pn, loc_id, sum(qty) val from history group by src,pn,loc_id'
,
' union '
,
'select ',char(39),'r',char(39),' as src, pn, loc_id, count(*) val from rota group by src,pn,loc_id
) s
group by pn
order by pn;'
)
from
(
select loc_id from history
union
select loc_id from rota
) a
);
prepare sqlstmt from #sql;
execute sqlstmt;
deallocate prepare sqlstmt;
+-----+---------+-----------+---------+-----------+---------+-----------+
| pn | AAA_qty | AAA_count | BBB_qty | BBB_count | CCC_qty | CCC_count |
+-----+---------+-----------+---------+-----------+---------+-----------+
| A1 | 0 | 0 | 2 | 1 | 0 | 0 |
| HHH | 0 | 0 | 0 | 0 | 3 | 0 |
| RRR | 0 | 0 | 0 | 0 | 0 | 1 |
| T1 | 1 | 1 | 0 | 0 | 0 | 0 |
+-----+---------+-----------+---------+-----------+---------+-----------+
4 rows in set (0.00 sec)

Swapping rows to columns

I've read previous posts regarding pivot table and swapping rows to columns, but I couldn't find correct answer for my question. I have below table in MySQL:
+--------+--------+
| Userid | gname |
+--------+--------+
| 12 | AVBD |
| 12 | ASD |
| 12 | AVFD |
| 12 | Aew1 |
| 12 | AVBD32 |
| 12 | ASD23 |
| 12 | AVBDe |
| 12 | ASDer |
| 45 | AVBD |
| 45 | ASD444 |
| 45 | AVBD44 |
| 45 | ASD44 |
| 453 | AVBD22 |
| 453 | ASD1 |
+--------+--------+
I want to produce an ordered pivot table:
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
| Userid | gname1 | gname2 | gname3 | gname4 | gname5 | gname6 | gname7 | gname8 |
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
| 12 | AVBD | ASD | AVFD | Aew1 | AVBD32 | ASD23 | AVBDe | ASDer |
| 45 | AVBD | ASD444 | AVBD44 | ASD44 | | | | |
| 453 | AVBD22 | ASD1 | | | | | | |
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
the name of gname is dynamic and has no limit.
Here is the data setup along with a SQLFiddle http://sqlfiddle.com/#!2/65fec
CREATE TABLE gnames
(`Userid` int, `gname` varchar(6))
;
INSERT INTO gnames
(`Userid`, `gname`)
VALUES
(12, 'AVBD'),
(12, 'ASD'),
(12, 'AVFD'),
(12, 'Aew1'),
(12, 'AVBD32'),
(12, 'ASD23'),
(12, 'AVBDe'),
(12, 'ASDer'),
(45, 'AVBD'),
(45, 'ASD444'),
(45, 'AVBD44'),
(45, 'ASD44'),
(453, 'AVBD22'),
(453, 'ASD1')
;
This would be significantly easier if MySQL supported windowing functions because it's not necessarily the easiest to create a row number for each userid. I'll show you two ways to do this with hard-coded queries (limited number of results) and then I'll include one version that is using dynamic SQL.
In order to get the final result, you need some sequential number for each gname within the userid. This can be done a few different ways.
First, you can use a correlated subquery to count the number of gnames per user, you'd then use this sequence to create your new columns via an aggregate function with CASE expression:
select
userid,
max(case when gnameNum = 1 then gname else '' end) gname1,
max(case when gnameNum = 2 then gname else '' end) gname2,
max(case when gnameNum = 3 then gname else '' end) gname3,
max(case when gnameNum = 4 then gname else '' end) gname4,
max(case when gnameNum = 5 then gname else '' end) gname5,
max(case when gnameNum = 6 then gname else '' end) gname6,
max(case when gnameNum = 7 then gname else '' end) gname7,
max(case when gnameNum = 8 then gname else '' end) gname8
from
(
select userid,
gname,
(select count(*)
from gnames d
where g.userid = d.userid
and g.gname <= d.gname) as gnameNum
from gnames g
) src
group by userid;
See SQL Fiddle with Demo. Inside the subquery you are creating a row number for each gname, you then use this new value in the column creation. The problem with correlated subqueries is you could suffer from performance issues on larger data sets.
A second method would be to include user variables to create the row number. This code uses 2 variables to compare the previous row to the current row and increases the row number, if the userid is the same as the previous row. Again, you'd use the row number created to convert the data to new columns:
select
userid,
max(case when rownum = 1 then gname else '' end) gname1,
max(case when rownum = 2 then gname else '' end) gname2,
max(case when rownum = 3 then gname else '' end) gname3,
max(case when rownum = 4 then gname else '' end) gname4,
max(case when rownum = 5 then gname else '' end) gname5,
max(case when rownum = 6 then gname else '' end) gname6,
max(case when rownum = 7 then gname else '' end) gname7,
max(case when rownum = 8 then gname else '' end) gname8
from
(
select
g.userid,
g.gname,
#row:=case when #prev=userid then #row else 0 end + 1 as rownum,
#prev:=userid
from gnames g
cross join
(
select #row:=0, #prev:=null
) r
order by userid, gname
) src
group by userid;
See SQL Fiddle with Demo.
Now, in order to do this dynamically you will need to use a prepared statement. This process will create a sql string that you'll execute to get the final result. For this example, I used the user variable query above:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(case when rownum = ',
rownum,
' then gname else '''' end) AS `gname',
rownum, '`'
)
) INTO #sql
from
(
select
g.userid,
g.gname,
#row:=case when #prev=userid then #row else 0 end + 1 as rownum,
#prev:=userid
from gnames g
cross join
(
select #row:=0, #prev:=null
) r
order by userid, gname
) src;
SET #sql = CONCAT('SELECT userid, ', #sql, '
from
(
select
g.userid,
g.gname,
#row:=case when #prev=userid then #row else 0 end + 1 as rownum,
#prev:=userid
from gnames g
cross join
(
select #row:=0, #prev:=null
) r
order by userid, gname
) src
group by userid');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo. All three versions will give a result of:
| USERID | GNAME1 | GNAME2 | GNAME3 | GNAME4 | GNAME5 | GNAME6 | GNAME7 | GNAME8 |
|--------|--------|--------|--------|--------|--------|--------|--------|--------|
| 12 | Aew1 | ASD | ASD23 | ASDer | AVBD | AVBD32 | AVBDe | AVFD |
| 45 | ASD44 | ASD444 | AVBD | AVBD44 | | | | |
| 453 | ASD1 | AVBD22 | | | | | | |
One thing to consider when using the dynamic SQL is MySQL has a set length for the group_concat_max_len, if you have a lot of columns being created you might run into issues. You'll want to account for that. Here is another question that deals with this MySQL and GROUP_CONCAT() maximum length.

Create a Summary View in MySQL by pivoting row into dynamic number of columns

I have a table in MySQL with the following fields:
id, company_name, year, state
There are multiple rows for the same customer and year, here is an example of the data:
id | company_name | year | state
----------------------------------------
1 | companyA | 2008 | 1
2 | companyB | 2009 | 2
3 | companyC | 2010 | 3
4 | companyB | 2009 | 1
5 | companyC | NULL | 3
I am trying to create a view from this table to show one company per row (i.e. GROUP BY pubco_name) where the state is the highest for a given year.
Here is an example of the view I am trying to create:
id | cuompany_name | NULL | 2008 | 2009 | 2010
--------------------------------------------------
1 | companyA | NULL | 1 | NULL | NULL
2 | companyB | NULL | 2 | NULL | NULL
3 | companyC | 3 | NULL | NULL | 3
There is a lot more data than this, but you can see what I am trying to accomplish.
I don't know how to select the max state for each year and group by pubco_name.
Here is the SQL I have thus far (I think we need to use CASE and/or sub-selects here):
SELECT
id,
company_name,
SUM(CASE WHEN year = 2008 THEN max(state) ELSE 0 END) AS 2008,
SUM(CASE WHEN year = 2009 THEN max(state) ELSE 0 END) AS 2009,
SUM(CASE WHEN year = 2010 THEN max(state) ELSE 0 END) AS 2010,
SUM(CASE WHEN year = 2011 THEN max(state) ELSE 0 END) AS 2011,
SUM(CASE WHEN year = 2012 THEN max(state) ELSE 0 END) AS 2012,
SUM(CASE WHEN year = 2013 THEN max(state) ELSE 0 END) AS 2013
FROM tbl
GROUP BY company_name
ORDER BY id DESC
Appreciate your help and thanks in advance.
You need to pivot the table but mysql does not have any such functionality of pivot
so we need to replicate its functionality
EDITED
Select
group_concat(
DISTINCT
if(year is null,
CONCAT('max(if (year is null, state, 0)) as ''NULL'' '),
CONCAT('max(if (year=''', year, ''', state, 0)) as ''',year, ''' '))
) into #sql from tbl join (SELECT #sql:='')a;
set #sql = concat('select company_name, ', #sql, 'from tbl group by company_name;');
PREPARE stmt FROM #sql;
EXECUTE stmt;
Result
| COMPANY_NAME | 2008 | 2009 | 2010 | NULL |
--------------------------------------------
| companyA | 1 | 0 | 0 | 0 |
| companyB | 0 | 2 | 0 | 0 |
| companyC | 0 | 0 | 3 | 3 |
SQL FIDDLE
There are 2 approaches to solve your problem
1. create case for each year, which is not possible in your case as we are dealing with year
2. generate the query dynamically so that we get proper columns as per your need.
I have given solution according to the second solution where I am generating the query and storing it in #sql variable. In the fiddle I have printed the contents of #sql before executing it.
select company_name, max(if (year='2008', state, 0)) as '2008' ,max(if (year='2009', state, 0)) as '2009' ,max(if (year='2010', state, 0)) as '2010' ,max(if (year is null, state, 0)) as 'NULL' from tbl group by company_name;
For more information regarding group_concat() go through the link
GROUP_CONCAT and
USER DEFINED VARIABLE
Hope this helps..
Please see the page linked in the answer to this question.
Note that when you do this, you must specify ahead of time how many columns you want in your output.
In response to the comment below, here is a simple/ basic implementation that reproduces the result table above (except for the ID column; having it makes no sense, as each row in the result can summarize more than one row in the input table)
SELECT
`company_name`,
NULLIF(SUM(CASE WHEN `t3`.`year` IS NULL THEN `t3`.`state` ELSE 0 END), 0) AS `null`,
NULLIF(SUM(CASE WHEN `t3`.`year` = 2008 THEN `t3`.`state` ELSE 0 END), 0) AS `2008`,
NULLIF(SUM(CASE WHEN `t3`.`year` = 2009 THEN `t3`.`state` ELSE 0 END), 0) AS `2009`,
NULLIF(SUM(CASE WHEN `t3`.`year` = 2010 THEN `t3`.`state` ELSE 0 END), 0) AS `2010`
FROM
(
SELECT
`t1`.`id`,
`t1`.`company_name`,
`t1`.`year`,
`t1`.`state`
FROM `tbl` `t1`
WHERE `t1`.`state` = (
SELECT MAX(`state`)
FROM `tbl` `t2`
WHERE `t2`.`company_name` = `t1`.`company_name`
AND (`t2`.`year` IS NULL AND `t1`.`year` IS NULL OR `t2`.`year` = `t1`.`year`)
)
) `t3`
GROUP BY `t3`.`company_name`;
This uses nested queries: the inner ones (with the t1 and t2 aliases) find the row with the maximum state for each year and company (and which will break unless you can be sure that this is unique!), and the outer one t3 does the pivot.
I would test this thoroughly to ensure performance is acceptable on real data.

combining multiple different sql into one

cusID | Name | status | Date
---------------------------------
1 | AA | 0 | 2013-01-25
2 | BB | 1 | 2013-01-23
3 | CC | 1 | 2013-01-20
SELECT COUNT(cusID) FROM customer WHERE STATUS=0;
SELECT COUNT(cusID) FROM customer WHERE STATUS=1;
Is there a way of combing such two sql and return the results as one. Because want to avoid calling to DB everytime. I tried UNION of two statments, but only showing one result.
This is the shortest possible solution in MySQL.
SELECT SUM(status = 1) totalActive,
SUM(status = 0) totalInactive
FROM tableName
SQLFiddle Demo
and this is the CASE version
SELECT SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) totalActive,
SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) totalInactive
FROM tableName
SQLFiddle Demo