InsertAll только новые записи с Slick

[PSQLException: ОШИБКА: повторяющееся значение ключа нарушает уникальное ограничение «dictionary_word_idx». Деталь: ключ (слово) = (одиран) уже существует.]

У меня есть уникальный индекс, предотвращающий любые дублирования. Интересно, как вставить все в массив с тысячами элементов, но только с новыми? Я использую Slick 1.0.1 и Postgresql 9.1.

Изменить: я пытаюсь сделать следующее:

    def run = {
      val source = scala.io.Source.fromFile("/home/user/dev/txt/test1.txt")
      val lines = source.mkString
      source.close()

      val words = lines.split("[^\\p{Ll}]").distinct

      database withTransaction {

        val q = for {
            w <- words.toList
            row <- Dictionary if row.word != w  
        } yield w


        Dictionary.autoInc.insertAll(q: _*)
      }


      words.length
    }

но не компилировать:

 polymorphic expression cannot be instantiated to expected type; 
 [error]  found   : [G, T]scala.slick.lifted.Query[G,T] 
 [error]  required: scala.collection.GenTraversableOnce[?] [error]          
  row <- Dictionary if row.word != w

Редактировать 2:

case class Word(id: Option[Long], word:String)

object Dictionary extends Table[Word]("dictionary") {
  def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
  def word = column[String]("word")

  def * = id.? ~ word <> (Word, Word.unapply _)
  def dictionary_word_idx = index("dictionary_word_idx", word, unique = true)
  def autoInc = word returning id
}

person Roskoto    schedule 05.08.2013    source источник


Ответы (2)


Другой альтернативой является написание необработанного SQL. У Postgres нет способа по умолчанию для on duplicate ignore, но вы можете эмулировать его несколькими различными способами, показанными здесь https://dba.stackexchange.com/questions/30499/optimal-way-to-ignore.-duplicate-inserts

Объедините это с http://slick.typesafe.com/doc/1.0.0-RC2/sql.html

Редактировать:

Вот пример

def insert(c: String) =
    (Q.u + """INSERT INTO dictionary
        (word)
    SELECT""" +?  c + 
    """WHERE
        NOT EXISTS (
            SELECT word FROM dictionary WHERE word = """ +? c + ")"
    ).execute

val words = lines.split("[^\\p{Ll}]")

words.foreach(insert)

Это ты имеешь в виду под "сразу"? Я думаю, что это будет самый эффективный способ сделать это, не сойдя с ума.

Если это слишком медленно для вас, есть еще одно предложение создать временную таблицу без ограничения уникальности, скопировать текущую таблицу во временную таблицу, вставить новые слова во временную таблицу, а затем выбрать отдельные из этой таблицы. Это показано здесь: https://stackoverflow.com/a/4070385/375874

Но я думаю, что это ОЧЕНЬ перебор. Если только у вас нет сумасшедших требований или что-то в этом роде.

person Falmarri    schedule 07.08.2013
comment
Не могли бы вы добавить пример, как создать один INSERT с WHERE NOT EXISTS, который вставляет (или не дублирует) сразу все новые значения из Seq[String]? - person Roskoto; 09.08.2013

Концептуально:

def insertAll[T](items: Seq[T]): Seq[Either[(T, Exception), (T, Int)]] = items.map { i =>
  try {
    // Perform an insert supposing returns and int representing the PK on the table
    val pk = …
    Right(i, pk)
  } catch {
    case e: Exception => Left(i, e)
  }
}

Вы выполняете каждую операцию вставки, а затем, в зависимости от результата, возвращаете объект Left или Right, который отслеживает конечный результат и дает вам подробный контекст для интерпретации операции.

ИЗМЕНИТЬ

Предположим, что ваш объект DAO выглядит так:

object Dictionary extends Table[Word]("dictionary") {
   // ...
}

где Word — ваша объектная модель, и, кроме того, вы предоставили гайки и болты (как я могу сделать вывод из вашего вставленного кода), она должна быть (где words — это Seq[Word]):

words.map { w => 
  try {
    Right(w, Dictionary.autoInc.insert(w))
  } catch {
    case e: Exception => Left(w, e)
  }
}

Вы получаете последовательность Either, которая инкапсулирует результат для дальнейшей обработки.

Соображения Предложенное мной решение оптимистично пытается выполнить операцию с БД, не требуя предварительной фильтрации списка на основе состояния БД. В общем, предварительная фильтрация проблематична в многопользовательском приложении, если вы не можете предположить, что никто не добавил слово в ваш предварительно отфильтрованный список после того, как вы выполнили фильтр. Сформулируйте проще: ограничение уникальности — это надежная функция, предоставляемая СУБД, которую лучше использовать, чем изобретать заново. Решение, которое вы отредактировали выше, не является решением, потому что вам все еще нужно столкнуться с возможным исключением нарушения PK.

person Lord of the Goo    schedule 06.08.2013
comment
Я не совсем понимаю ваш ответ. Возможно, пример реализации и использования будет полезен. - person Roskoto; 07.08.2013
comment
Пожалуйста, предоставьте определение словаря DAO и взгляните на мой предыдущий комментарий. - person Lord of the Goo; 08.08.2013
comment
Этот метод вставляет записи одну за другой, что очень медленно. - person Roskoto; 09.08.2013