diff --git a/README.md b/README.md
index ba5c5c999d1527fd37be71bf41bc64e2c172a0a7..3872875ef0349b266be8d917bd7a575323254b83 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,7 @@ Set up the database:
 
     rails db:create
     rails db:schema:load
+    rails r db/scripts/create_tags_path_view.rb
     rails db:migrate
     rails db:seed
 
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index ad6a20dea1903130d6242a8127bbca64702d28bf..35705a7e260bf618f28560c8329a8430ae3244d2 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -23,10 +23,16 @@ class TagsController < ApplicationController
     @tag_set = @category.tag_set
     @tags = if params[:q].present?
               @tag_set.tags.search(params[:q])
+            elsif params[:hierarchical].present?
+              @tag_set.tags_with_paths.order(:path)
             else
               @tag_set.tags.order(Arel.sql('COUNT(posts.id) DESC'))
-            end.left_joins(:posts).group(Arel.sql('tags.id')).select(Arel.sql('tags.*, COUNT(posts.id) AS post_count'))
-            .paginate(per_page: 96, page: params[:page])
+            end
+    @count = @tags.count
+    table = params[:hierarchical].present? ? 'tags_paths' : 'tags'
+    @tags = @tags.left_joins(:posts).group(Arel.sql("#{table}.id"))
+              .select(Arel.sql("#{table}.*, COUNT(posts.id) AS post_count"))
+              .paginate(per_page: 96, page: params[:page])
   end
 
   def show
@@ -65,10 +71,16 @@ class TagsController < ApplicationController
   def children
     @tags = if params[:q].present?
               @tag.children.search(params[:q])
+            elsif params[:hierarchical].present?
+              @tag_set.tags_with_paths.order(:path)
             else
               @tag.children.order(Arel.sql('COUNT(posts.id) DESC'))
-            end.left_joins(:posts).group(Arel.sql('tags.id')).select(Arel.sql('tags.*, COUNT(posts.id) AS post_count'))
-            .paginate(per_page: 96, page: params[:page])
+            end
+    @count = @tags.count
+    table = params[:hierarchical].present? ? 'tags_paths' : 'tags'
+    @tags = @tags.left_joins(:posts).group(Arel.sql("#{table}.id"))
+                 .select(Arel.sql("#{table}.*, COUNT(posts.id) AS post_count"))
+                 .paginate(per_page: 96, page: params[:page])
   end
 
   private
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index 289eb912a683599b1ea0b06060e863f0ff63ab6c..d65fc384a5a380c8f0fa52a2c32ad5556c222304 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -37,6 +37,19 @@ class ApplicationRecord < ActiveRecord::Base
     ary = ary.map { |el| ActiveRecord::Base.sanitize_sql_array(['?', el]) }
     "(#{ary.join(', ')})"
   end
+
+  # This is a BRILLIANT idea. BRILLIANT, I tell you.
+  def self.with_lax_group_rules
+    return unless block_given?
+
+    transaction do
+      connection.execute "SET @old_sql_mode = @@sql_mode"
+      connection.execute "SET SESSION sql_mode = REPLACE(REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY,', ''), " \
+                         "'ONLY_FULL_GROUP_BY', '')"
+      yield
+      connection.execute "SET SESSION sql_mode = @old_sql_mode"
+    end
+  end
 end
 
 module UserSortable
diff --git a/app/models/tag.rb b/app/models/tag.rb
index d6629cbdb86380142fabbb843d8f2dc2a4fcefd7..51bf767f37e1eaf83d5382e7d29c049f0dd56f66 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -22,6 +22,16 @@ class Tag < ApplicationRecord
     ActiveRecord::Base.connection.execute(query).to_a.map(&:first)
   end
 
+  def parent_chain
+    Enumerator.new do |enum|
+      parent_group = group
+      while parent_group != nil
+        enum.yield parent_group
+        parent_group = parent_group.group
+      end
+    end
+  end
+
   private
 
   def parent_not_self
diff --git a/app/models/tag_set.rb b/app/models/tag_set.rb
index 8e23746e0dbdd3f8db9400b44a223f15c91a5908..0a3d04066a8efbe4ece7b08bd9a2301f3cd92501 100644
--- a/app/models/tag_set.rb
+++ b/app/models/tag_set.rb
@@ -1,6 +1,7 @@
 class TagSet < ApplicationRecord
   include CommunityRelated
   has_many :tags
+  has_many :tags_with_paths, class_name: 'TagWithPath'
   has_many :categories
 
   validates :name, uniqueness: { scope: [:community_id] }, presence: true
