MySQL - Different WHERE clauses to same column - mysql

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

Related

MySQL Pivot Prepare Statement AS Integer error

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 need to create several columns from several rows

I have a MYSQL table I use from another plugin. The table has two fields called element_value and element_label.
element_value contains the name, address, city, state and zip of 2500 people for which I want to create a report. But, I want to take each row and create it's own column.
For example, the table looks like this:
**element_value**
John Smith
100 Elm Street
Columbus
Ohio
13579
element_label is used as an index. element_label 1 relates to the name, 2 the address, 3 the city, etc.
I want to be able to diplay the data in columns:
Name Address City State Zip
John Smith 100 Elm Street Columbus Ohio 13579
Can anyone think of how to use a MYSQL statement to do this?
This type of transformation from rows to columns is called a pivot. MySQL does not have an pivot function but this can be replicated using an aggregate function with a CASE expression.
You can use the following:
select
max(case when element_label = 'Name' then element_value end) Name,
max(case when element_label = 'Address' then element_value end) Address,
max(case when element_label = 'State' then element_value end) State,
max(case when element_label = 'City' then element_value end) City,
max(case when element_label = 'Zip' then element_value end) Zip
from yt
See SQL Fiddle with Demo.
If you had an unknown number of values that you want to convert to columns, then you can use a prepared statement to generate dynamic sql:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(CASE WHEN element_label = ''',
element_label,
''' THEN element_value else null END) AS `',
element_label, '`'
)
) INTO #sql
FROM yt;
SET #sql
= CONCAT('SELECT ', #sql, '
from yt
');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo. Both queries give the result:
| NAME | ADDRESS | CITY | STATE | ZIP |
----------------------------------------------------------
| John Smith | 100 Elm Street | Columbus | Ohio | 13579 |
Typically, you will have a column that you will group by as well. For example, if you have a record_id column, then you will add a GROUP BY record_id, so you will return more than one row of results.

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 |

Multi-Join Query, Pivot? Troubles

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

Reforming table structure of columns possible?

I have bee at this all day. I have played around with a lot of MySQL code and I am either inept, or not very good at coding..
I have a table
name id type amount
=================================
apple 21 cars 67
apple 21 bikes 85
apple 21 skates 557
apple 21 pajs 56
orange 34 bikes 345
orange 34 disks 678
orange 34 cars 234
orange 34 pajs 5678
I want to write a query that will bring back the table in this form
name id cars bikes skates pajas disks
=========================================
apple 21 67 85 557 56 0
orange 34 234 345 0 5678 678
I really just have no clue where to start. Sorry if this is noobie question but MySQL is really hard to conceptualize sometimes.
Try using PreparedStatement
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(ID),MAX(case when type = ''',
type,
''' then amount ELSE NULL end) AS ',
type
)
) INTO #sql
FROM table1;
SET #sql = CONCAT('SELECT name, ', #sql, '
FROM table1 GROUP BY name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SQLFiddle Demo
Alternatively,
SELECT name,
MAX(ID) ID,
MAX(case when type = 'cars' THEN amount ELSE NULL end) AS cars,
MAX(case when type = 'bikes' THEN amount ELSE NULL end) AS bikes,
MAX(case when type = 'skates' THEN amount ELSE NULL end) AS skates,
MAX(case when type = 'pajas' THEN amount ELSE NULL end) AS pajas,
MAX(case when type = 'disks' THEN amount ELSE NULL end) AS disks
FROM table1
GROUP BY name;
SQLFiddle Demo