В мускле есть хорошая штука: group_concat.

1
2
3
4
5
6
7

SELECT articles.title, group_concat(tags.name, ', ') AS tag_list 
   FROM articles 
   LEFT JOIN taggings ON (taggings.taggable_id = articles.id AND 
                          taggings.taggable_type = 'Article') 
   INNER JOIN tags ON (taggings.tag_id = tags.id) 
   GROUP BY articles.title;

А в постгресе и в sqlite её нет.

В гугле легко найти советы для SQLite и для для PostgreSQL. Оба этих рецепта страдают одной проблемой: они не позволяют задавать разделитель для склейки.

Дополним оба рецепта.

В постгресе можем просто создать хранимую процедуру:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

CREATE FUNCTION _group_concat(text, text, text)
RETURNS text AS $$
  SELECT CASE
    WHEN $2 IS NULL THEN $1
    WHEN $1 IS NULL THEN $2
    ELSE $1 operator(pg_catalog.||) $3 operator(pg_catalog.||) $2
  END
$$ IMMUTABLE LANGUAGE SQL;
    
CREATE AGGREGATE group_concat(text, text) (
  SFUNC = _group_concat,
  STYPE = text
);

Да, миграция для рельс оформляется так:


class TeachPgToGroupConcat < ActiveRecord::Migration
  def self.up
    return unless adapter_name == "PostgreSQL"
    execute <<-EOF
    CREATE FUNCTION _group_concat(text, text, text)
    RETURNS text AS $$
      SELECT CASE
        WHEN $2 IS NULL THEN $1
        WHEN $1 IS NULL THEN $2
        ELSE $1 operator(pg_catalog.||) $3 operator(pg_catalog.||) $2
      END
    $$ IMMUTABLE LANGUAGE SQL;
    
    CREATE AGGREGATE group_concat(text, text) (
      SFUNC = _group_concat,
      STYPE = text
    );    
    EOF
  end
 
  def self.down
    return unless adapter_name == "PostgreSQL"
    execute <<-EOF
      DROP AGGREGATE group_concat(text, text);
      DROP FUNCTION _group_concat(text, text, text)
    EOF
  end
end

А вот с SQLite надо прибегать к платформозависимым решениям. Поскольку я пишу на рельсах, то и решение будет рельсовым. В файл config/initializers/sqlite_group_concat.rb пишем такое содержимое:


module ActiveRecord
  module ConnectionAdapters
    class SQLite3Adapter
      def initialize(*args)
        Rails.logger.debug "Adding group_concat to SQLite database"
        super
        raw_connection.create_aggregate("group_concat", 2) do
          step do |func, value, separator|
            if String(func[:concat]).empty? then
              func[:concat] = String(value)
            else
              func[:concat] = String(func[:concat]) + "#{separator}" + String(value)
            end
          end
 
          finalize do |func|
            func.result = func[:concat]
          end
        end
      end
    end
  end
end

Sidebar