Selecting MySQL Rows Into Columns


Sometimes it’s nice to combine multiple database queries into one to save on round-trips, or for any other reason fetch results from separate tables in a single query without actually joining each of the tables involved. This can be done by effectively selecting the results from the rows of one query into the columns of another.

First, an example table setup; let’s say we’re collecting videos and screenshots for various websites:


CREATE TABLE IF NOT EXISTS `screenshots` (
`id` int(11) NOT NULL,
`site_id` int(11) NOT NULL,
`title` varchar(255) NOT NULL
);

INSERT INTO `screenshots` (`id`, `site_id`, `title`) VALUES(1, 1, 'Homepage');
INSERT INTO `screenshots` (`id`, `site_id`, `title`) VALUES(2, 2, 'Homepage');
INSERT INTO `screenshots` (`id`, `site_id`, `title`) VALUES(3, 1, 'Search Results');

-- --------------------------------------------------------

CREATE TABLE IF NOT EXISTS `sites` (
`id` int(11) NOT NULL,
`url` varchar(255) NOT NULL
);

INSERT INTO `sites` (`id`, `url`) VALUES(1, 'http://google.com/');
INSERT INTO `sites` (`id`, `url`) VALUES(2, 'http://yahoo.com/');

-- --------------------------------------------------------

CREATE TABLE IF NOT EXISTS `videos` (
`id` int(11) NOT NULL,
`site_id` int(11) NOT NULL,
`duration` int(11) NOT NULL
);

INSERT INTO `videos` (`id`, `site_id`, `duration`) VALUES(1, 1, 532);
INSERT INTO `videos` (`id`, `site_id`, `duration`) VALUES(1, 2, 331);

And now, the magic query, which selects the number of screenshots and the number of videos for a single site in a single query:


select
max(if(type='screenshots',num,'')) as num_screenshots,
max(if(type='videos',num,'')) as num_videos
from (
(select count(s.id) as num, 'screenshots' as type from screenshots s where s.site_id = 1)
UNION ALL
(select count(v.id) as num, 'videos' as type from videos v where v.site_id = 1)
) as foo

If you want this information for, say, all sites, we could say:


select
site_id,
max(if(type='screenshots',num,'')) as num_screenshots,
max(if(type='videos',num,'')) as num_videos
from (
(select s.site_id, count(s.id) as num, 'screenshots' as type from screenshots s where s.site_id in (1,2,3) group by site_id)
UNION ALL
(select v.site_id, count(v.id) as num, 'videos' as type from videos v where v.site_id in (1,2,3) group by site_id)
) as foo group by site_id

In Oracle something similar can be achieved using the decode or case operators.

3 Comments

  1. Comment by Victor De la Rocha on 2009-04-30 11:33 am

    Is there any way to do this dynamically?

    Instead of this:


    from (
    (select count(s.id) as num, ’screenshots’ as type from screenshots s where s.site_id = 1)
    UNION ALL
    (select count(v.id) as num, ‘videos’ as type from videos v where v.site_id = 1)
    ) as foo

    Do it with the records grouped from a table.

  2. Comment by hulet on 2009-05-02 9:50 pm

    That would be cool if there were… could you give an example?

  3. Comment by Chris on 2009-08-26 1:05 pm

    After searching for like 5 hours and playing around I have found a small way to generate it… See the following snippet:

    SELECT DISTINCT
    CONCAT(‘,’,build_field_name,’ AS ‘,build_field_name)
    AS pivotarg
    FROM aspin_build_fields
    WHERE build_field_name IS NOT NULL

    This generates the select statement fields so i dont see why you cant just wrapper the other sql statement with it.

Comments RSS TrackBack Identifier URI

Leave a comment


powered by WordPress     themed by Mukkamu     presented by ideaharbor.org     everything else by steve hulet