Как запустить пакетную/частичную базу данных UPDATE в Scala Slick?

Мы хотели бы запустить patch/partial UPDATE с помощью Slick (3.0.0), чтобы мы только модифицировали некоторые из полей в записи. Точно, какие поля будут обновляться точно, это будет известно только во время выполнения.

Например, для запроса REST PATCH.

В настоящее время мы запускаем SELECT сначала, чтобы получить исходную запись, затем запустите UPDATE, но было бы лучше сделать это в одном выражении SQL.

Что-то вроде этого:

def patchPerson(name: Option[String], age: Option[Int]) = {
   people.filter(_.name === "M Odersky")
       .map(p => 
           (name, age) match {
              case (Some(_), Some(_)) => (p.name, p.age)
              case (Some(_), None)    => (p.name)
              case (None   , Some(_)) => (p.age)
           }
       )
       .update(
           (name, age) match {
              case (Some(_), Some(_)) => (name.get, age.get)
              case (Some(_), None)    => (name.get)
              case (None   , Some(_)) => (age.get)
           }
       )
}

(Пожалуйста, проигнорируйте уродливый код здесь)

Вышеописанное не компилируется со следующим сообщением об ошибке:

Нет соответствующей формы. Слик не знает, как сопоставить данные типы. Возможные причины: T в таблице [T] не соответствует вашему * проекция. Или вы используете неподдерживаемый тип в запросе (например, scalaСписок). Требуемый уровень: slick.lifted.FlatShapeLevel Тип источника: Объект Распакованный тип: T Упакованный тип: G

и

недостаточно аргументов для карты метода: (неявная форма: slick.lifted.Shape [_ <: slick.lifted.FlatShapeLevel, Object, T, G]) slick.lifted.Query [G, Т, Seq]. Неопределенная форма параметра значения.

Я предполагаю, что это происходит потому, что Slick ожидает, что длина и тип кортежа соответствуют результатам для функций filter и UPDATE.

Мы попытались использовать класс Slick разнородный список, но также кажется, что длина и типы будут соответствовать.

Есть ли способ записать это в Slick, чтобы мы могли обновить произвольное количество полей в записи с помощью одного вызова базы данных?

Ответы

Ответ 1

Почему не нужно сопоставлять шаблоны перед созданием запроса на обновление?

def patchPerson(name: Option[String], age: Option[Int]) = {
   val query = people.filter(_.name === "M Odersky")
   (name, age) match {
     case (Some(name), Some(age)) =>
       query.map(p => (p.name, p.age)).update(name, age)
     case (Some(name), None) =>
       query.map(p => p.name).update(name)
     case (None, Some(age)) =>
       query.map(p => p.age).update(age)
   }
}

Ответ 2

Лучше всего было бы запустить простой запрос SQL

Даже если SQL-запрос имеет 2 части, система управления реляционными базами данных (postgresql, mysql и т.д.) может настраивать запрос под капотами.

Я не уверен, что в этом случае Slick может оптимизировать, но в некоторых случаях он также сам оптимизирует запросы./p >

Типичное обновление:

def updateRecord(id: Long, field1: Int) = {
    db.withSession {
      self.filter(_.id === id).map(_.field1).update(field1)
    }
}

Для вашего типа обновления потребуется немного больше логики, как и вы. Не думайте, что это возможно для упрощения, если вы знаете только во время выполнения, какие поля меняться. Но вы можете принудительно обновить, используя существующее значение для поля в записи как резервную (может привести к большему количеству обновлений в БД, чем это должно быть)

def updateRecord(id: Long, field1: Option[Int], field2: Option[Int]) = {
    db.withSession {
        self.filter(_.id === id).map(_.field1, _.field2).update(field1.getOrElse(existingValue1), field2.getOrElse(existingValue2)) 
    }
}

Ответ 3

У вас уже есть ответы, написанные @pedrorijo91 и @thirstycow, но я попытаюсь объяснить, почему это не работает.

Я havent использовал slick 3, но я угадал его, потому что функция map не возвращает согласованный тип для запуска обновления. Как мысленный эксперимент, если вы сократите свой звонок на карте, как вы думаете, какой тип будет?

val partialQuery:??? = people.filter(_.name === "M Odersky")
       .map(p => 
           (name, age) match {
              case (Some(_), Some(_)) => (p.name, p.age)
              case (Some(_), None)    => (p.name)
              case (None   , Some(_)) => (p.age)
           }
       );

val fullQuery:??? = partialQuery.update {
       (name, age) match {
          case (Some(_), Some(_)) => (name.get, age.get)
          case (Some(_), None)    => (name.get)
          case (None   , Some(_)) => (age.get)
       }    
}

Соединитель возвращает разные "фигуры" во время компиляции, которое im guessing вернет к любому типу.