{"id":3955,"date":"2017-08-22T10:00:34","date_gmt":"2017-08-22T17:00:34","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=3955"},"modified":"2025-06-13T17:32:59","modified_gmt":"2025-06-14T00:32:59","slug":"aggregate-grouping-n1ql-mapreduce","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/","title":{"rendered":"Aggregate grouping with N1QL \/ MapReduce"},"content":{"rendered":"<div class=\"paragraph\">\n<p>Aggregate grouping is what I\u2019m titling this blog post, but I don\u2019t know if it\u2019s the best name. Have you ever used MySQL\u2019s <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.7\/en\/group-by-functions.html#function_group-concat\">GROUP_CONCAT function<\/a> or the <a href=\"https:\/\/stackoverflow.com\/questions\/451415\/simulating-group-concat-mysql-function-in-microsoft-sql-server-2005\"><code>FOR XML PATH('')<\/code> workaround in SQL Server<\/a>? That\u2019s basically what I\u2019m writing about today. With Couchbase Server, the easiest way to do it is with N1QL\u2019s <code>ARRAY_AGG<\/code> function, but you can also do it with an old school MapReduce View.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>I\u2019m writing this post because one of our solution engineers was working on this problem for a customer (who will go unnamed). Neither of us could find a blog post like this with the answer, so after we worked together to come up with a solution, I decided I would blog about it for my future self (which is pretty much the main reason I blog anything, really. The other reason is to find out if anyone else knows a better way).<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Before we get started, I\u2019ve made some material available if you want to follow along. The source code I used to generate the &#8220;patient&#8221; data used in this post <a href=\"https:\/\/github.com\/couchbaselabs\/blog-source-code\/tree\/master\/Groves\/076GroupConcateN1qlAndMapReduce\/src\">is available on GitHub<\/a>. If you aren\u2019t .NET savvy, you can just use <a href=\"https:\/\/developer.couchbase.com\/documentation\/server\/current\/tools\/cbimport.html\">cbimport<\/a> on <a href=\"https:\/\/github.com\/couchbaselabs\/blog-source-code\/blob\/master\/Groves\/076GroupConcateN1qlAndMapReduce\/src\/GenerateData\/GenerateData\/patients.json\">sample data<\/a> that I\u2019ve created. (Or, you can use the <a href=\"https:\/\/query.pub.couchbase.com\/tutorial\/#1\">N1QL sandbox<\/a>, more information on that later). The rest of this blog post assumes you have a &#8220;patients&#8221; bucket with that sample data in it.<\/p>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_requirements\">Requirements<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>I have a bucket of patient documents. Each patient has a single doctor. The patient document refers to a doctor by a field called <code>doctorId<\/code>. There may be other data in the patient document, but we\u2019re mainly focused on the patient document\u2019s key and the <code>doctorId<\/code> value. Some examples:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-JavaScript\">key 01257721\r\n{\r\n    \"doctorId\": 58,\r\n    \"patientName\": \"Robyn Kirby\",\r\n    \"patientDob\": \"1986-05-16T19:01:52.4075881-04:00\"\r\n}\r\n\r\nkey 116wmq8i\r\n{\r\n    \"doctorId\": 8,\r\n    \"patientName\": \"Helen Clark\",\r\n    \"patientDob\": \"2016-02-01T04:54:30.3505879-05:00\"\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>Next, we can assume that each doctor can have multiple patients. We can also assume that a doctor document exists, but we don\u2019t actually need that for this tutorial, so let\u2019s just focus on the patients for now.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Finally, what we want for our application (or report or whatever), is an aggregate grouping of the patients with their doctor. Each record would identify a doctor and a list\/array\/collection of patients. Something like:<\/p>\n<\/div>\n<table class=\"tableblock frame-all grid-all spread\">\n<colgroup>\n<col style=\"width: 50%\" \/>\n<col style=\"width: 50%\" \/> <\/colgroup>\n<thead>\n<tr>\n<th class=\"tableblock halign-left valign-top\">doctor<\/th>\n<th class=\"tableblock halign-left valign-top\">patients<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">58<\/p>\n<\/td>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">01257721, 450mkkri, 8g2mrze2 \u2026\u200b<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">8<\/p>\n<\/td>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">05woknfk, 116wmq8i, 2t5yttqi \u2026\u200b<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">\u2026\u200b etc \u2026\u200b<\/p>\n<\/td>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">\u2026\u200b etc \u2026\u200b<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div class=\"paragraph\">\n<p>This might be useful for a dashboard showing all the patients assigned to doctors, for instance. How can we get the data in this form, with N1QL or with MapReduce?<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_n1ql_aggregate_grouping\">N1QL Aggregate grouping<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p><a href=\"https:\/\/www.couchbase.com\/products\/n1ql\/\">N1QL<\/a> gives us the <a href=\"https:\/\/developer.couchbase.com\/documentation\/server\/current\/n1ql\/n1ql-language-reference\/arrayfun.html\"><code>ARRAY_AGG<\/code> function<\/a> to make this possible.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Start by selecting the doctorId from each patient document, and the key to the patient document. Then, apply <code>ARRAY_AGG<\/code> to the patient document ID. Finally, group the results by the doctorId.<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-SQL\">SELECT p.doctorId AS doctor, ARRAY_AGG(META(p).id) AS patients\r\nFROM patients p\r\nGROUP BY p.doctorId;<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p><em>Note: don\u2019t forget to run <code>CREATE PRIMARY INDEX ON patients<\/code> for this tutorial to enable a primary index scan.<\/em><\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Imagine this query without the <code>ARRAY_AGG<\/code>. It would return one record for each patient. By adding the <code>ARRAY_AGG<\/code> and the <code>GROUP BY<\/code>, it now returns one record for each doctor.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Here\u2019s a snippet of the results on the sample data set I created:<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p><span class=\"image\"><img decoding=\"async\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2017\/08\/07501-aggregate-grouping-n1ql.png\" alt=\"Aggregate grouping results in N1QL\" \/><\/span><\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>If you don\u2019t want to go through the trouble of creating a bucket and importing sample data, you can also try this in the <a href=\"https:\/\/query.pub.couchbase.com\/tutorial\/#1\">N1QL tutorial sandbox<\/a>. There aren\u2019t patient documents in there, so the query will be a little different.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>I\u2019m going to group up emails by age. Start by selecting the age from each document, and the email from each document. Then, apply <code>ARRAY_AGG<\/code> to the email. Finally, group the results by the age.<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-SQL\">SELECT t.age AS age, ARRAY_AGG(t.email) AS emails\r\nFROM tutorial t\r\ngroup by t.age;<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>Here\u2019s a screenshot of some of the results from the sandbox:<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p><span class=\"image\"><img decoding=\"async\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2017\/08\/07503-n1ql-sandbox.png\" alt=\"N1QL sandbox results\" \/><\/span><\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_aggregate_group_with_mapreduce\">Aggregate group with MapReduce<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>Similar aggregate grouping can also be achieved with a MapReduce View.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Start by creating a new View. From Couchbase Console, go to Indexes, then Views. Select the &#8220;patients&#8221; bucket. Click &#8220;Create Development View&#8221;. Name a design document (I called mine &#8220;_design\/dev_patient&#8221;. Create a view, I called mine &#8220;doctorPatientGroup&#8221;.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>We\u2019ll need both a Map and a custom Reduce function.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>First, for the map, we just want the doctorId (in an array, since we\u2019ll be using grouping) and the patient\u2019s document ID.<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-JavaScript\">function (doc, meta) {\r\n    emit([doc.doctorId], meta.id);\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>Next, for the reduce function, we\u2019ll take the values and concatenate them into an array. Below is one way that you can do it. I do not claim to be a JavaScript expert or a MapReduce expert, so there might be a more efficient way to tackle this:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-JavaScript\">function reduce(key, values, rereduce) {\r\n    var merged = [].concat.apply([], values);\r\n    return merged;\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>After you\u2019ve created both map and reduce functions, save the index.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Finally, when actually calling this Index, set group_level to 1. You can do this in the UI:<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p><span class=\"image\"><img decoding=\"async\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2017\/08\/07502-aggregate-grouping-mapreduce.png\" alt=\"Aggregate grouping with MapReduce\" \/><\/span><\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Or you can do it from the Index URL. Here\u2019s an example from a cluster running on my local machine:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-JavaScript\">https:\/\/127.0.0.1:8092\/patients\/_design\/dev_patients\/_view\/doctorPatientGroup?connection_timeout=60000&amp;full_set=true&amp;group_level=1&amp;inclusive_end=true&amp;skip=0&amp;stale=false<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>The result of that view should look like this (truncated to look nicer in a blog post):<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-JavaScript\">{\"rows\":[\r\n{\"key\":[0],\"value\":[\"reo8th6f\",\"g53x9e8d\", ... ]},\r\n{\"key\":[1],\"value\":[\"k4xkhmki\",\"g1jtc0oj\", ... ]},\r\n{\"key\":[2],\"value\":[\"spp6gf3k\",\"3z93wyan\"]},\r\n{\"key\":[3],\"value\":[\"qnx93fh3\",\"gssusiun\", ...]},\r\n{\"key\":[4],\"value\":[\"qvqgb0ve\",\"jm0g69zz\", ...]},\r\n{\"key\":[5],\"value\":[\"ywjfvad6\",\"so4uznxx\", ...]}\r\n...\r\n]}<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_summary\">Summary<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>I think the N1QL method is easier, but there may be performance benefits to using MapReduce in some cases. In either case, you can accomplish aggregate grouping just as easily (if not more easily) as in a relational database.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Interested in learning more about N1QL? Be sure to check out the <a href=\"https:\/\/query.pub.couchbase.com\/tutorial\/#1\">complete N1QL tutorial\/sandbox<\/a>. Interested in MapReduce Views? Check out the <a href=\"https:\/\/developer.couchbase.com\/documentation\/server\/current\/sdk\/dotnet\/view-queries-with-sdk.html\">MapReduce Views documentation<\/a> to get started.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Did you find this post useful? Have suggestions for improvement? Please leave a comment below, or contact me on <a href=\"https:\/\/twitter.com\/mgroves\">Twitter @mgroves<\/a>.<\/p>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Aggregate grouping is what I\u2019m titling this blog post, but I don\u2019t know if it\u2019s the best name. Have you ever used MySQL\u2019s GROUP_CONCAT function or the FOR XML PATH(&#8221;) workaround in SQL Server? That\u2019s basically what I\u2019m writing about [&hellip;]<\/p>\n","protected":false},"author":71,"featured_media":3956,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1814,1816,1819,1812],"tags":[2041,2042],"ppma_author":[8937],"class_list":["post-3955","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-application-design","category-couchbase-server","category-data-modeling","category-n1ql-query","tag-aggregation","tag-grouping"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.8 (Yoast SEO v25.8) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Aggregate grouping with N1QL \/ MapReduce - The Couchbase Blog<\/title>\n<meta name=\"description\" content=\"Aggregate grouping (similar to GROUP_CONCAT) can be done in Couchbase Server with N1QL&#039;s ARRAY_AGG or with a MapReduce View.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Aggregate grouping with N1QL \/ MapReduce\" \/>\n<meta property=\"og:description\" content=\"Aggregate grouping (similar to GROUP_CONCAT) can be done in Couchbase Server with N1QL&#039;s ARRAY_AGG or with a MapReduce View.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:published_time\" content=\"2017-08-22T17:00:34+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-14T00:32:59+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/08\/075-hero-aggregate-grouping.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1280\" \/>\n\t<meta property=\"og:image:height\" content=\"731\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Matthew Groves\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@mgroves\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Matthew Groves\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/\"},\"author\":{\"name\":\"Matthew Groves\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/3929663e372020321b0152dc4fa65a58\"},\"headline\":\"Aggregate grouping with N1QL \/ MapReduce\",\"datePublished\":\"2017-08-22T17:00:34+00:00\",\"dateModified\":\"2025-06-14T00:32:59+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/\"},\"wordCount\":901,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/08\/075-hero-aggregate-grouping.jpg\",\"keywords\":[\"aggregation\",\"grouping\"],\"articleSection\":[\"Application Design\",\"Couchbase Server\",\"Data Modeling\",\"SQL++ \/ N1QL Query\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/\",\"name\":\"Aggregate grouping with N1QL \/ MapReduce - The Couchbase Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/08\/075-hero-aggregate-grouping.jpg\",\"datePublished\":\"2017-08-22T17:00:34+00:00\",\"dateModified\":\"2025-06-14T00:32:59+00:00\",\"description\":\"Aggregate grouping (similar to GROUP_CONCAT) can be done in Couchbase Server with N1QL's ARRAY_AGG or with a MapReduce View.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#primaryimage\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/08\/075-hero-aggregate-grouping.jpg\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/08\/075-hero-aggregate-grouping.jpg\",\"width\":1280,\"height\":731,\"caption\":\"Aggregate grouping - licensed through Creative Commons https:\/\/commons.wikimedia.org\/wiki\/File:Spices_of_Sa%C3%BAde_flea_market,_S%C3%A3o_Paulo,_Brazil.jpg\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.couchbase.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Aggregate grouping with N1QL \/ MapReduce\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/3929663e372020321b0152dc4fa65a58\",\"name\":\"Matthew Groves\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/ba51e6aacc53995c323a634e4502ef54\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g\",\"caption\":\"Matthew Groves\"},\"description\":\"Matthew D. Groves is a guy who loves to code. It doesn't matter if it's C#, jQuery, or PHP: he'll submit pull requests for anything. He has been coding professionally ever since he wrote a QuickBASIC point-of-sale app for his parent's pizza shop back in the 90s. He currently works as a Senior Product Marketing Manager for Couchbase. His free time is spent with his family, watching the Reds, and getting involved in the developer community. He is the author of AOP in .NET, Pro Microservices in .NET, a Pluralsight author, and a Microsoft MVP.\",\"sameAs\":[\"https:\/\/crosscuttingconcerns.com\",\"https:\/\/x.com\/mgroves\"],\"url\":\"https:\/\/www.couchbase.com\/blog\/author\/matthew-groves\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Aggregate grouping with N1QL \/ MapReduce - The Couchbase Blog","description":"Aggregate grouping (similar to GROUP_CONCAT) can be done in Couchbase Server with N1QL's ARRAY_AGG or with a MapReduce View.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/","og_locale":"en_US","og_type":"article","og_title":"Aggregate grouping with N1QL \/ MapReduce","og_description":"Aggregate grouping (similar to GROUP_CONCAT) can be done in Couchbase Server with N1QL's ARRAY_AGG or with a MapReduce View.","og_url":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/","og_site_name":"The Couchbase Blog","article_published_time":"2017-08-22T17:00:34+00:00","article_modified_time":"2025-06-14T00:32:59+00:00","og_image":[{"width":1280,"height":731,"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/08\/075-hero-aggregate-grouping.jpg","type":"image\/jpeg"}],"author":"Matthew Groves","twitter_card":"summary_large_image","twitter_creator":"@mgroves","twitter_misc":{"Written by":"Matthew Groves","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/"},"author":{"name":"Matthew Groves","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/3929663e372020321b0152dc4fa65a58"},"headline":"Aggregate grouping with N1QL \/ MapReduce","datePublished":"2017-08-22T17:00:34+00:00","dateModified":"2025-06-14T00:32:59+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/"},"wordCount":901,"commentCount":0,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/08\/075-hero-aggregate-grouping.jpg","keywords":["aggregation","grouping"],"articleSection":["Application Design","Couchbase Server","Data Modeling","SQL++ \/ N1QL Query"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/","url":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/","name":"Aggregate grouping with N1QL \/ MapReduce - The Couchbase Blog","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/08\/075-hero-aggregate-grouping.jpg","datePublished":"2017-08-22T17:00:34+00:00","dateModified":"2025-06-14T00:32:59+00:00","description":"Aggregate grouping (similar to GROUP_CONCAT) can be done in Couchbase Server with N1QL's ARRAY_AGG or with a MapReduce View.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/08\/075-hero-aggregate-grouping.jpg","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/08\/075-hero-aggregate-grouping.jpg","width":1280,"height":731,"caption":"Aggregate grouping - licensed through Creative Commons https:\/\/commons.wikimedia.org\/wiki\/File:Spices_of_Sa%C3%BAde_flea_market,_S%C3%A3o_Paulo,_Brazil.jpg"},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/aggregate-grouping-n1ql-mapreduce\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Aggregate grouping with N1QL \/ MapReduce"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"The Couchbase Blog","description":"Couchbase, the NoSQL Database","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"The Couchbase Blog","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/3929663e372020321b0152dc4fa65a58","name":"Matthew Groves","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/ba51e6aacc53995c323a634e4502ef54","url":"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g","caption":"Matthew Groves"},"description":"Matthew D. Groves is a guy who loves to code. It doesn't matter if it's C#, jQuery, or PHP: he'll submit pull requests for anything. He has been coding professionally ever since he wrote a QuickBASIC point-of-sale app for his parent's pizza shop back in the 90s. He currently works as a Senior Product Marketing Manager for Couchbase. His free time is spent with his family, watching the Reds, and getting involved in the developer community. He is the author of AOP in .NET, Pro Microservices in .NET, a Pluralsight author, and a Microsoft MVP.","sameAs":["https:\/\/crosscuttingconcerns.com","https:\/\/x.com\/mgroves"],"url":"https:\/\/www.couchbase.com\/blog\/author\/matthew-groves\/"}]}},"authors":[{"term_id":8937,"user_id":71,"is_guest":0,"slug":"matthew-groves","display_name":"Matthew Groves","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g","author_category":"","last_name":"Groves","first_name":"Matthew","job_title":"","user_url":"https:\/\/crosscuttingconcerns.com","description":"Matthew D. Groves is a guy who loves to code.  It doesn't matter if it's C#, jQuery, or PHP: he'll submit pull requests for anything.  He has been coding professionally ever since he wrote a QuickBASIC point-of-sale app for his parent's pizza shop back in the 90s.  He currently works as a Senior Product Marketing Manager for Couchbase. His free time is spent with his family, watching the Reds, and getting involved in the developer community.  He is the author of AOP in .NET, Pro Microservices in .NET, a Pluralsight author, and a Microsoft MVP."}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/3955","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/users\/71"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/comments?post=3955"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/3955\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media\/3956"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media?parent=3955"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/categories?post=3955"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/tags?post=3955"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=3955"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}