Get some rows as columns - mysql

I have 2 tables:
table1:
|id|create_date|title |type |
-------------------------------
| 1|2015-07-20 |bla-bla|TICKET|
table2_meta:
|id|table1_id|meta_name|meta_value|
-----------------------------------
| 1| 1 | status | new |
| 2| 1 | priority| low |
| 3| 1 | owner | alex |
Now, i wish to select it like this:
|id|create_date|title |status|priority|owner|
|1 |2015-07-20 |bla-bla|new |low |alex |
My solution is:
SELECT
t1.id,
t1.event_date,
case when m.meta_name="status" then m.meta_value end as status,
case when m.meta_name="priority" then m.meta_value end as priority,
case when m.meta_name="owner" then m.meta_value end as owner
FROM table1 as t1
LEFT JOIN table2_meta as m
on m.table1_id=t1.id
WHERE t1.type='TICKET'
GROUP BY t1.id
So, it works. But seems a litle bit ugly.
My question is:
Are there any other solutions to this select and how to make it more productive?

Hey try this in your case for pivoting of table SQLFIDDLE
set #sql = null;
select
group_concat(distinct
concat(
'max(case when meta_name = ''',
meta_name,
''' then value end) AS ',
concat(meta_name)
)
) into #sql
FROM table1 as t1
LEFT JOIN table2 as m
on m.table1_id=t1.id
WHERE t1.type='TICKET';
set #sql = concat('select t1.id,', #sql, ' FROM table1 as t1
LEFT JOIN table2 as m
on m.table1_id=t1.id
WHERE t1.type=''TICKET''
GROUP BY t1.id
');
prepare stmt from #sql;
execute stmt;
deallocate prepare stmt;

In oracle you could:
Instead of case when you could use pivot.
SELECT * FROM table2
PIVOT (
meta_value FOR (meta_name) IN ('Status' AS status, 'Priority' AS priority, 'owner' AS Ownser))

Related

MySQL Self Join Table

I have a table named Inventory with the following structure:
Location_ID |Item_ID |Stock
1 |A |100
1 |B |500
1 |C |300
2 |A |10
2 |B |20
field location_ID and item_ID are composite key. I want to produce the following data from that single table:
Item_ID |Stock_1 |Stock_2
A |100 |10
B |500 |20
C |300 |0
I tried writing several self join queries but it doesn't work. There is also another problem: Item_ID C does not exist on location_ID 2. How can we put the value '0' on the resulting table if it does not exist? Can someone with brighter mind shed any light?
select DIS_ITME_ID,
IFNULL ((select stock
from inventory
where location_id = 1
and item_id = DIS_ITEM_ID), 0) as stock_1,
IFNULL ((select stock
from inventory
where location_id = 2
and item_id = DIS_ITEM_ID), 0) as stock_2
from (select distinct item_ID as DIS_ITEM_ID from inventory)
I know it's probably too late but there is a simpler way:
SELECT Item_Id,
SUM(
CASE WHEN Location_ID = 1 THEN
Stock
ELSE
0
END) As Stock1,
SUM(
CASE WHEN Location_ID = 2 THEN
Stock
ELSE
0
END) As Stock2
FROM Inventory
GROUP BY Item_Id
sqlfiddle
You can use a dinamic pivot query:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'SUM(IF(i.location_ID = ''',
CAST(location_ID as CHAR(50)),
''', i.stock, 0)) AS stock_',
CAST(location_ID as CHAR(50))
)
) INTO #sql
FROM inventory;
SET #sql = CONCAT('SELECT i.item_ID, ', #sql, '
FROM inventory i
GROUP BY i.item_ID');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
You can find more info here: http://buysql.com/mysql/14-how-to-automate-pivot-tables.html

MySQL - Select Query with junction table to one row

i have the following tables:
**entries**
entry_id | date | engineer | project
**entries_allowanes_map**
entry_id | allowance_id
**allowances**
allowance_id | allowance_name
I want to create a SELECT query that will give the following result:
entry_id | date | engineer | project | allowance_name1 | allowance_name2 | allowance_name_n...
The queries I have tried return a row for each allowance an entry has registered with. I want just one row with all allowances attached to it.
Thanks in advance
I would propose doing this with group_concat(). It doesn't put the values in separate columns, but it does put everything for a given entry on one row:
select e.entry_id, e.date, e.engineer, e.project,
group_concat(a.allowance_name) as allowances
from entries e join
entries_allowances_map f
on e.entry_id = eam.entry_id
allowances a
on eam.allowance_id = a.allowance_id
group by e.entry_id;
Here is the query that I got:
It outputs your expected results in different columns:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'(SELECT max(CASE WHEN AL.ALLOWANCE_ID = ''',
ALLOWANCE_ID,
''' THEN 1 END) AS `',
ALLOWANCE_ID, '` FROM entries_allowanes_map AL WHERE E.ENTRY_ID = AL.ENTRY_ID ) AS `',
ALLOWANCE_NAME, '`'
)
) INTO #sql
FROM allowances;
SET #sql
= CONCAT('SELECT E.ENTRY_ID, E.DATE, E.ENGINEER, E.PROJECT, ', #sql, '
FROM entries as e');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Here is the SqlFiddle
Try
SELECT * FROM entries as e
INNER JOIN entries_allowanes_map as a ON e.entry_id=e.entry_id
INNER JOIN allownces as al ON al.allownce_id=a.allowance_id

MySQL create columns out of rows on the fly

My issue is that I have a table apidata that holds multiple rows of data for each domain. So when I query apidata I naturally get multiple rows as a result. Is there any way to turn those rows into columns? I ask because I'm already using a query to pull the domain data (page title, URL, top level domain, ip address etc) and I need to add the api data with it. I believe I have to do this in two queries but I would love to at least have one row per domain to make the query and loop as fast a possible.
So the question is, can I create columns out of rows on the fly?
Heres a SQL Fiddle => http://sqlfiddle.com/#!2/8e408/4
(Note, I didnt put the whole database in the fiddle just the tables that effect the query. If you think somethings missing that you need, let me know.)
Tool_Runs (id_sha is the main lookup value for tool runs)
| ID | ID_SHA |
+----+------------------------------------------+
| 1 | 68300DF58B2A8A6E098CB0B3D1A9AE80BBE5897A |
Domains (Run_id is FK to tool_runs.id)
| ID | RUN_ID |
+----+--------+
| 1 | 1 |
API Data
| ID | DOMAIN_ID | EXPORT_COLUMN | COLUMN_TITLE | VALUE |
+----+-----------+------------------+-------------------+-------+
| 1 | 1 | referringDomains | Referring Domains | 10 |
+----+-----------+------------------+-------------------+-------+
| 2 | 1 | linkCount | Backlink Count | 55 |
Heres my query now:
SELECT a.domain_id, a.export_column, a.column_title, a.value
FROM apidata AS a WHERE domain_id IN
(
SELECT d.id FROM tool_runs AS t
JOIN domains AS d ON d.run_id = t.id
WHERE id_sha = '68300DF58B2A8A6E098CB0B3D1A9AE80BBE5897A'
)
ORDER BY a.domain_id
And what I get is:
| DOMAIN_ID | EXPORT_COLUMN | COLUMN_TITLE | VALUE |
+-----------+------------------+-------------------+----------+
| 1 | referringDomains | Referring Domains | 10 |
+-----------+------------------+-------------------+----------+
| 1 | linkCount | Backlink Count | 55 |
But what I want is
| DOMAIN_ID | referringDomains | referringDomains_TITLE | linkCount | linkCount_TITLE |
+-----------+------------------+------------------------+-----------+-----------------+
| 1 | 10 | Referring Domains | 55 | Backlink Count |
What you are trying to is to pivot the table rows into columns. Unfortunately MySQL doesn't have a native pivot table operator, but you can use the CASE expression to do so:
SELECT
a.Domain_id,
MAX(CASE WHEN a.export_column = 'referringDomains' THEN a.value END) AS referringDomains,
MAX(CASE WHEN a.export_column = 'referringDomains' THEN a.column_title END) AS referringDomains_TITLE,
MAX(CASE WHEN a.export_column = 'linkCount' THEN a.value END) AS linkCount,
MAX(CASE WHEN a.export_column = 'linkCount' THEN a.column_title END) AS linkCount_TITLE
FROM apidata AS a
WHERE domain_id IN
(
SELECT d.id FROM tool_runs AS t
JOIN domains AS d ON d.run_id = t.id
WHERE id_sha = '68300DF58B2A8A6E098CB0B3D1A9AE80BBE5897A'
)
GROUP BY a.domain_id;
Updated SQL Fiddle Demo
Note that: If you want to do so for all the values in the export_column, you have to write a CASE expression for each value. But you can do that using dynamic sql like this:
SET #ecvalues = NULL;
SET #ectitles = NULL;
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT CONCAT('MAX(IF(a.export_column = ''',
a.export_column, ''', a.value , NULL)) AS ', '''', a.export_column , '''')
) INTO #ecvalues
FROM apidata a;
SELECT
GROUP_CONCAT(DISTINCT CONCAT('MAX(IF(a.export_column = ''',
a.export_column, ''', column_title , NULL)) AS ', '''', CONCAT(a.export_column , '_Titles'), '''')
) INTO #ectitles
FROM apidata a;
SET #sql = CONCAT('SELECT
a.Domain_id, ', #ectitles , ',', #ecvalues, '
FROM apidata AS a
WHERE domain_id IN
(
SELECT d.id FROM tool_runs AS t
JOIN domains AS d ON d.run_id = t.id
WHERE id_sha = ''68300DF58B2A8A6E098CB0B3D1A9AE80BBE5897A''
)
GROUP BY a.domain_id;');
prepare stmt
FROM #sql;
execute stmt;
You can put that query inside a stored procedure.
Updated SQL Fiddle Demo
Just as a complement to the #MahmoudGamal answer you should know that for any new registry (EXPORT_COLUMN) you will have to add a new case statement.
So in order to do it dynamic you can create a procedure as described on this post at dba.stackexchange.
How to transpose/convert rows as columns in mysql
It shows how to do it dynamically.
If you want columns, go ahead and pivot as the example above. If you only want a single string, for some reporting reason, go ahead and do:
SELECT group_concat(CONCAT_WS(' ',a.domain_id, a.value, a.column_title, a.export_column, 'next row string separator'))
FROM apidata AS a WHERE domain_id IN
(
SELECT d.id FROM tool_runs AS t
JOIN domains AS d ON d.run_id = t.id
WHERE id_sha = '68300DF58B2A8A6E098CB0B3D1A9AE80BBE5897A'
)
ORDER BY a.domain_id

MySQL how to adress columns that are generated in a pivot table to sum their values

SOLVED
I'am so sorry guys, it was so easy, but workday smashed my brain.
I just had to made the sum on the basis of id to get what i need, like
this:
SET #sql = CONCAT('SELECT tt.Id, SUM(tt.Values) AS TOTAL, ' #sql, ' FROM db.Table tt GROUP BY tt.Id');
Thank you for help!
Your help is really appreciated :)
I have a generated table of the following a look:
id | key1 | key2 | ... | keyN
id_val1 | k1_val1 | k2_val1 | ... | kN_val1
... | ... | ... | ... | ...
id_valM | k1_valM | k2_valM | ... | kN_valM
it is generated with the code:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(tt.Keys = ''',
tt.Keys,
''', tt.Values, NULL)) AS ',
tt.Keys
)
) INTO #sql
FROM db.Table tt;
SET #sql = CONCAT('SELECT tt.Id, ', #sql, ' FROM db.Table tt GROUP BY tt.Id WITH ROLLUP');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
What I have to do is to add Total column, like this:
id | key1 | key2 | ... | keyN | Total
id_val1 | k1_val1 | k2_val1 | ... | kN_val1 | sum(k1_val1, k2_val1,...,kN_val1)
... | ... | ... | ... | ... | ...
id_valM | k1_valM | k2_valM | ... | kN_valM | sum(k1_valM, k2_valM,...,kN_valM)
"WITH ROLLUP" didn't work though, it only added another row with max values in columns, while I need sums in rows.
The problem is the fact that number of columns and their names may differ through time. And using their names properly is the problem itself :).
For example i got the names of columns, but how to use them, as they don't belong to any table? I can convert dynamically built sql to the following look. But can you tell me how the #xxx should look:
SET #sql = CONCAT('SELECT tt.Id, SUM(', #xxx,') AS TOTAL, ' #sql, ' FROM db.Table tt GROUP BY tt.Id WITH ROLLUP');
Thanks in advance,
Michael.
If I am interpreting this correctly, I think this is what you want to do. You aren't actually doing a sum, correct? You are adding all of the field values for each row. The second GROUP_CONCAT here should create the list of fields with plus signs (+) in between them. Give it a shot.
SET #sql = NULL;
SET #sql2 = NULL;
SELECT GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(tt.Keys = ''',
tt.Keys,
''', tt.Values, NULL)) AS ',
tt.Keys
)
) INTO #sql,
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(tt.Keys = ''',
tt.Keys,
''', tt.Values, NULL))'
)
SEPARATOR '+'
) INTO #sql2
FROM db.Table tt;
SET #sql = CONCAT('SELECT tt.Id, ', #sql, ', ', #sql2,' AS Total FROM db.Table tt GROUP BY tt.Id');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

Pivot Table Using MySQL

I have two tables Triples and Tags
Triples Table has the following Columns
id PostID TagID Value
1 1 1 Murder
2 1 2 New Brunswick
3 2 1 Theft
4 2 3 Gun
Tags Table has the following Columns
id TagName
1 Incident
2 Location
3 Weapon
I am trying to write sql to create a Pivot Table with Dynamic Headers
Output should be like this
PostID Incident Location Weapon
1 Murder New Brunswick
2 Theft Gun
Any help in writing the SQL would be appreciated. I have seen examples online but could not figure out this one
In order to pivot the data in MySQL, you will need to use both an aggregate function and a CASE expression.
If you have a known number of columns, then you can hard-code the query:
select p.postid,
max(case when t.tagname = 'Incident' then p.value end) Incident,
max(case when t.tagname = 'Location' then p.value end) Location,
max(case when t.tagname = 'Weapon' then p.value end) Weapon
from triples p
left join tags t
on p.tagid = t.id
group by p.postid;
See SQL Fiddle with Demo
But if you have an unknown number of columns, then you will need to use a prepared statement to generate dynamic SQL:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(CASE WHEN TagName = ''',
TagName,
''' THEN p.value END) AS `',
TagName, '`'
)
) INTO #sql
FROM tags;
SET #sql
= CONCAT('SELECT p.postid, ', #sql, '
from triples p
left join tags t
on p.tagid = t.id
group by p.postid');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo.
Both will give the result:
| POSTID | INCIDENT | LOCATION | WEAPON |
----------------------------------------------
| 1 | Murder | New Brunswick | (null) |
| 2 | Theft | (null) | Gun |