Сумма транзакций на ежедневных или месячных интервалах - PullRequest
0 голосов
/ 03 мая 2018

В моей коллекции зарегистрированы транзакции.

{
   "_id":{
      "$oid":"5ae77296c780351beadc5518"
   },
   "payment_amount":10000,
   "store_id":{
      "$oid":"5aa6babce7c97f0875556ae6"
   },
   "operator_id":{
      "$oid":"5aa6ba95e7c97f0875556ae3"
   },
   "cashier_id":{
      "$oid":"5acd4144c94ba7250da4af78"
   },
   "player_id":{
      "$oid":"5ae75fccc780351beadc5493"
   },
   "payment_type":"deposit",
   "payment_time":{
      "$date":"2018-04-30T19:46:30.055+0000"
   },
   "__v":0
}

Теперь мне нужно определенным образом агрегировать данные.

  1. за последние 12 месяцев мне нужно 12 результатов для каждого месяца. в каждом результате должна быть сумма всех payment_amount, где payment_type равна deposit и сумма всех payment_type == withdraw

  2. за последние 31 день мне нужны те же суммы депозита и снятия, но в день.

Я использую мангуст, и это моя схема

//schema definition
var Sch = new Schema({
    store_id: Schema.Types.ObjectId,
    operator_id: Schema.Types.ObjectId, //users._id
    cashier_id: {type: Schema.Types.ObjectId, ref: 'usersMD'}, //users._id
    player_id: {type: Schema.Types.ObjectId, ref: 'playersMD'},
    payment_type: {type: String, enum: ['deposit', 'withdraw']},
    payment_amount: {type: Number, default: 0},
    payment_time: {type: Date, default: Date.now}
}, opts);

Как лучше всего подойти к этому?

Я думал создать два метода в модуле log_transaction в моем API.

Один будет призывать к 1., а другой - к 2.

1 Ответ

0 голосов
/ 04 мая 2018

Я в основном собираюсь поговорить здесь о деталях «реализации», потому что есть здравый вывод о том, как на самом деле построить методы вокруг этого, как только вы поймете, как это на самом деле делается.

В идеальной ситуации вы на самом деле записываете свои данные «предварительно накопленными» за такие интервалы, как «месяц» или «день» во время хранения. Это обычно, как мы делаем это в средах с большим объемом, записывая накопленные итоги по мере их возникновения.

Без этого вы прибегаете к агрегации существующих данных, где вам следует использовать структуру агрегации. Обычно у вас есть хотя бы некоторый код для сообщения о различных временных интервалах, которые вы на самом деле не «предварительно накапливали» в любом случае.

Основная вещь, которую вы ищете здесь, это оператор $cond. Это «троичное» или if/then/else условие, которое позволяет выразить условие для if, которое разветвляет логику для возврата значения с условием, истинным как then или ложным как else.

Это оператор, который позволяет нам посмотреть на "payment_type" и решить, имеет ли значение «положительное» или «отрицательное» числовое представление при накоплении с $sum. Итак, основное утверждение здесь:

 "$sum": {
   "$cond": {
     "if": { "$eq": [ "$payment_type", "deposit" ] },
     "then": "$payment_amount",
     "else": { "$subtract": [ 0, "$payment_amount" ] }
   }
 }

Здесь применяется основная математика, поэтому остальная часть задачи накопления заключается в сборе «за интервал времени», и для этого есть несколько различных способов:

в месяц

Использование MongoDB 3.6 $ dateFromParts

Model.aggregate([
  { "$match": {
    "payment_time": { "$gte": start_date, "$lt": end_date }
  }},
  { "$group": {
    "_id": {
      "$dateFromParts": {
        "year": { "$year": "$payment_time" },
        "month": { "$month": "$payment_time" }
      }
    },
    "balance": {
      "$sum": {
        "$cond": {
          "if": { "$eq": [ "$payment_type", "deposit" ] },
          "then": "$payment_amount",
          "else": { "$subtract": [ 0, "$payment_amount" ] }
        }
      }
    }
  }}
])

Использование Date Math

Model.aggregate([
  { "$match": {
    "payment_time": { "$gte": start_date, "$lt": end_date }
  }},
  { "$group": {
    "_id": {
      "$add": [
        { "$subtract": [
          { "$subtract": ["$payment_time", new Date(0)] },
          { "$mod": [
            { "$subtract": ["$payment_time", new Date(0)] },
            1000 * 60 * 60 * 24
          ]}
        ]},
        { "$multiply": [
          { "$subtract": [{ "$dayOfMonth": "$payment_time" }, 1] },
          -1000 * 60 * 60 * 24
        ]},
        new Date(0)
      ]
    },
    "balance": {
      "$sum": {
        "$cond": {
          "if": { "$eq": [ "$payment_type", "deposit" ] },
          "then": "$payment_amount",
          "else": { "$subtract": [ 0, "$payment_amount" ] }
        }
      }
    }
  }}
])

Операторы с обычной датой

Model.aggregate([
  { "$match": {
    "payment_time": { "$gte": start_date, "$lt": end_date }
  }},
  { "$group": {
    "_id": {
      "year": { "$year": "$payment_time" },
      "month": { "$month": "$payment_time" }
    },
    "balance": {
      "$sum": {
        "$cond": {
          "if": { "$eq": [ "$payment_type", "deposit" ] },
          "then": "$payment_amount",
          "else": { "$subtract": [ 0, "$payment_amount" ] }
        }
      }
    }
  }}
])