diff --git a/app/models/tag_with_path.rb b/app/models/tag_with_path.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dde39913adf7e5a4c21ea562b3d8c941bf4d1de6
--- /dev/null
+++ b/app/models/tag_with_path.rb
@@ -0,0 +1,3 @@
+class TagWithPath < Tag
+  self.table_name = 'tags_paths'
+end
\ No newline at end of file
diff --git a/app/views/tags/_list.html.erb b/app/views/tags/_list.html.erb
index 7b4ffc202edee459079d59a0278e4876177abb1c..cc7e4ea91d0ff381483c94768663366c39b2e4b3 100644
--- a/app/views/tags/_list.html.erb
+++ b/app/views/tags/_list.html.erb
@@ -3,11 +3,13 @@
   <% moderator_ids = @category&.moderator_tag_ids %>
   <% topic_ids = @category&.topic_tag_ids %>
 
-  <% @tags.each do |tag| %>
-    <% required = required_ids&.include?(tag.id) ? 'is-filled' : '' %>
-    <% topic = topic_ids&.include?(tag.id) ? 'is-outlined' : '' %>
-    <% moderator = moderator_ids&.include?(tag.id) ? 'is-red is-outlined' : '' %>
-    <% classes = "badge is-tag #{required} #{topic} #{moderator}" %>
-    <%= render 'tag', category: @category, tag: tag, classes: classes %>
+  <% ApplicationRecord.with_lax_group_rules do %>
+    <% @tags.each do |tag| %>
+      <% required = required_ids&.include?(tag.id) ? 'is-filled' : '' %>
+      <% topic = topic_ids&.include?(tag.id) ? 'is-outlined' : '' %>
+      <% moderator = moderator_ids&.include?(tag.id) ? 'is-red is-outlined' : '' %>
+      <% classes = "badge is-tag #{required} #{topic} #{moderator}" %>
+      <%= render 'tag', category: @category, tag: tag, classes: classes %>
+    <% end %>
   <% end %>
 </div>
\ No newline at end of file
diff --git a/app/views/tags/_tag.html.erb b/app/views/tags/_tag.html.erb
index a5a3799572704f73b2f6406a0d67fe913b5d3ba9..b0d11c7c088c6f8d4596bb8bcff7c0409d447c78 100644
--- a/app/views/tags/_tag.html.erb
+++ b/app/views/tags/_tag.html.erb
@@ -1,6 +1,13 @@
 <div class="grid--cell is-4-lg is-6-md is-12-sm">
+  <% if tag.respond_to?(:path) && tag.path.present? %>
+    <span class="has-font-size-caption">
+      <% tag.path.split(' > ')[0..-2].each do |tag| %>
+        <span class="badge is-tag is-small is-muted"><%= tag %></span> &raquo;
+      <% end %>
+    </span>
+  <% end %>
   <%= link_to tag.name, tag_path(id: category.id, tag_id: tag.id), class: classes %>
-  <span class="has-color-tertiary-900">&times; <%= tag.post_count %></span>
+  <span class="has-color-tertiary-900">&times;&nbsp;<%= tag.post_count %></span>
   <% if tag.excerpt.present? %>
     <p class="has-font-size-caption has-color-tertiary-900">
       <% splat = split_words_max_length(tag.excerpt, 120) %>
diff --git a/app/views/tags/category.html.erb b/app/views/tags/category.html.erb
index 00d461de38226f46eb6b0ad5a55f96a73c8a3566..ba519acf32abf3787bf129d96f464bed0d89c21f 100644
--- a/app/views/tags/category.html.erb
+++ b/app/views/tags/category.html.erb
@@ -14,6 +14,18 @@
   </div>
 <% end %>
 
+<% unless params[:q].present? %>
+  <div class="category-meta">
+    <h3><%= pluralize(@count, 'tag') %></h3>
+    <div class="button-list is-gutterless has-margin-2">
+      <%= link_to 'Usage', category_tags_path(@category),
+                  class: "button is-muted is-outlined #{params[:hierarchical].nil? ? 'is-active' : ''}" %>
+      <%= link_to 'Hierarchy', query_url(hierarchical: '1'),
+                  class: "button is-muted is-outlined #{params[:hierarchical].present? ? 'is-active' : ''}" %>
+    </div>
+  </div>
+<% end %>
+
 <%= render 'list' %>
 
 <%= will_paginate @tags, renderer: BootstrapPagination::Rails %>
