Преобразование большого количества результатов базы данных в массив объектов в javascript

#javascript #node.js

#javascript #node.js

Вопрос:

Я извлекаю запрос к базе данных, который содержит следующую информацию:

id, name, roleId, roleTitle

В запросе я указываю пользователей и их роли. У каждого пользователя может быть от 0 до N ролей. Я хочу, чтобы в конце концов у меня был такой объект, как этот:

 {
    id
    name
    roles: [{
       id
       title
    }]
}
 

Каким был бы наиболее эффективный способ сделать это? В настоящее время я делаю что-то вроде этого:

 const data = [];
arr.forEach((u) => {
   const index = data.findIndex(x => x.id === u.id);
    if (index >= 0) {
      data[index].roles.push({ id: u.roleId, title: u.roleTitle });
    } else {
      data.push({
        id: u.id,
        name: u.name,
        roles: u.roleId ? [{
          id: u.roleId,
          title: u.roleTitle,
        }] : [],
      });
    }
}
 

Это решение работает правильно, но я не был уверен, что это самый быстрый способ сделать это, если мы увеличим количество пользователей до 10 тыс. со средней ролью на пользователя 3 или 50 тыс. и 5 ролей на пользователя

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

1. Нет, это излишне медленно: замените массив хэшем или набором. Каждый раз, когда вы пытаетесь найти then index, вам приходится обходить весь массив.

2. Если вам действительно нужно сделать это на стороне клиента, то я бы использовал Web Workers для анализа этого, если вы считаете, что ваш набор данных будет масштабироваться до 5 цифр и более.

3. @AbanaClara Я предполагаю node.js даже несмотря на то, что ОП не пометил его как таковой.

4. Можете ли вы предоставить некоторые демонстрационные данные

5. Какую базу данных вы используете? Некоторые базы данных (например, PostgreSQL, Oracle) поддерживают JSON, и в этом случае вы могли бы использовать такой метод, как json_agg и выполнять большую часть работы на уровне базы данных, что было бы намного эффективнее.

Ответ №1:

Лучше всего на самом деле делать все это в SQL, поскольку вы используете PostgreSQL для своей базы данных (как указано в комментариях). Я не знаю точных названий ваших таблиц и столбцов, поэтому вам может потребоваться изменить это, но это даст вам то, что вы хотите:

 SELECT json_agg(t)
FROM (
  SELECT
    u.id,
    u.name,
    ro.roles
  FROM "user" u
  LEFT JOIN (
    SELECT
      ur.user_id,
      json_agg(
        json_build_object(
          'id', r.id,
          'title', r.title
        )
      ) AS roles
    FROM user_role ur
    LEFT JOIN "role" r ON r.id = ur.role_id
    GROUP BY ur.user_id
  ) ro ON ro.user_id = u.id
) t;
 

Скрипка SQL: http://www.sqlfiddle.com /#!17/5f6ca/11

Объяснение

json_build_object создаст объект, используя указанные пары имя / значение, поэтому:

 json_build_object(
  'id', r.id,
  'title', r.title
)
 

Объединяет роль id и title в объект JSON, подобный этому:

 {id: 1, title: "Title 1"}
 

json_agg объединяет несколько строк в массив JSON, поэтому он преобразует ролевые объекты выше в один столбец, который представляет собой массив ролевых объектов для каждого пользователя (благодаря GROUP BY u.id части внутреннего подзапроса). Внутренний подзапрос выдает нам результирующий набор, подобный этому (одна строка на пользователя)

 | user_id |                       roles                          |
|---------|------------------------------------------------------|
|    1    | [{id: 1, title: "Role 1"}, {id: 2, title: "Role 2"}] |
 

Затем подзапрос присоединяется к таблице user, и все это оборачивается в другой подзапрос, поэтому json_agg может использоваться для всего результата и возвращать один объект json, который представляет собой массив пользователей с ролями.

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

1. Спасибо! Это выглядит великолепно. Один последний вопрос, в последнем результате, у него есть {"id":3,"name":"User 3","roles":[{"id" : null, "title" : null}]} . Есть ли способ вернуться null roles , когда нет результатов?

2. @user081608 Да, это должно быть возможно, дайте мне минуту, чтобы обновить его.

3. @user081608 Хорошо, я обновил его, чтобы возвращать null roles , когда нет ролей. Извините за все правки в ответе, у меня возникли проблемы с привязкой к правильному sql-скрипту.

4. @user081608 Рад помочь! Базы данных, добавляющие встроенную поддержку JSON, действительно эффективны (MySQL добавил ее в 5.7). Всякий раз, когда вы работаете с большим набором данных или выполняете агрегирование, обычно быстрее сделать это на уровне базы данных.

Ответ №2:

Это почти наверняка не самая эффективная из возможных версий, но быстрее, чем то, что вы делаете сейчас:

 const data = Object.values(arr.reduce((obj, {id, name, roleId, roleTitle}) => {
  if (!(id in obj)) {
    obj[id] = {
      id,
      name,
      roles: {},
    };
  }
  if (!obj[id].roles[roleId]) {
    obj[id].roles[roleId] = {
      id: roleId,
      title: roleTitle,
    };
  }
  return obj;
}, {}));
 

При использовании объектов (хэшей) вместо массивов определение того, есть ли пользователь уже там или у пользователя уже есть роль, представляет собой операцию O (1) с постоянным временем (стоимость функции хеширования). Но поиск в массиве, в зависимости от используемого метода поиска, является линейным в худшем случае O (n), и даже в лучшем случае O (log n) .

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

Я использовал Object.values для преобразования обратно в массив в конце, если вы опустите это и просто будете придерживаться объектов, это может быть еще быстрее.

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

1. Спасибо, Джаред, в этом есть смысл. Я уже делал это на Java раньше, но не знал, что здесь верна та же концепция

2. @user081608 ага. Javascript становится лучше разбираться в структурах данных. Для этого я бы предпочел использовать объекты и массивы вместо карт и наборов, поскольку, похоже, в какой-то момент вы, вероятно, будете сериализовать их в формате JSON. Надеюсь, это сработает для вас!

Ответ №3:

Надеюсь, это поможет.

 var modified_array = function(xs, key) {
  return xs.reduce(function(rv, x) {
    obj = (rv[x[key]] = rv[x[key]] || {});
    obj.id = x.id;
    obj.name = x.name;
    obj.roles = obj.roles || []
    obj.roles.push({ id: x.roleId, title: x.roleTitle})
    return rv;
  }, {});
};

arr = [{id:1,name:"abd",roleId: 10,roleTitle: "hello"},
       {id:1, name: "abd", roleId: 15,roleTitle: "fello"}]    
console.log( Object.values(modified_array(arr, 'id')));