Использование upsert в postgres с файлом json в nodejs

#sql #node.js #json #postgresql #sequelize.js

#sql #node.js #json #postgresql #sequelize.js

Вопрос:

У меня есть большой файл данных в формате json, который я хочу поместить в свою базу данных. Некоторые объекты являются дубликатами, поэтому я хочу обновить данные на случай, если строка уже есть в базе данных. Вот мой код:

 const FILE_PATH = path.join(__dirname, "../../files/apps.json");

const columns = [
          "name",
          "description",
          "ext_id"
        ];

const myFile = fs.readFileSync(FILE_PATH, { encoding: "utf-8" });
const appData = await models.sequelize.query(
          `
        INSERT INTO data (${columns.join(", ")})
        SELECT ${columns.join(", ")}
        FROM (:path)
        ON CONFLICT (ext_id)
          DO UPDATE SET
            ${columns.map(col => `${col} = EXCLUDED.${col}`).join(", ")}
        RETURNING ext_id;
        `,
          { replacements: { path: FILE_PATH } }
        );
  

Как вы можете видеть, я хочу прочитать файл напрямую и поместить его в базу данных. Я использую средство отображения под названием sequelize , но в данном случае использую необработанный запрос. Моя непосредственная проблема заключается в том, что я получаю эту ошибку:

 syntax error at or near "'/home/blub/filePath'"
  

Я действительно не знаю, как мне следует указывать путь. Я попытался проанализировать его напрямую, но затем программа пожаловалась на /. Любая помощь здесь? Кроме того, я также не уверен, является ли запрос синтаксически правильным.

Ответ №1:

Вот решение, использующее CTE postgres.

Версии:

  • "sequelize": "^5.21.3"
  • postgres: 9.6

apps.json :

 [
    {
        "name": "app-a",
        "description": "app a desc",
        "ext_id": 1
    },
    {
        "name": "app-b",
        "description": "app b desc",
        "ext_id": 2
    },
    {
        "name": "app-c",
        "description": "app c desc",
        "ext_id": 3
    }
]
  

index.ts :

 import { sequelize } from '../../db';
import { Model, DataTypes, QueryTypes } from 'sequelize';
import fs from 'fs';
import path from 'path';

class Data extends Model {}
Data.init(
  {
    name: DataTypes.STRING,
    description: DataTypes.STRING,
    ext_id: {
      type: DataTypes.INTEGER,
      unique: true,
    },
  },
  { sequelize, tableName: 'data' },
);

(async function test() {
  try {
    await sequelize.sync({ force: true });
    const FILE_PATH = path.join(__dirname, './apps.json');
    const columns = ['name', 'description', 'ext_id'];
    const myFile = fs.readFileSync(FILE_PATH, { encoding: 'utf-8' });
    const appData = await sequelize.query(
      `
        with app_json(doc) as (
            values ('${myFile}'::json)
        )
        insert into data (${columns.join(', ')})
        select ${columns.join(', ')}
        from app_json l
            cross join lateral json_populate_recordset(null::data, doc) as p
        on conflict (ext_id) do update 
            set ${columns.map((col) => `${col} = EXCLUDED.${col}`).join(', ')}
        returning ext_id;
    `,
      { type: QueryTypes.INSERT },
    );
    console.log(appData);
  } catch (error) {
    console.log(error);
  } finally {
    await sequelize.close();
  }
})();
  

Результат выполнения:

 Executing (default): DROP TABLE IF EXISTS "data" CASCADE;
Executing (default): DROP TABLE IF EXISTS "data" CASCADE;
Executing (default): CREATE TABLE IF NOT EXISTS "data" ("id"   SERIAL , "name" VARCHAR(255), "description" VARCHAR(255), "ext_id" INTEGER UNIQUE, PRIMARY KEY ("id"));
Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'data' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;
Executing (default): with app_json(doc) as (
            values ('[
    {
        "name": "app-a",
        "description": "app a desc",
        "ext_id": 1
    },
    {
        "name": "app-b",
        "description": "app b desc",
        "ext_id": 2
    },
    {
        "name": "app-c",
        "description": "app c desc",
        "ext_id": 3
    }
]'::json)
        )
        insert into data (name, description, ext_id)
        select name, description, ext_id
        from app_json l
            cross join lateral json_populate_recordset(null::data, doc) as p
        on conflict (ext_id) do update 
            set name = EXCLUDED.name, description = EXCLUDED.description, ext_id = EXCLUDED.ext_id
        returning ext_id;
[ [ { ext_id: 1 }, { ext_id: 2 }, { ext_id: 3 } ], 3 ]
  

Проверьте записи данных в базе данных:

 id  name    description ext_id
1   app-a   app a desc  1
2   app-b   app b desc  2
3   app-c   app c desc  3
  

Комментарии:

1. Спасибо за вашу помощь. Я уже нашел обходной путь, но ваш ответ очень ценится.