Разница между первыми двумя методами и последним заключается в том, что эти первоначальные методы действительно возвращают объект Date BSON, который будет представлен в NodeJS как стандартный объект JavaScript Date. Оба с использованием $dateFromParts и «математический» подход по существу возвращают «округленную» дату, представляющую первый день месяца.

Обычно такие вещи, как «банковские выписки», фактически выпускаются в «определенный день месяца». Это действительно требует только расширения представленной логики для настройки, просто «переделывая» день возвращения.

Скажите за "15-й" день каждого месяца:

    "_id": {
      "$add": [
        { "$subtract": [
          { "$subtract": ["$payment_time", new Date(0)] },
          { "$mod": [
            { "$subtract": ["$payment_time", new Date(0)] },
            1000 * 60 * 60 * 24
          ]}
        ]},
        { "$multiply": [
          { "$subtract": [{ "$dayOfMonth": "$payment_time" }, 1] },
          -1000 * 60 * 60 * 24
        ]},
        1000 * 60 * 60 * 24 * (15-1),       // n-1 days adjusting
        new Date(0)
      ]
    },

Что является серьезной причиной, почему я предпочитаю "математический" подход, так как он намного более гибкий, чем другие формы. Вы можете сделать это с теми, которые полагаются на другие «операторы даты», но в конечном итоге они применяют «условия диапазона» в день месяца таким образом, что для реализации требуется гораздо больше логики. Однако добавление еще одного числа в массив! Что может быть проще, чем это?

Конечно, есть и такие вещи, как $dateFromString и $dateToString в качестве дополнительных способов сделать это с Modern MongoDB, однако приведение к «string» является дорогостоящей операцией. Чем больше операций, которые вы выполняете для преобразования чего-либо, по существу числового, в строковую форму для манипулирования, в конечном итоге имеет кумулятивный эффект, который равнозначно влияет на производительность. А в современном мире плата за вычислительные циклы и передачу данных равняется $$ money $$. И я cheapskate , так что мне нравится, чтобы вещи были эффективными.

Ежедневно

То же самое, что и раньше, с небольшим разбросом по получению "ежедневных" интервалов

MongoDB 3,6 $ dateFromParts

Model.aggregate([
  { "$match": {
    "payment_time": { "$gte": start_date, "$lt": end_date }
  }},
  { "$group": {
    "_id": {
      "$dateFromParts": {
        "year": { "$year": "$payment_time" },
        "month": { "$month": "$payment_time" },
        "day": { "$dayOfMonth": "$payment_time" }
      }
    },
    "balance": {
      "$sum": {
        "$cond": {
          "if": { "$eq": [ "$payment_type", "deposit" ] },
          "then": "$payment_amount",
          "else": { "$subtract": [ 0, "$payment_amount" ] }
        }
      }
    }
  }}
])

Использование Date Math

Model.aggregate([
  { "$match": {
    "payment_time": { "$gte": start_date, "$lt": end_date }
  }},
  { "$group": {
    "_id": {
      "$add": [
        { "$subtract": [
          { "$subtract": ["$payment_time", new Date(0)] },
          { "$mod": [
            { "$subtract": ["$payment_time", new Date(0)] },
            1000 * 60 * 60 * 24
          ]}
        ]},
        new Date(0)
      ]
    },
    "balance": {
      "$sum": {
        "$cond": {
          "if": { "$eq": [ "$payment_type", "deposit" ] },
          "then": "$payment_amount",
          "else": { "$subtract": [ 0, "$payment_amount" ] }
        }
      }
    }
  }}
])

Операторы с обычной датой

Model.aggregate([
  { "$match": {
    "payment_time": { "$gte": start_date, "$lt": end_date }
  }},
  { "$group": {
    "_id": {
      "year": { "$year": "$payment_time" },
      "month": { "$month": "$payment_time" },
      "day": { "$dayOfMonth": "$payment_time" }
    },
    "balance": {
      "$sum": {
        "$cond": {
          "if": { "$eq": [ "$payment_type", "deposit" ] },
          "then": "$payment_amount",
          "else": { "$subtract": [ 0, "$payment_amount" ] }
        }
      }
    }
  }}
])

Примечательно, что здесь другие подходы «добавили» оператор $dayOfMonth, а подход «Math» фактически удалил этот же оператор из рассмотрения. Причина этого в том, что в математических операциях трудно иметь дело с «месяцами» из-за основного факта, что каждый месяц меняется по количеству дней, которые у него есть. Поэтому то, что мы, по сути, делаем в «ежемесячном» округлении, учитывает текущий день, чтобы найти начало месяца.

Суть в том, что та же самая представленная «математика» фактически применима к ЛЮБОМУ временному интервалу, будь то дни или годы, часы или секунды. ЕДИНСТВЕННОЕ время, которое вам нужно изменить и откорректировать, - это накопление «месяца», в котором количество дней не совпадает.

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

Так что "манипулируйте" ими. Как и любая другая структура данных.


ПРИМЕЧАНИЕ Вопрос на самом деле не упоминает ничего, кроме «даты», для которой в поле должно быть что-либо накоплено. Если вы хотите что-то вроде "player_id", вы просто добавляете это к "_id" этапа "$group" как часть составного ключа. «Составной ключ» демонстрируется в каждом операторах простой даты в разделе выше.

...