Продвинутое использование ORM Sequelize

05.02.2019 17:52

В этот раз мы углубимся в join реквесты, сеттеры и ещё пару вещей, а для затравки разберём пару простых методов:

Подсчёт количества строк в таблице

let value = await model.count({});

Вернёт целое значение, количество строк в таблице. Можно использовать условия, так же как и в findAll:

let value = await model.count({ where: { someVal: 123 }});

Получение максимального/минимального значения

let minVal = await model.min('fieldName', {});
let maxVal = await model.max('fieldname', {});

fieldName - имя колонки. Условия поиска передаются вторым параметром:

let minVal = await model.min('fieldName', { where: { ... } });
let maxVal = await model.max('fieldname', { where: { ... } });

Join запросы

Давайте возьмём 2 таблички из прошлого примера:

posts:
id | title | body

comments:
id | posts_id | userName | comment

Между ними нужно добавить ассоциацию:

posts.belongsTo(comments, { as: 'post_comments', foreignKey: 'id', targetKey: 'posts_id'});

LEFT OUTER JOIN

И прежде чем выполнять запрос, нужно добавить инклюд:

  let queryParams = {
    where: {},
    include: [
      {
        model: sequelize.models.comments,
        as: 'post_comments',
        //attributes: [ 'id', 'name' ]
      }
    ]
  };
  let data1 = await sequelize.models.posts.findAll(queryParams);

Эта конструкция выполнит следующий запрос:

SELECT `posts`.`id`, `posts`.`title`, `posts`.`body`, `posts`.`createdAt`, `posts`.`updatedAt`, `post_comments`.`id` AS `post_comments.id`, `post_comments`.`posts_id` AS `post_comments.posts_id`, `post_comments`.`userName` AS `post_comments.userName`, `post_comments`.`comment` AS `post_comments.comment`, `post_comments`.`createdAt` AS `post_comments.createdAt`, `post_comments`.`updatedAt` AS `post_comments.updatedAt` FROM `posts` AS `posts` LEFT OUTER JOIN `comments` AS `post_comments` ON `posts`.`id` = `post_comments`.`posts_id`;

Но в отличии от оригинального sql, данные по комментам будут лежать не в одной строке, а в отдельном объекте который мы назвали post_comments:

{ id: 1,
  title: 'Post title 1',
  body: 'Ololo ololo ya voditel NLO',
  createdAt: 2019-01-26T07:22:50.034Z,
  updatedAt: 2019-01-26T07:22:50.034Z,
  post_comments:
   comments {
    dataValues: { id: 1,
      posts_id: 1,
      userName: 'Ololoev_O',
      comment: 'Preved medved',
      createdAt: 2019-01-27T05:20:05.348Z,
      updatedAt: 2019-01-27T05:41:47.133Z
    },
   }
}

Если данных для post_comments не будет то там будет лежать нулл.

Теперь сделаем INNER JOIN.

Так же как и в прошлом примере нужно сделать инклюд в параметрах:

  let queryParams = {
    where: {},
    include: [
      {
        model: sequelize.models.comments,
        as: 'post_comments',
        required: true, // ОТЛИЧИЯ ТУТ <<<----
        //attributes: [ 'id', 'name' ]
      }
    ]
  };
  let data1 = await sequelize.models.posts.findAll(queryParams);

Отличия заключаются в поле "required: true". В результате выполнится запрос:

SELECT `posts`.`id`, `posts`.`title`, `posts`.`body`, `posts`.`createdAt`, `posts`.`updatedAt`, `post_comments`.`id` AS `post_comments.id`, `post_comments`.`posts_id` AS `post_comments.posts_id`, `post_comments`.`userName` AS `post_comments.userName`, `post_comments`.`comment` AS `post_comments.comment`, `post_comments`.`createdAt` AS `post_comments.createdAt`, `post_comments`.`updatedAt` AS `post_comments.updatedAt` FROM `posts` AS `posts` INNER JOIN `comments` AS `post_comments` ON `posts`.`id` = `post_comments`.`posts_id`;

И в этом случае, если данных для post_comments не будет, то НЕ вернётся вся строка целиком.

Ещё больше всяких интересных примеров с джоинами можно посмотреть в доке: http://docs.sequelizejs.com/manual/tutorial/associations.html

Сортировка результатов

Простая сортировка по какому-нить столбцу:

  let queryParams = {
    where: {},
    order: [ [ 'id', 'ASC' ] ]
  };
  let data1 = await sequelize.models.posts.findAll(queryParams);

Если нужно отсортировать по вложенной модели:

  let queryParams = {
    where: {},
    order: [ [ 'имя_ассоциации', 'столбец', 'тип сортировки' ] ]
  };
  let data1 = await sequelize.models.posts.findAll(queryParams);

Ну и если нужно отсортировать по вложенной модели, которая вложена в ещё одну модель то:

  let queryParams = {
    where: {},
    order: [ [ 'имя_ассоциации', 'имя_ещё_более_глубокой_ассоциации', 'столбец', 'тип сортировки' ] ]
  };
  let data1 = await sequelize.models.posts.findAll(queryParams);

