Независимая от базы данных конкатенация строк SQL в Rails

Я хочу выполнить конкатенацию строки на стороне базы данных в запросе Rails и сделать это независимым от базы данных способом.

SQL-92 задает двойной бар (||) как оператор конкатенации. К сожалению, похоже, что MS SQL Server не поддерживает его; вместо этого он использует +.

Я предполагаю, что аббревиатура грамматики SQL Rails уже решила проблему оператора, специфичную для db. Если он существует, как его использовать?

Ответы

Ответ 1

У меня была такая же проблема, и я никогда не придумывал ничего, что было встроено в Rails. Поэтому я написал этот маленький метод.

# Symbols should be used for field names, everything else will be quoted as a string
def db_concat(*args)

  adapter = configurations[RAILS_ENV]['adapter'].to_sym
  args.map!{ |arg| arg.class==Symbol ? arg.to_s : "'#{arg}'" }

  case adapter
    when :mysql
      "CONCAT(#{args.join(',')})"
    when :sqlserver
      args.join('+')
    else
      args.join('||')
  end

end

Я думаю, что кто-то должен действительно написать какой-то SQL-помощник, который мог бы автоматически форматировать простые выражения SQL, основанные на использовании правильных функций или операторов для текущего адаптера. Возможно, я сам напишу.

Ответ 2

Он еще не получил много пользы, но я написал следующий код, который, кажется, решает проблему. Эта обезьяна исправляет адаптеры, чтобы иметь способ ее поддержки:

module ActiveRecord
  module ConnectionAdapters
    class AbstractAdapter

      # Will return the given strings as a SQL concationation. By default
      # uses the SQL-92 syntax:
      #
      #   concat('foo', 'bar') -> "foo || bar"
      def concat(*args)
        args * " || "
      end

    end

    class AbstractMysqlAdapter < AbstractAdapter

      # Will return the given strings as a SQL concationation.
      # Uses MySQL format:
      #
      #   concat('foo', 'bar')  -> "CONCAT(foo, bar)"
      def concat(*args)
        "CONCAT(#{args * ', '})"
      end

    end

    class SQLServerAdapter < AbstractAdapter

      # Will return the given strings as a SQL concationation.
      # Uses MS-SQL format:
      #
      #   concat('foo', 'bar')  -> foo + bar
      def concat(*args)
        args * ' + '
      end

    end
  end
end

С этим вы сможете сделать следующее в своем коде:

class User < ActiveRecord::Base

  def self.find_by_name(name)
    where("#{connection.concat('first_name', 'last_name')} = ?", name)
  end

end

Это выводит следующий SQL-запрос в базе данных SQL-92 (Oracle, SQLite, PostgreSQL):

SELECT * FROM users WHERE first_name || last_name = ?

Для MySQL он выводит:

SELECT * FROM users WHERE CONCAT(first_name, last_name) = ?

Для SQL Server он выводит

SELECT * FROM users WHERE first_name + last_name = ?

Очевидно, вы можете расширить эту концепцию до других адаптеров баз данных.

Ответ 3

Если вы хотите, чтобы Rails был нейтральным, вам нужно будет вернуть значения, которые вы хотите объединить, и сделать это, как только данные будут доставлены на рельсы (или сделайте это в рельсах, прежде чем вы передадите их в базу данных).

Похоже, что Mysql использует CONCAT(), Postgres ||, Oracle CONCAT() или ||, T-SQL +.

Любая реляционная абстракция того же самого должна была бы иметь место в точке, где вы могли бы просто выполнять конкатенацию с использованием обычного Ruby, или я полностью не понял этот вопрос.