Search code examples
mysqlnode.jscsvsequelize.jsfast-csv

Sequelize - How to insert csv data into multiple tables in mysql using sequelize


What I'm using

node.js, express, sequelize 6.3.3, fast-csv 4.3.1 and mysql database.

What I have

I have a CSV file with following headers and data.

no, name, email, township, father_name, father_phone, mother_name, mother_phone, parent_township

In this CSV, I have two types of data: students and parents.

In my MySQL database, I have three tables:

students, parents and townships 

Students table:

id, name, email, township_id

Parents table:

id, student_id, father_name, father_phone, mother_name, mother_phone, parent_township_id

Townships table:

id, name

What I've done

I read the CSV with fast-csv npm package with the following code.

let csvData = [];

fs.createReadStream(req.file.path)
    .pipe(
      csv.parse({
        ignoreEmpty: true,
        trim: true,
        skipLines: 6,
      })
    )
    .on("error", (error) => console.error(error))
    .on("data", (row) => {
      csvData.push(getHeaders(_.compact(row), csvStudentDataEntryFields));
    })
    .on("end", () => {
      fs.unlinkSync(req.file.path);

      const data = csvData.map((data) => {
        const student = _.pick(data, studentFields);
        const parent = _.pick(data, parentFields);
        return [student, parent];
      });

      return res.status(201).send({
        status: "success",
        data,
      });
    });

What I got

With the above code, I got data with following values.

[
    [ // row 1 from CSV
        {
            "name": "John",
            "email": "[email protected]",
            "township": "NYC",
        },
        {
            "fatherName": "Smith",
            "fatherPhone": "9111111111",
            "motherName": "Mary",
            "motherPhone": "9111111111",
            "parentTownship": "NYC"
         }
    ],
    [ // row 2 from CSV
        {
            "name": "Cutter",
            "email": "[email protected]",
            "township": "NYC",
        },
        {
            "fatherName": "Laseli",
            "fatherPhone": "9111111111",
            "motherName": "Mary",
            "motherPhone": "9111111111",
            "parentTownship": "NYC"
        }
    ]
]

What I want

I want to store those row 1 and row 2 from data into respective tables in the database.

The problems

I think I need to replace those township text data with real id because I have foreign keys stated as above.

How can I achieve it? I want to do it in database level. I don't want to look up the id of that township name in separate js module.

Updated

Student Model

class Student extends Model {
    static associate(models) {
      Student.belongsTo(models.Township, {
        foreignKey: "townshipId",
        as: "township",
        targetKey: "townshipId",
      });

     Student.hasOne(models.Parent, {
        foreignKey: "studentId",
        as: "parent",
        sourceKey: "studentId",
      });
    }
  }

Parent Model

class Parent extends Model {
    static associate(models) {
      Parent.belongsTo(models.Student, {
        foreignKey: "studentId",
        as: "student",
        targetKey: "studentId",
      });

      Parent.belongsTo(models.Township, {
        foreignKey: "parentTownshipId",
        as: "township",
        targetKey: "townshipId",
      });
    }
  }

Township Model

class Township extends Model {
    static associate(models) {
      Township.hasMany(models.Student, {
        foreignKey: "townshipId",
        as: "township",
        sourceKey: "townshipId",
      });

      Township.hasMany(models.Parent, {
        foreignKey: "townshipId",
        as: "parentTownship",
        sourceKey: "townshipId",
      });
    }
  }

Updated:

in my controller,

let std = await models.Student.create(
    {
        nameEn: "John",
        email: "[email protected]",
        townshipId: 1,
        parent: [{ ...d[1], parentTownshipId: 1 }],
    },
    { include: ["parent"] }
);

Solution

  • I think you can insert all relationship together.

    This is from sequelize document

    const amidala = await User.create({
      username: 'p4dm3',
      points: 1000,
      profiles: [{
        name: 'Queen',
        User_Profile: {
          selfGranted: true
        }
      }]
    }, {
      include: Profile
    });
    
    const result = await User.findOne({
      where: { username: 'p4dm3' },
      include: Profile
    });
    
    console.log(result);
    

    set source key

     const Foo = sequelize.define('foo', {
      name: { type: DataTypes.TEXT, unique: true }
    }, { timestamps: false });
    const Bar = sequelize.define('bar', {
      title: { type: DataTypes.TEXT, unique: true }
    }, { timestamps: false });
    const Baz = sequelize.define('baz', { summary: DataTypes.TEXT }, { timestamps: false });
    Foo.hasOne(Bar, { sourceKey: 'name', foreignKey: 'fooName' });
    Bar.hasMany(Baz, { sourceKey: 'title', foreignKey: 'barTitle' });
    // [...]
    await Bar.setFoo("Foo's Name Here");
    await Baz.addBar("Bar's Title Here");
    

    ref https://sequelize.org/master/manual/advanced-many-to-many.html#using-one-to-many-relationships-instead