Группировка запросов

  let queryParams = {
    where: {},
    group: [ 'title' ]
  };
  let data1 = await sequelize.models.posts.findAll(queryParams);

Эквивалентно запросу:

select * from posts group by title

Сеттеры

Для примера, добавить в табличку posts поле randomVal

  randomVal: {
    type: Sequelize.DataTypes.STRING,
    set (val) {
      // делаем что-то с переданным val
      // И присваиваем значение
      this.setDataValue('randomVal', `${val}-test-${Math.random()}`);
    }
  }

И когда мы будем добавлять данные в табличку, то значение будет автоматически перегенерироваться:

await sequelize.models.posts.create({ title: 'Setter-test1', body: 'Setter-body', randomVal: '123' });

Получим:

{ id: 1,
  title: 'Setter-test1',
  body: 'Setter-body',
  randomVal: '123-test-0.7252707392753635',
  createdAt: 2019-02-05T17:34:03.918Z,
  updatedAt: 2019-02-05T17:34:03.918Z
}

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

Валидация данных

Тут лучше чем в оригинальной документации не напишешь:

const ValidateMe = sequelize.define('foo', {
  foo: {
    type: Sequelize.STRING,
    validate: {
      is: ["^[a-z]+$",'i'],     // will only allow letters
      is: /^[a-z]+$/i,          // same as the previous example using real RegExp
      not: ["[a-z]",'i'],       // will not allow letters
      isEmail: true,            // checks for email format (foo@bar.com)
      isUrl: true,              // checks for url format (http://foo.com)
      isIP: true,               // checks for IPv4 (129.89.23.1) or IPv6 format
      isIPv4: true,             // checks for IPv4 (129.89.23.1)
      isIPv6: true,             // checks for IPv6 format
      isAlpha: true,            // will only allow letters
      isAlphanumeric: true,     // will only allow alphanumeric characters, so "_abc" will fail
      isNumeric: true,          // will only allow numbers
      isInt: true,              // checks for valid integers
      isFloat: true,            // checks for valid floating point numbers
      isDecimal: true,          // checks for any numbers
      isLowercase: true,        // checks for lowercase
      isUppercase: true,        // checks for uppercase
      notNull: true,            // won't allow null
      isNull: true,             // only allows null
      notEmpty: true,           // don't allow empty strings
      equals: 'specific value', // only allow a specific value
      contains: 'foo',          // force specific substrings
      notIn: [['foo', 'bar']],  // check the value is not one of these
      isIn: [['foo', 'bar']],   // check the value is one of these
      notContains: 'bar',       // don't allow specific substrings
      len: [2,10],              // only allow values with length between 2 and 10
      isUUID: 4,                // only allow uuids
      isDate: true,             // only allow date strings
      isAfter: "2011-11-05",    // only allow date strings after a specific date
      isBefore: "2011-11-05",   // only allow date strings before a specific date
      max: 23,                  // only allow values <= 23
      min: 23,                  // only allow values >= 23
      isCreditCard: true,       // check for valid credit card numbers

      // custom validations are also possible:
      isEven(value) {
        if (parseInt(value) % 2 != 0) {
          throw new Error('Only even values are allowed!')
          // we also are in the model's context here, so this.otherField
          // would get the value of otherField if it existed
        }
      }
    }
  }
});

Индексы

По умолчанию на все autoincrement поля сикулайз автоматически добавить BTREE индекс. А вот на остальные поля, индексы нужно добавлять вручную, например давайте добавим тот же BTREE на наше поле randomVal. Модифицируем нашу модельку:

let posts = sequelize.define('posts', {
  id: {
    allowNull: false,
    autoIncrement: true,
    primaryKey: true,
    type: Sequelize.DataTypes.INTEGER
  },
  title: {
    type: Sequelize.DataTypes.STRING,
    allowNull: false
  },
  body: {
    type: Sequelize.DataTypes.STRING,
  },
  randomVal: {
    type: Sequelize.DataTypes.STRING,
    set (val) {
      // делаем что-то с переданным val
      // И присваиваем значение
      this.setDataValue('randomVal', `${val}-test-${Math.random()}`);
    }
  }
}, {
  timestamps: true, // Колонки createdAt и updatedAt будут созданы автоматически
  indexes: [ // вот тут и описываются индексы <<<----
    {
      name: 'posts_randomVal', // Имя индекса
      method: 'BTREE', // Тип
      fields: [ 'randomVal' ] // По каким колонкам
    },
  ]
});

И при синхронизации моделей, будет выполнен запрос:

CREATE INDEX `posts_randomVal` ON `posts` (`randomVal`);


Как использовать ORM Sequelize

Sequelize - это ORM библиотека для nodejs. Sequelize поддерживает PostgreSQL, MySQL, SQLite и MSSQL диалекты.

Тестируем near protocol

Есть такой проект, "near protocol", это очередной блокчейн. В отличии от биткоина и эфира, он в теории, должен быть быстрым и масштабируемым. Прям сейчас проект "is currently under heavy development".


(0) Комментариев