diff --git a/app/views/tags/children.html.erb b/app/views/tags/children.html.erb
index 92cc1e14b5339f1d1df57be9c687bcdac33a358f..442a20269a0f3c03ad06071cce3d577f0e3a9551 100644
--- a/app/views/tags/children.html.erb
+++ b/app/views/tags/children.html.erb
@@ -14,6 +14,18 @@
   </div>
 <% end %>
 
+<% unless params[:q].present? %>
+  <div class="category-meta">
+    <h3><%= pluralize(@count, 'tag') %></h3>
+    <div class="button-list is-gutterless has-margin-2">
+      <%= link_to 'Usage', category_tags_path(@category),
+                  class: "button is-muted is-outlined #{params[:hierarchical].nil? ? 'is-active' : ''}" %>
+      <%= link_to 'Hierarchy', query_url(hierarchical: '1'),
+                  class: "button is-muted is-outlined #{params[:hierarchical].present? ? 'is-active' : ''}" %>
+    </div>
+  </div>
+<% end %>
+
 <%= render 'list' %>
 
 <%= will_paginate @tags, renderer: BootstrapPagination::Rails %>
diff --git a/config/initializers/activerecord_relation.rb b/config/initializers/activerecord_relation.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd91f7f2ebebcff927a8a63e4d898fba8f35599e
--- /dev/null
+++ b/config/initializers/activerecord_relation.rb
@@ -0,0 +1,32 @@
+class ActiveRecord::Relation
+  # Preload one level a chained association whose name is specified in attribute.
+  def preload_chain(attribute, collection: nil)
+    preloader = ActiveRecord::Associations::Preloader.new
+    preloader.preload(collection || records, attribute.to_sym)
+    self
+  end
+
+  # Preload all levels of a chained association specified in attribute. Will cause infinite loops if there are cycles.
+  def deep_preload_chain(attribute, collection: nil)
+    return if (collection || records).empty?
+    preload_chain(attribute, collection: collection)
+    deep_preload_chain(attribute, collection: (collection || records).select(&attribute.to_sym).map(&attribute.to_sym))
+    self
+  end
+
+  # Preload one level of a chained association on a table referenced by the current table.
+  def preload_reference_chain(**reference_attribs)
+    reference_attribs.each do |t, a|
+      preload_chain(a, collection: records.map { |r| r.public_send(t.to_sym) })
+    end
+    self
+  end
+
+  # Preload all levels (including infinite loops) of a chained association on a referenced table.
+  def deep_preload_reference_chain(**reference_attribs)
+    reference_attribs.each do |t, a|
+      deep_preload_chain(a, collection: records.map { |r| r.public_send(t.to_sym) })
+    end
+    self
+  end
+end
\ No newline at end of file
diff --git a/db/scripts/create_tags_path_view.rb b/db/scripts/create_tags_path_view.rb
new file mode 100644
index 0000000000000000000000000000000000000000..99ddc3b58c0b625195074ba31d406284ca8a8556
--- /dev/null
+++ b/db/scripts/create_tags_path_view.rb
@@ -0,0 +1 @@
+ActiveRecord::Base.connection.execute File.read(Rails.root.join('db/scripts/create_tags_path_view.sql'))
\ No newline at end of file
diff --git a/db/scripts/create_tags_path_view.sql b/db/scripts/create_tags_path_view.sql
new file mode 100644
index 0000000000000000000000000000000000000000..92708f38b2f27e1ab369542c522404cbac7ae882
--- /dev/null
+++ b/db/scripts/create_tags_path_view.sql
@@ -0,0 +1,15 @@
+create view tags_paths as
+WITH RECURSIVE tag_path (id, created_at, updated_at, community_id, tag_set_id, wiki_markdown,
+                         wiki, excerpt, parent_id, name, path) AS
+                   (
+                       SELECT id, created_at, updated_at, community_id, tag_set_id, wiki_markdown,
+                              wiki, excerpt, parent_id, name, name as path
+                       FROM tags
+                       WHERE parent_id IS NULL
+                       UNION ALL
+                       SELECT t.id, t.created_at, t.updated_at, t.community_id, t.tag_set_id, t.wiki_markdown,
+                              t.wiki, t.excerpt, t.parent_id, t.name, concat(tp.path, ' > ', t.name) as path
+                       FROM tag_path AS tp JOIN tags AS t ON tp.id = t.parent_id
+                   )
+SELECT * FROM tag_path
+ORDER BY path;
\ No newline at end of file