Обновление таблицы с использованием dataframes и dbSendQuery - PullRequest
0 голосов
/ 28 января 2020

Я запросил таблицу в базе данных, сохранил значения в кадре данных и затем манипулировал ими. Это мой код для запроса данных:

#Setup Connection
con1 <- dbConnect(odbc::odbc(), "XXXX", database="XXXX")
r1 <- dbSendQuery(con1, "
select pcd, oseast1m, osnrth1m from onspd as ons where ons.pcd like 'bt%' and oseast1m != ''
")
result <- dbFetch(r1)

Теперь я хочу записать значения обратно из базы данных в базу данных с чем-то вроде:

dbClearResult(r1)
sql <- "
update ons
set ons.oseast1m=?east, os.osnrth1m=?west
from ONS_TEST as ons where ons.Postcode=?post
"
r1 <- dbSendQuery(con1, sqlInterpolate(ANSI(), sql, east = result$oseast1m, west = result$osnrth1m, post = result$pcd))

Это дает мне ошибку «значения должны быть длиной 1», что, очевидно, не является правильным для того, что я хочу.

Какой синтаксис для запуска обновления? Или мне нужно написать для l oop, чтобы добиться того же?

спасибо

Майк

1 Ответ

1 голос
/ 29 января 2020

Я думаю, у вас есть два варианта: (1) UPSERT или (2) параметризованных запросов.

Первый имеет преимущество в скорости (часто в зависимости от СУБД) при за счет СУБД-специфика c диалект SQL и небольшая сложность. Второй имеет преимущество простоты, но может занять больше времени, если у вас много строк.

1. UPSERT

Шаги: создать временную таблицу (см. Примечания); загрузить данные; выполните операцию обновления с разрешением конфликтов.

Я использую temp_table_997 в качестве временной таблицы здесь, но есть много способов иметь дело с временными таблицами, чтобы вы случайно не оставили их. Я обнаружил, что успех зависит от СУБД, поэтому я оставлю это на усмотрение читателя.

DBI::dbExecute(con, "
  CREATE TABLE temp_table_997 AS
  SELECT oseast1m, osnrth1m, Postcode FROM ons LIMIT 0")                         # [1,2]
DBI::dbWriteTable(con, "temp_table_997", result[,c("east", "west", "postcode")]) # [3,4]
DBI::dbExecute(con, "
  INSERT INTO ons (oseast1m, osnrth1m)
    SELECT oseast1m, osnrth1m
    FROM temp_table_997
  ON CONFLICT ( Postcode ) DO
    UPDATE SET oseast1m=EXCLUDED.oseast1m, osnrth1m=EXCLUDED.osnrth1m
")                                                                               # [5]

Примечания:

  1. Другие ответы / статьи которые используют эту технику, могут использовать select * ..., хотя лучшие практики это не одобряют Как правило, лучше указывать в таблице и только необходимые поля.

  2. Я использую create table ... as select ..., чтобы типы столбцов были сохранены. Особенно со всеми различными типами чисел (float, integer, bigint, smallint, даже "bit") и другими полями ... и с тем фактом, что R не go до этого уровня детализации, я считаю, что лучше всего быть явным при загрузке данных. Использование этого метода гарантирует, что тип, используемый в целевой таблице, является тем, что фактически используется. Это может быть необязательно в некоторых СУБД, но я не думаю, что это больно.

  3. Как и в примечании 1, вам, вероятно, следует загружать только необходимые столбцы, включая поле идентификации и поля с обновлениями; если есть поля, которые никогда не обновляются, нет необходимости тратить пропускную способность, а для больших наборов данных это может оказать значительное влияние на время загрузки. (Например, results[,c("Postcode",...)]).

  4. Хотя инструменты и базы данных, которые я использую, достаточно умны, чтобы справляться со столбцами не по порядку, я не знаю, что это так все СУБД, поэтому, вероятно, лучше и проще сохранять порядок столбцов одинаковым.

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

Это работает (для меня) на SQLite и Postgres, но язык для других СУБД может быть таким же или очень похожий.

Для SQL Server вам нужен немного другой запрос. (Поймите, что CREATE ... SELECT ... LIMIT 0 выше должно быть CREATE ... SELECT TOP 0 ..., из-за SQL диалекта Сервера.)

DBI::dbExecute(con, "
  DECLARE @dummy int;
  MERGE ons WITH (HOLDLOCK) as tgt
  USING (SELECT ... FROM temp_table_997) as src
    ON ( tgt.Postcode )
  WHEN MATCHED THEN UPDATE SET tgt.oseast1m=src.oseast1m, tgt.osnrth1m=src.osnrth1m
  WHEN NOT MATCHED THEN INSERT (oseast1m, oseast1m) values (src.oseast1m, src.oseast1m)
")

Если вы используете этот метод, не забудьте очистить:

DBI::dbExecute(con, "drop table temp_table_997")

2. Связывание (параметризованные запросы)

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

res <- DBI::dbSendStatement(con, "
  UPDATE ons
  SET ons.oseast1m=?, ons.osnrth1m=?
  WHERE ons.Postcode=?")
DBI::dbBind(res, result[,c("east", "west", "postcode")]) # order and number of columns is important
DBI::dbGetRowsAffected(res)

Метод указания параметров (? выше) зависит исключительно от СУБД, а не от пакетов DBI или odbc; вам нужно найти конкретный метод c для вашего. Это может быть ?, ?name, $name или :name; возможно, существуют и другие.

(Я признаю, что это может быть столь же эффективно. Несколько лет я пробовал несколько методов go, и из-за используемого драйвера или из-за одной версии DBI или даже мое недопонимание вещей ... возможно, что это так же эффективно, как упор. Я не собираюсь проверять это сейчас, так как разница может иметь отношение только к большим наборам данных. YMMV.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...