skydum

個人的な作業記録とか備忘録代わりのメモ

MongoDB+mongoose

MongoDB

最近良くNode.jsのmongooseでMongoDBをよく使う。
aggregateってMongoDBだと使ったことがなかったので少し触ってみた。
aggregateはDjangoのaggregateと同様に何でもできそうな感じがある。
aggregateでデータ件数が多いデータを処理するときは最初にmatchを書くといいようだ。
簡単なフィルターならpopulateの方がわかりやすくて良いなと思った。 Node.jsは個人的にあまり好きになれない…。

docker compose

# Use root/example as user/password credentials

services:

  mongo:
    image: mongo:7.0
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    ports:
      - 27017:27017

  mongo-express:
    image: mongo-express
    restart: always
    ports:
      - 8081:8081
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: example
      ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
      ME_CONFIG_BASICAUTH: false

ソース

const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");

const app = express();

app.use(express.urlencoded({ extended: true }));
app.use(cors());

const dburl =
    "mongodb://root:example@localhost:27017/mongo-db?authSource=admin";

mongoose
    .connect(dburl)
    .then(() => {
        console.log("DB connected");
    })
    .catch((err) => {
        console.log(err);
    });

// insert data
const Schema = mongoose.Schema;

const commentSchema = new mongoose.Schema({
    content: { type: String, required: true },
    author: { type: String, required: true },
});

const Comment = mongoose.model("Comment", commentSchema);

const blogSchema = new Schema(
    {
        title: {
            type: String,
            required: true,
        },
        content: {
            type: String,
            required: true,
        },
        comments: [{ type: mongoose.Schema.Types.ObjectId, ref: "Comment" }],
    },
    { timestamps: true }
);

const Blog = mongoose.model("Blog", blogSchema);

async function insertData() {
    const blog = new Blog({
        title: "Blog 1",
        content: "This is blog 1",
    });

    const comment1 = new Comment({
        content: "This is the first comment",
        author: "User1",
    });

    await comment1.save();

    const comment2 = new Comment({
        content: "This is the second comment.",
        author: "User2",
    });

    await comment2.save();

    blog.comments = [comment1._id, comment2._id];
    const res = await blog.save();

    console.log(res);
}

async function filterData() {
    const blogWithComments = await Blog.find().populate("comments");
    console.log(`all data`);
    console.log(`comment author: ${blogWithComments[0].comments[0].author}`);
    console.log(`comment author: ${blogWithComments[0].comments[1].author}`);

    console.log("populate filter data");
    const populateFilter = await Blog.find().populate({
        path: "comments",
        match: { author: "User1" },
    });
    if (populateFilter == {}) {
        console.error("no match");
    } else if (populateFilter.length == 1) {
        console.log(`comment author: ${populateFilter[0].comments[0].author}`);
    } else {
        console.log(`comment author: ${populateFilter[0].comments[0].author}`);
        console.log(`comment author: ${populateFilter[0].comments[1].author}`);
    }

    console.log("aggregate filter data");
    const aggregateFilter = await Blog.aggregate([
        {
            $lookup: {
                from: "comments", // MongDBのコレクション名を指定する
                localField: "comments", // blogSchemaのフィールド名を指定
                foreignField: "_id", // commentsのリレーションはcommentsのどのキーに対応するのかを指定
                as: "allMatchedComments", // 結果を入れるキー名
            },
        },
        {
            $addFields: {
                filteredComments: {
                    $filter: {
                        input: "$allMatchedComments", // フィルタ対象の配列(今回は結果を入れるキー名で指定したキー名を入れる)
                        as: "singleComment", // 配列の各要素を参照する変数名
                        cond: { $eq: ["$$singleComment.author", "User1"] }, // フィルターする条件
                    },
                },
            },
        },
    ]);
    if (aggregateFilter == {}) {
        console.error("no match");
    } else if (aggregateFilter.length == 1) {
        console.log(
            `comment author: ${aggregateFilter[0].filteredComments[0].author}`
        );
    } else {
        console.log(
            `comment author: ${aggregateFilter[0].filteredComments[0].author}`
        );
        console.log(
            `comment author: ${aggregateFilter[0].filteredComments[1].author}`
        );
    }
}

(async () => {
    await insertData();
    await filterData();
})();