В мускле есть хорошая штука: 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