2013-03-24 6 views
1

У меня есть таблица Join в Rails, которая представляет собой всего лишь таблицу из 2 столбцов с идентификаторами.2 столбец столбца, игнорировать дубликаты на массовой вставке postgresql

Для массовой вставки в эту таблицу, я использую

ActiveRecord::Base.connection.execute("INSERT INTO myjointable (first_id,second_id) VALUES #{values}) 

К сожалению, это дает мне ошибки, когда есть дубликаты. Мне не нужно обновлять какие-либо значения, просто переходите к следующему insert, если существует дубликат.

Как мне это сделать?

Как fyi я искал stackoverflow, и большинство ответов немного продвинулось для меня, чтобы понять. Я также проверял документы postgresql и играл в консоли rails, но все равно безрезультатно. Я не могу понять этого, поэтому я надеюсь, что кто-то еще может помочь мне рассказать, что я делаю неправильно.

Ближайшим заявление я попытался это:

INSERT INTO myjointable (first_id,second_id) SELECT 1,2 
WHERE NOT EXISTS (
     SELECT first_id FROM myjointable 
     WHERE first_id = 1 AND second_id IN (...)) 

Часть проблемы с этим утверждением является то, что я только вставив значение 1 в то время, в то время как я хочу о том, что массовые вставки. Также раздел second_id IN (...) может содержать до 100 различных значений, поэтому я не уверен, насколько это будет медленным.

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

Изменить, чтобы добавить контекст:

Причина мне нужно массовую вставку, потому что у меня есть отношения многие ко многим между 2 моделями, где одна из моделей никогда не заселенных форме. У меня есть акции и история цен на акции. Истории цен на акции никогда не создаются в форме, а скорее вставляются сами, вытаскивая данные из YahooFinance с их API финансов Yahoo. Я использую атрибут activerecord-import для массового ввода для истории цен на акции (например, столбцы Model.import, значения), но я не могу набирать столбцы conable.import, значения, потому что я получаю

+0

Непонятно, какие проблемы вы пытаетесь решить. Например, зачем вам нужна массовая вставка с использованием пользовательского SQL? Каков контекст? – muttonlamb

+0

@muttonlamb Я добавил причину, по которой мне нужна массовая вставка с использованием пользовательского SQL –

+0

. В будущем, пожалуйста, всегда указывайте свою версию PostgreSQL и, где возможно, предоставите некоторые примеры данных или SQLFiddle. –

ответ

0

Вот как я занялся it: Создайте временную таблицу и заполните ее новыми значениями. Затем заблокируйте таблицу старых значений соединений, чтобы предотвратить одновременную модификацию (важно) и вставить все пары значений, которые появляются в новой таблице, но не старые.

Один из способов сделать это - сделать левое внешнее объединение старых значений на новые и фильтровать для строк, где старые значения таблицы соединений являются нулевыми. Другой подход заключается в использовании подзапроса EXISTS. Эти два варианта, вероятно, приведут к такому же плану запроса, как только оптимизатор запросов будет выполнен с ними в любом случае.

Пример, непроверенных (так как вы не обеспечивают SQLFiddle или выборочные данные), но должно работать:

BEGIN; 

CREATE TEMPORARY TABLE newjoinvalues(
    first_id integer, 
    second_id integer, 
    primary key(first_id,second_id) 
); 

-- Now populate `newjoinvalues` with multi-valued inserts or COPY 
COPY newjoinvalues(first_id, second_id) FROM stdin; 

LOCK TABLE myjoinvalues IN EXCLUSIVE MODE; 

INSERT INTO myjoinvalues 
SELECT n.first_id, n.second_id 
FROM newjoinvalues n 
LEFT OUTER JOIN myjoinvalues m ON (n.first_id = m.first_id AND n.second_id = m.second_id) 
WHERE m.first_id IS NULL AND m.second_id IS NULL; 

COMMIT; 

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

Обратите внимание, что режим блокировки указано выше, не будет блокировать SELECT с, пишет только как INSERT, UPDATE и DELETE, поэтому запросы могут продолжать быть внесены в таблицу, в то время как процесс продолжается, вы просто не можете обновить его.

Если вы не можете согласиться с тем, что альтернативный вариант заключается в том, чтобы запустить обновление в изоляции SERIALIZABLE (работает только для этой цели в Pg 9.1 и выше). Это приведет к сбою запроса при возникновении одновременной записи, поэтому вы должны быть готовы повторить его снова и снова. По этой причине, скорее всего, будет лучше просто жить с блокировкой стола на некоторое время.

+0

Я решил использовать предложение WITH, которое выбрало мои значения и дало им имя (это почти так, как если бы оно создало временную таблицу). Затем я вставляю Into table Select * From withqueryname WHERE NOT EXISTS' и использует 'WHERE NOT EXISTS (определяя запрос, чтобы увидеть, есть ли в моей таблице)', чтобы эффективно пропускать любые вставки, которые уже были в таблице. Будет ли мой метод более или менее эффективным, чем ваш? –

+0

@Chowza. Ваш подход подходит для небольших наборов новых значений; для больших наборов новых значений вам понадобится индекс в наборе новых значений, и явный размер выражения SQL для синтаксического анализа становится проблемой. Если вы ожидаете лоты (сотни +) новых значений, вы должны использовать таблицу temp, а не выражение 'VALUES' в предложении' WITH'. В любом случае, эффективность сравнима с «EXPLAIN ANALYZE» и посмотреть. Выполнение всего этого в одном операторе SQL делает * не * избегать необходимости блокировки. –

+0

Извините Крейг, еще один вопрос. Если я напишу 'ActiveRecord :: Base.transaction do ActiveRecord :: Base.connection.execute ('LOCK TABLE, объединенный в EXCLUSIVE MODE') ActiveRecord :: Base.connection.execute ('my sql statement above') end', это удовлетворит проблема с одновременными изменениями? –

1

Я закончил с использованием предложения WITH, чтобы выбрать мои значения и присвоить им имя. Затем я вставил эти значения и использовал WHERE NOT EXISTS, чтобы эффективно пропускать любые элементы, которые уже есть в моей базе данных.

Пока это выглядит, как он работает ...

WITH withqueryname(first_id,second_id) AS (VALUES(1,2),(3,4),(5,6)...etc) 
INSERT INTO jointablename (first_id,second_id) 
SELECT * FROM withqueryname 
WHERE NOT EXISTS( 
     SELECT first_id FROM jointablename WHERE 
      first_id = 1 AND 
      second_id IN (1,2,3,4,5,6..etc)) 

Вы можете поменять местами значения с переменной. Шахта была VALUES#{values}

Вы также можете поменять second_id IN на переменную. Шахта была second_id IN #{variable}.

+0

Вам не нужен раздел 'second_id IN (... list ...)'. У вас уже есть эта информация в 'withqueryname' и не нужно ее повторять. Кроме того, 'FROM ... WHERE' действительно плохой стиль; используйте 'FROM firsttable INNER JOIN secondtable ON (условие)'. В целом это выглядит излишне сложным для того, что он делает, хотя я подозреваю, что планировщик запросов будет оптимизировать его в том же плане, что и подход, основанный на «левом внешнем соединении», когда вы избавитесь от повторения списка «second_id». 'объяснить анализ' и посмотреть. –

+0

@CraigRinger Спасибо за помощь и совет. Я внедрил INNER Присоединяйтесь сейчас. Я также хочу добавить блокировку, потому что я не думал о параллельных импортах. Я отвечу на ваш ответ, потому что он кажется более «подходящим» и «чистым» способом обработки дубликатов. –

Смежные вопросы