Related
I tried to search posts, but I only found solutions for SQL Server/Access. I need a solution in MySQL (5.X).
I have a table (called history) with 3 columns: hostid, itemname, itemvalue.
If I do a select (select * from history), it will return
+--------+----------+-----------+
| hostid | itemname | itemvalue |
+--------+----------+-----------+
| 1 | A | 10 |
+--------+----------+-----------+
| 1 | B | 3 |
+--------+----------+-----------+
| 2 | A | 9 |
+--------+----------+-----------+
| 2 | C | 40 |
+--------+----------+-----------+
How do I query the database to return something like
+--------+------+-----+-----+
| hostid | A | B | C |
+--------+------+-----+-----+
| 1 | 10 | 3 | 0 |
+--------+------+-----+-----+
| 2 | 9 | 0 | 40 |
+--------+------+-----+-----+
I'm going to add a somewhat longer and more detailed explanation of the steps to take to solve this problem. I apologize if it's too long.
I'll start out with the base you've given and use it to define a couple of terms that I'll use for the rest of this post. This will be the base table:
select * from history;
+--------+----------+-----------+
| hostid | itemname | itemvalue |
+--------+----------+-----------+
| 1 | A | 10 |
| 1 | B | 3 |
| 2 | A | 9 |
| 2 | C | 40 |
+--------+----------+-----------+
This will be our goal, the pretty pivot table:
select * from history_itemvalue_pivot;
+--------+------+------+------+
| hostid | A | B | C |
+--------+------+------+------+
| 1 | 10 | 3 | 0 |
| 2 | 9 | 0 | 40 |
+--------+------+------+------+
Values in the history.hostid column will become y-values in the pivot table. Values in the history.itemname column will become x-values (for obvious reasons).
When I have to solve the problem of creating a pivot table, I tackle it using a three-step process (with an optional fourth step):
select the columns of interest, i.e. y-values and x-values
extend the base table with extra columns -- one for each x-value
group and aggregate the extended table -- one group for each y-value
(optional) prettify the aggregated table
Let's apply these steps to your problem and see what we get:
Step 1: select columns of interest. In the desired result, hostid provides the y-values and itemname provides the x-values.
Step 2: extend the base table with extra columns. We typically need one column per x-value. Recall that our x-value column is itemname:
create view history_extended as (
select
history.*,
case when itemname = "A" then itemvalue end as A,
case when itemname = "B" then itemvalue end as B,
case when itemname = "C" then itemvalue end as C
from history
);
select * from history_extended;
+--------+----------+-----------+------+------+------+
| hostid | itemname | itemvalue | A | B | C |
+--------+----------+-----------+------+------+------+
| 1 | A | 10 | 10 | NULL | NULL |
| 1 | B | 3 | NULL | 3 | NULL |
| 2 | A | 9 | 9 | NULL | NULL |
| 2 | C | 40 | NULL | NULL | 40 |
+--------+----------+-----------+------+------+------+
Note that we didn't change the number of rows -- we just added extra columns. Also note the pattern of NULLs -- a row with itemname = "A" has a non-null value for new column A, and null values for the other new columns.
Step 3: group and aggregate the extended table. We need to group by hostid, since it provides the y-values:
create view history_itemvalue_pivot as (
select
hostid,
sum(A) as A,
sum(B) as B,
sum(C) as C
from history_extended
group by hostid
);
select * from history_itemvalue_pivot;
+--------+------+------+------+
| hostid | A | B | C |
+--------+------+------+------+
| 1 | 10 | 3 | NULL |
| 2 | 9 | NULL | 40 |
+--------+------+------+------+
(Note that we now have one row per y-value.) Okay, we're almost there! We just need to get rid of those ugly NULLs.
Step 4: prettify. We're just going to replace any null values with zeroes so the result set is nicer to look at:
create view history_itemvalue_pivot_pretty as (
select
hostid,
coalesce(A, 0) as A,
coalesce(B, 0) as B,
coalesce(C, 0) as C
from history_itemvalue_pivot
);
select * from history_itemvalue_pivot_pretty;
+--------+------+------+------+
| hostid | A | B | C |
+--------+------+------+------+
| 1 | 10 | 3 | 0 |
| 2 | 9 | 0 | 40 |
+--------+------+------+------+
And we're done -- we've built a nice, pretty pivot table using MySQL.
Considerations when applying this procedure:
what value to use in the extra columns. I used itemvalue in this example
what "neutral" value to use in the extra columns. I used NULL, but it could also be 0 or "", depending on your exact situation
what aggregate function to use when grouping. I used sum, but count and max are also often used (max is often used when building one-row "objects" that had been spread across many rows)
using multiple columns for y-values. This solution isn't limited to using a single column for the y-values -- just plug the extra columns into the group by clause (and don't forget to select them)
Known limitations:
this solution doesn't allow n columns in the pivot table -- each pivot column needs to be manually added when extending the base table. So for 5 or 10 x-values, this solution is nice. For 100, not so nice. There are some solutions with stored procedures generating a query, but they're ugly and difficult to get right. I currently don't know of a good way to solve this problem when the pivot table needs to have lots of columns.
SELECT
hostid,
sum( if( itemname = 'A', itemvalue, 0 ) ) AS A,
sum( if( itemname = 'B', itemvalue, 0 ) ) AS B,
sum( if( itemname = 'C', itemvalue, 0 ) ) AS C
FROM
bob
GROUP BY
hostid;
Another option,especially useful if you have many items you need to pivot is to let mysql build the query for you:
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'ifnull(SUM(case when itemname = ''',
itemname,
''' then itemvalue end),0) AS `',
itemname, '`'
)
) INTO #sql
FROM
history;
SET #sql = CONCAT('SELECT hostid, ', #sql, '
FROM history
GROUP BY hostid');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
FIDDLE
Added some extra values to see it working
GROUP_CONCAT has a default value of 1000 so if you have a really big query change this parameter before running it
SET SESSION group_concat_max_len = 1000000;
Test:
DROP TABLE IF EXISTS history;
CREATE TABLE history
(hostid INT,
itemname VARCHAR(5),
itemvalue INT);
INSERT INTO history VALUES(1,'A',10),(1,'B',3),(2,'A',9),
(2,'C',40),(2,'D',5),
(3,'A',14),(3,'B',67),(3,'D',8);
hostid A B C D
1 10 3 0 0
2 9 0 40 5
3 14 67 0 8
Taking advantage of Matt Fenwick's idea that helped me to solve the problem (a lot of thanks), let's reduce it to only one query:
select
history.*,
coalesce(sum(case when itemname = "A" then itemvalue end), 0) as A,
coalesce(sum(case when itemname = "B" then itemvalue end), 0) as B,
coalesce(sum(case when itemname = "C" then itemvalue end), 0) as C
from history
group by hostid
I edit Agung Sagita's answer from subquery to join.
I'm not sure about how much difference between this 2 way, but just for another reference.
SELECT hostid, T2.VALUE AS A, T3.VALUE AS B, T4.VALUE AS C
FROM TableTest AS T1
LEFT JOIN TableTest T2 ON T2.hostid=T1.hostid AND T2.ITEMNAME='A'
LEFT JOIN TableTest T3 ON T3.hostid=T1.hostid AND T3.ITEMNAME='B'
LEFT JOIN TableTest T4 ON T4.hostid=T1.hostid AND T4.ITEMNAME='C'
use subquery
SELECT hostid,
(SELECT VALUE FROM TableTest WHERE ITEMNAME='A' AND hostid = t1.hostid) AS A,
(SELECT VALUE FROM TableTest WHERE ITEMNAME='B' AND hostid = t1.hostid) AS B,
(SELECT VALUE FROM TableTest WHERE ITEMNAME='C' AND hostid = t1.hostid) AS C
FROM TableTest AS T1
GROUP BY hostid
but it will be a problem if sub query resulting more than a row, use further aggregate function in the subquery
If you could use MariaDB there is a very very easy solution.
Since MariaDB-10.02 there has been added a new storage engine called CONNECT that can help us to convert the results of another query or table into a pivot table, just like what you want:
You can have a look at the docs.
First of all install the connect storage engine.
Now the pivot column of our table is itemname and the data for each item is located in itemvalue column, so we can have the result pivot table using this query:
create table pivot_table
engine=connect table_type=pivot tabname=history
option_list='PivotCol=itemname,FncCol=itemvalue';
Now we can select what we want from the pivot_table:
select * from pivot_table
More details here
My solution :
select h.hostid, sum(ifnull(h.A,0)) as A, sum(ifnull(h.B,0)) as B, sum(ifnull(h.C,0)) as C from (
select
hostid,
case when itemName = 'A' then itemvalue end as A,
case when itemName = 'B' then itemvalue end as B,
case when itemName = 'C' then itemvalue end as C
from history
) h group by hostid
It produces the expected results in the submitted case.
I make that into Group By hostId then it will show only first row with values,
like:
A B C
1 10
2 3
I figure out one way to make my reports converting rows to columns almost dynamic using simple querys. You can see and test it online here.
The number of columns of query is fixed but the values are dynamic and based on values of rows. You can build it So, I use one query to build the table header and another one to see the values:
SELECT distinct concat('<th>',itemname,'</th>') as column_name_table_header FROM history order by 1;
SELECT
hostid
,(case when itemname = (select distinct itemname from history a order by 1 limit 0,1) then itemvalue else '' end) as col1
,(case when itemname = (select distinct itemname from history a order by 1 limit 1,1) then itemvalue else '' end) as col2
,(case when itemname = (select distinct itemname from history a order by 1 limit 2,1) then itemvalue else '' end) as col3
,(case when itemname = (select distinct itemname from history a order by 1 limit 3,1) then itemvalue else '' end) as col4
FROM history order by 1;
You can summarize it, too:
SELECT
hostid
,sum(case when itemname = (select distinct itemname from history a order by 1 limit 0,1) then itemvalue end) as A
,sum(case when itemname = (select distinct itemname from history a order by 1 limit 1,1) then itemvalue end) as B
,sum(case when itemname = (select distinct itemname from history a order by 1 limit 2,1) then itemvalue end) as C
FROM history group by hostid order by 1;
+--------+------+------+------+
| hostid | A | B | C |
+--------+------+------+------+
| 1 | 10 | 3 | NULL |
| 2 | 9 | NULL | 40 |
+--------+------+------+------+
Results of RexTester:
http://rextester.com/ZSWKS28923
For one real example of use, this report bellow show in columns the hours of departures arrivals of boat/bus with a visual schedule. You will see one additional column not used at the last col without confuse the visualization:
** ticketing system to of sell ticket online and presential
This isn't the exact answer you are looking for but it was a solution that i needed on my project and hope this helps someone. This will list 1 to n row items separated by commas. Group_Concat makes this possible in MySQL.
select
cemetery.cemetery_id as "Cemetery_ID",
GROUP_CONCAT(distinct(names.name)) as "Cemetery_Name",
cemetery.latitude as Latitude,
cemetery.longitude as Longitude,
c.Contact_Info,
d.Direction_Type,
d.Directions
from cemetery
left join cemetery_names on cemetery.cemetery_id = cemetery_names.cemetery_id
left join names on cemetery_names.name_id = names.name_id
left join cemetery_contact on cemetery.cemetery_id = cemetery_contact.cemetery_id
left join
(
select
cemetery_contact.cemetery_id as cID,
group_concat(contacts.name, char(32), phone.number) as Contact_Info
from cemetery_contact
left join contacts on cemetery_contact.contact_id = contacts.contact_id
left join phone on cemetery_contact.contact_id = phone.contact_id
group by cID
)
as c on c.cID = cemetery.cemetery_id
left join
(
select
cemetery_id as dID,
group_concat(direction_type.direction_type) as Direction_Type,
group_concat(directions.value , char(13), char(9)) as Directions
from directions
left join direction_type on directions.type = direction_type.direction_type_id
group by dID
)
as d on d.dID = cemetery.cemetery_id
group by Cemetery_ID
This cemetery has two common names so the names are listed in different rows connected by a single id but two name ids and the query produces something like this
CemeteryID Cemetery_Name Latitude
1 Appleton,Sulpher Springs 35.4276242832293
You can use a couple of LEFT JOINs. Kindly use this code
SELECT t.hostid,
COALESCE(t1.itemvalue, 0) A,
COALESCE(t2.itemvalue, 0) B,
COALESCE(t3.itemvalue, 0) C
FROM history t
LEFT JOIN history t1
ON t1.hostid = t.hostid
AND t1.itemname = 'A'
LEFT JOIN history t2
ON t2.hostid = t.hostid
AND t2.itemname = 'B'
LEFT JOIN history t3
ON t3.hostid = t.hostid
AND t3.itemname = 'C'
GROUP BY t.hostid
I'm sorry to say this and maybe I'm not solving your problem exactly but PostgreSQL is 10 years older than MySQL and is extremely advanced compared to MySQL and there's many ways to achieve this easily. Install PostgreSQL and execute this query
CREATE EXTENSION tablefunc;
then voila! And here's extensive documentation: PostgreSQL: Documentation: 9.1: tablefunc or this query
CREATE EXTENSION hstore;
then again voila! PostgreSQL: Documentation: 9.0: hstore
How we can show category and subcategory in one column in sql. Both columns are present in same table TableA.
Example: TableA
--------------------------------------
| category | subcategory | Values |
--------------------------------------
| Bird | parrot | 5 |
| Bird | pigeon | 10 |
| Animal | lion | 2 |
| Animal | Tiger | 5 |
--------------------------------------
Output table :
-------------------
| NEW | Value |
--------------------
| Bird | 15 |
| parrot | 5 |
| Piegon | 10 |
| Animal | 7 |
| lion | 2 |
| Tiger | 5 |
--------------------
In the output, New is a column where I want both category and sub category.
Sample Query to generate data:
CREATE TABLE #TEMP
(
catgory nvarchar(200),
sub_category nvarchar(200),
[values] nvarchar(200),
)
INSERT INTO #TEMP VALUES ('Bird','parrot',5)
INSERT INTO #TEMP VALUES ('Bird','pigeon',10)
INSERT INTO #TEMP VALUES ('Animal','lion',2)
INSERT INTO #TEMP VALUES ('Animal','Tiger',5)
Where the logic is:
I want category and sub category together, Where category should show the sum of all sub category values and should be in order as I have output table
select category, sum(values) values from table group by category
union
select subcategory, values from table
WITH cte AS
(
SELECT category,value FROM yourtable WHERE category IS NOT NULL
UNION ALL
SELECT subcategory, value FROM yourtable WHERE subcategory IS NOT NULL
)
SELECT Company as new, value FROM cte
You can use cte :
with cte as (
select t.*,
dense_rank() over (order by category) as seq,
sum([values]) over (partition by category) as sums
from table t
)
select t.cat as new, (case when cat_name = 'category' then sums else c.[values] end) as Value
from cte c cross apply
( values ('category', category), ('sub_category', sub_category) ) t(cat_name, cat)
order by seq, (case when cat_name = 'category' then 1 else 2 end);
Try this.
select new, [values] from (select catgory, sub_category as 'new', [values] from temp
union all
select catgory, catgory as 'new', sum([values]) from temp group by catgory) order by catgory
Output:
new values
-------------
parrot 5
pigeon 10
Bird 15
lion 2
lion 2
Tiger 5
Animal 9
Needs UNION ALL after getting the sums with the main table:
select
case t.sub_category
when '' then t.category
else sub_category
end new,
t.value
from (
select category, '' sub_category, sum([values]) value from temp group by category
union all
select category, sub_category, [values] value from temp
) t
order by t.category, t.sub_category
See the demo.
Results:
> new | value
> :----- | ----:
> Animal | 7
> lion | 2
> Tiger | 5
> Bird | 15
> parrot | 5
> pigeon | 10
A simple way to do this uses grouping sets:
select coalesce(subcategory, category) as new, sum(val)
from temp
group by grouping sets ( (category), (subcategory, category))
order by category, subcategory
Here is a db<>fiddle.
Table structure:
Id, name, parentcategoryId
1, animals, null
2, fish, 1
Example mssql:
Select name, subcats = STUFF((
SELECT ',' + NAME
FROM category as cat1 where cat1.parentcategoryId = cat. parentcategoryId
FOR XML PATH('')
), 1, 1, '') from category as cat where parentcategoryId = null
Preview:
Name, subcats
Animals, fish
This question already has answers here:
Convert Rows to columns using 'Pivot' in SQL Server
(9 answers)
Closed 7 years ago.
I have a table like this and there are only two feature for all user in this table
+-------+---------+-----------+----------+
| User | Feature | StartDate | EndDate |
+-------+---------+-----------+----------+
| Peter | F1 | 2015/1/1 | 2015/2/1 |
| Peter | F2 | 2015/3/1 | 2015/4/1 |
| John | F1 | 2015/5/1 | 2015/6/1 |
| John | F2 | 2015/7/1 | 2015/8/1 |
+-------+---------+-----------+----------+
I want to transform to
+-------+--------------+------------+--------------+------------+
| User | F1_StartDate | F1_EndDate | F2_StartDate | F2_EndDate |
+-------+--------------+------------+--------------+------------+
| Peter | 2015/1/1 | 2015/2/1 | 2015/3/1 | 2015/4/1 |
| John | 2015/5/1 | 2015/6/1 | 2015/7/1 | 2015/8/1 |
+-------+--------------+------------+--------------+------------+
If you are using SQL Server 2005 or up by any chance, PIVOT is what you are looking for.
The best general way to perform this sort of operation is a simple group by statement. This should work across all major ODBMS:
select user,
max(case when feature='F1' then StartDate else null end) F1_StartDate,
max(case when feature='F1' then EndDate else null end) F1_EndDate,
max(case when feature='F2' then StartDate else null end) F2_StartDate,
max(case when feature='F2' then EndDate else null end) F2_EndDate
from table
group by user
Note: as mentioned in the comments, this is often bad practice, as depending on your needs, it can make the data harder to work with. However, there are cases where it makes sense, when you have a small, limited number of values.
This is a bit of a hack with a CTE:
;WITH CTE AS (
SELECT [User], [Feature] + '_StartDate' AS [Type], StartDate AS [Date]
FROM Table1
UNION ALL
SELECT [User], [Feature] + '_EndDate' AS [Type], EndDate AS [Date]
FROM Table1)
SELECT * FROM CTE
PIVOT(MAX([Date]) FOR [Type] IN ([F1_StartDate],[F2_StartDate], [F1_EndDate], [F2_EndDate])) PIV
Use UNPIVOT & PIVOT like this:
Test data:
DECLARE #t table
(User1 varchar(20),Feature char(2),StartDate date,EndDate date)
INSERT #t values
('Pete','F1','2015/1/1 ','2015/2/1'),
('Pete','F2','2015/3/1 ','2015/4/1'),
('John','F1','2015/5/1 ','2015/6/1'),
('John','F2','2015/7/1 ','2015/8/1')
Query:
;WITH CTE AS
(
SELECT User1, date1, Feature + '_' + Seq cat
FROM #t as p
UNPIVOT
(date1 FOR Seq IN
([StartDate], [EndDate]) ) AS unpvt
)
SELECT * FROM CTE
PIVOT
(MIN(date1)
FOR cat
IN ([F1_StartDate],[F1_EndDate],[F2_StartDate],[F2_EndDate])
) as p
Result:
User1 F1_StartDate F1_EndDate F2_StartDate F2_EndDate
John 2015-05-01 2015-06-01 2015-07-01 2015-08-01
Pete 2015-01-01 2015-02-01 2015-03-01 2015-04-01
I am trying to get a database query back that has some inputs from one table and some from a another table. I know how to do this but my problem is in my query I have to get values from this table:
AID | Meet | Event | Score
--------------------------
1 | Val | Beam | 9.47
1 | Snow | Beam | 7.78
I need to get the score values from the snow meet and the val meet separately so my out put query will look like this:
AID | SnowScore | ValScore
--------------------------
1 | 7.78 | 9.47
I am just very confused on where to start here. Sorry if this is confusing it is very hard to describe. Thank you for any help.
This type of result is pretty roundabout. You have to query each Column separately.
Try this:
SELECT s.AID, s.Score AS SnowScore, v.Score as ValScore
FROM tbl s INNER JOIN tbl v ON (s.AID = v.AID)
WHERE s.Meet = "Snow" AND v.Meet = "Val";
if the column "Score" is nullable, you have to work with outer join instead of inner join.
If there are more than exactly one Score for each AID and Meet, you also may consider an aggregate function and a group by AID-clause.
SQL Fiddle
MS SQL Server 2008 Schema Setup:
Query 1:
DECLARE #TABLE TABLE(AID INT, Meet VARCHAR(10), [Event] VARCHAR(10),Score DECIMAL(10,2))
INSERT INTO #TABLE VALUES
(1 , 'Val' , 'Beam' ,9.47),
(1 , 'Snow' , 'Beam' ,7.78),
(2 , 'Val' , 'Beam' ,9.50),
(2 , 'Snow' , 'Beam' ,7.50)
SELECT AID
,Snow AS SnowScore
,Val AS ValScore
FROM #TABLE T
PIVOT (SUM(Score)
FOR Meet
IN (Snow, Val)
)p
Results:
| AID | SNOWSCORE | VALSCORE |
|-----|-----------|----------|
| 1 | 7.78 | 9.47 |
| 2 | 7.5 | 9.5 |
Database Generic Query
SELECT AID
,SUM(CASE WHEN Meet = 'Val' THEN Score ELSE NULL END) AS ValScore
,SUM(CASE WHEN Meet = 'Snow' THEN Snow ELSE NULL END) AS SnowScore
FROM Table_Name
GROUP BY AID
This question already has answers here:
Oracle sql to count instances of different values in single column
(2 answers)
Closed 10 years ago.
Is it possible to return multiple row values, per same id, as a column?
If my table is:
ID | Value |Column_data
--------------------------------
1 | a | DATA1
1 | b | DATA1
2 | c | DATA2
2 | x | DATA2
3 | y | DATA3
3 | z | DATA3
(Each Id has always 2 values)
The select should return:
1,a,b,DATA1
2,c,x,DATA2
3,y,z,DATA3
You did not state what version of Oracle you are using but if you are using Oracle 11g+, then you can transform this data into columns using the PIVOT function:
select id,
C1,
C2,
column_data
from
(
select id, value, column_data,
row_number() over(partition by id order by id, value) rn
from yourtable
)
pivot
(
max(value)
for rn in ('1' as C1, '2' as C2)
)
order by id
See SQL Fiddle with Demo.
Prior to Oracle 11g, you could use an aggregate function with a CASE expression to transform the rows into columns:
select id,
max(case when rn = 1 then value end) C1,
max(case when rn = 2 then value end) C2,
column_data
from
(
select id, value, column_data,
row_number() over(partition by id order by id, value) rn
from yourtable
)
group by id, column_data
order by id
See SQL Fiddle with Demo
The result of both queries is:
| ID | C1 | C2 | COLUMN_DATA |
------------------------------
| 1 | a | b | DATA1 |
| 2 | c | x | DATA2 |
| 3 | y | z | DATA3 |
Or listagg(col2) over (...) in 11g
You could use pivot, have a look here:
http://www.oracle.com/technetwork/articles/sql/11g-pivot-097235.html
You can try something like this:
with t as ( select id, Column_data, xmlagg(xmlelement("e", Value)) xl
from table1
group by id, Column_data)
select id,
extract(xl, 'e[1]/text()').getstringval() c1,
extract(xl, 'e[2]/text()').getstringval() c2,
Column_data
from t
Here is a sqlfiddle demo