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 |
Related
Probably a very basic error but here is my problem.
There are users who are giving likes to certain pages of a given document and my aim is to return a breakdown of pages liked per user
Here are (a simplified view of) the two following tables:
User table
id name
---------
1 Jim
2 John
Vote table
userid pageno voteup
1 1 1
1 2 1
2 1 1
2 2 1
2 3 1
My desired output would be the following:
id name Page 1 Page 2 Page 3
1 Jim 1 1 0
2 John 1 1 1
I've made my prepared statement as followed. My aim is to display 'Page 1', 'Page 2' and so on for the column names instead of the 'test' below but as my pageno field is an int i fail in formatting the column name. I have tried various things but with no luck.
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(v.pageno = ',
pageno,
', v.voteup, 0)) AS ',
'test'
)
) INTO #sql
FROM vote;
SET #sql = CONCAT('SELECT u.id, u.name, ', #sql, '
FROM user u
LEFT JOIN vote AS v
ON u.id = v.id
GROUP BY u.id');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
I'm sure this is something very basic I am missing. Could you help?
Thanks in advance
Link to SQLFiddle
This will do the trick although is not dynamic, if you need a dynamic solution.....
select
n.id,
n.name,
CASE WHEN v.pageno=1 THEN sum(v.voteup) ELSE NULL END as "Page 1"
CASE WHEN v.pageno=2 THEN sum(v.voteup) ELSE NULL END as "Page 2"
from votetable as v
join usertable as n on n.id = v.userid
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
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
I have a situation where I need to create columns depending on their content.
For instance, here is SQLFiddle - http://sqlfiddle.com/#!2/0ec7a/1.
I need to get a result like this:
--------------------------
| CITY | MALES | FEMALES |
--------------------------
| NY | 5 | 2 |
--------------------------
| DC | 2 | 1 |
--------------------------
How do I go about this?
I'm looking at CASE WHEN statements and IF statements from the MySQL Manual, but a clearer explanation would be very useful.
You don't even need CASE!
SELECT
city,
sum(gender = 'm') as males,
sum(gender = 'f') as females
FROM Population
group by city
See this working in SQLFiddle.
The reason this works is that in mysql, true is 1 and false is 0, so summing a condition counts how many times it was true!
For (most?) other databases, you must use the boring case inside the sum: sum(case when gender = 'm' then 1 else 0 end) etc
This type of data layout is called a "pivot". Some databases, like Oracle, support it natively through specific extensions to its flavour of SQL, but in mysql you have to "roll your own".
SELECT CITY,
SUM(CASE WHEN GENDER = 'M' THEN 1 ELSE 0 END) MALE,
SUM(CASE WHEN GENDER = 'F' THEN 1 ELSE 0 END) FEMALE
FROM Population
GROUP BY City
SQLFiddle Demo
You can also do prepared statement
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'SUM(CASE WHEN GENDER = ''',
GENDER,
''' then 1 ELSE 0 end) AS ',
GENDER
)
) INTO #sql
FROM Population;
SET #sql = CONCAT('SELECT CITY, ', #sql, '
FROM Population
GROUP BY City');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SQLFiddle Demo
Thank you in advance for anyone who helps with this. I know i've seen this done before without too much pain but cant seem to find the solution
My database looks something like:
`tbl_user:
----------
id ( pkey )
email
fName
lName
tbl_userSparseType:
-------------------
id ( pkey )
varName
displayName
tbl_userSparse:
---------------
id ( pkey )
value ( Value of Sparse Type )
user_id ( => tbl_user.id )
userSparseType_id ( => tbl_userSparseType.id )
with sample data:
tbl_user:
(id, email, fName, lName)
1 Bob#example.com Billy Bob
2 Betty#example.com Betty Sue
3 Beam#example.com Jim Beam
tbl_userSparseType:
(id, varName, displayName)
1 fullName Full Name
2 dayPhone Day Phone
3 nightPhone Night Phone
4 cellPhone Cell Phone
5 homeAddr Home Address
tbl_userSparse:
(id, value, user_id, userSparseType_id)
1 Billy John Bob 1 1
2 James B. Beam 3 1
3 123-234-3456 1 2
4 234-345-4567 1 4
5 24 Best st. 2 5
6 456-567-6789 3 3
I tried doing two left joins, but this gave me a tbl_user row for each sparse entry like:
(id, email, fName, lName, displayName, value)
1,"Bob#example.com","Billy","Bob","Full Name","Billy John Bob"
1,"Bob#example.com","Billy","Bob","Day Phone","123-234-3456"
1,"Bob#example.com","Billy","Bob","Cell Phone","234-345-4567"
And despite a few 45 or so minute sessions of looking, I cant find a way to get something more like the following without explicitly naming the columns, I need a dynamic way to only pull all display names that apply to the subset of tbl_user rows being queried:
WHERE tbl_user.id IN (1,2)
id | email | fName | lName | Full Name, | Day Phone | Cell Phone |
Home Address
-------------------------------------------------------------------------------------------------------
1 | Bob#example.com | Billy | Bob | Billy John Bob | 123-234-3456 | 234-345-4567 |
2 | Betty#example.com | Betty | Sue | | | | 24 Best St.
Thanks again in advance, I'm hoping this can be done without too much fuss. :\
Unfortunately MySQL does not have a PIVOT function which is basically what you are trying to do. So you will need to use an aggregate function with a CASE statement. If you know the number of columns, then you can hard-code the values:
select u.id,
u.email,
u.fname,
u.lname,
max(case when t.displayname = 'Full Name' then us.value end) FullName,
max(case when t.displayname = 'Day Phone' then us.value end) DayPhone,
max(case when t.displayname = 'Cell Phone' then us.value end) CellPhone,
max(case when t.displayname = 'Home Address' then us.value end) HOmeAddress
from tbl_user u
left join tbl_userSparse us
on u.id = us.user_id
left join tbl_userSparseType t
on us.userSparseType_id = t.id
where u.id in (1, 2)
group by u.id, u.email, u.fname,u.lname;
See SQL Fiddle With Demo
Now if you want to perform this dynamically, meaning you do not know ahead of time the columns to transpose, then you should review the following article:
Dynamic pivot tables (transform rows to columns)
Your code would look like this:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(case when t.displayname = ''',
t.displayname,
''' then us.value end) AS ',
replace(t.displayname, ' ', '')
)
) INTO #sql
FROM tbl_userSparse us
left join tbl_userSparseType t
on us.userSparseType_id = t.id;
SET #sql = CONCAT('SELECT u.id, u.email, u.fname, u.lname, ', #sql, '
from tbl_user u
left join tbl_userSparse us
on u.id = us.user_id
left join tbl_userSparseType t
on us.userSparseType_id = t.id
where u.id in (1, 2)
group by u.id, u.email, u.fname, u.lname');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo