NodeJS Google数据存储库合并(联合)多组实体结果以实现OR条件

jxct1oxe  于 2023-01-01  发布在  Node.js
关注(0)|答案(3)|浏览(132)

我正在与谷歌应用引擎上的NodeJS与数据存储数据库。
由于Datastore不支持OR运算符,我需要运行多个查询并合并结果。
我计划运行多个查询,然后将结果组合到单个实体对象数组中。我已经有一个查询在运行。
问:合并数据存储区返回的两个(或多个)实体集(包括重复数据消除)的合理有效方法是什么?我认为这是集合论中的"联合"操作。
下面是基本查询大纲,它将使用一些不同的筛选器运行多次,以实现所需的OR条件。

//Set requester username
  const requester = req.user.userName;
  //Create datastore query on Transfer Request kind table
  const task_history = datastore.createQuery('Task');
  //Set query conditions
  task_history.filter('requester', requester);
  //Run datastore query
  datastore.runQuery(task_history, function(err, entities) {
    if(err) {
      console.log('Task History JSON unable to return data results. Error message: ', err);
      return;
      //If query works and returns any entities
    } else if (entities[0]) {
      //Else if query works but does not return any entities return empty JSON response
      res.json(entities); //HOW TO COMBINE (UNION) MULTIPLE SETS OF ENTITIES EFFICIENTLY?
      return;
    }
  });

下面是我的原始帖子:Google Datastore filter with OR condition

9wbgstp7

9wbgstp71#

恕我直言,最有效的方法是在第一阶段使用Keys-only查询,然后将获得的键组合成一个列表(包括重复数据删除),然后通过简单的键查找获得实体。
仅键查询
仅键查询(一种投影查询)只返回结果实体的键,而不是实体本身,与检索整个实体相比,延迟和成本更低。
通常,首先执行仅关键字查询,然后从结果中获取实体子集比执行常规查询更经济,因为常规查询可能获取比实际需要更多的实体。
以下是创建仅限键的查询的方法:

const query = datastore.createQuery()
  .select('__key__')
  .limit(1);

此方法解决了在尝试直接合并通过常规非仅键查询获得的实体列表时可能遇到的几个问题:

  • 您无法正确执行重复数据消除,因为您无法区分具有相同值的不同实体与多重查询结果中显示的同一实体
  • 通过属性值比较实体可能比较棘手,并且肯定比仅比较实体键更慢/计算成本更高
  • 如果您不能在单个请求中处理所有结果,则会因为阅读结果而实际上没有使用它们而产生不必要的数据存储成本
  • 当只处理实体键时,将实体的处理拆分为多个请求(例如,通过任务队列)要简单得多

也有一些缺点:

  • 可能会慢一点,因为您要访问数据存储区两次:一次用于键,一次用于获取实际实体
  • 你不能通过非键Map查询来获取你所需要的属性
z8dt9xmd

z8dt9xmd2#

下面是我根据已接受答案中提供的建议创建的解决方案。

/*History JSON*/
module.exports.treqHistoryJSON = function(req, res) {
  if (!req.user) {
    req.user = {};
    res.json();
    return;
  }

  //Set Requester username
  const loggedin_username = req.user.userName;

  //Get records matching Requester OR Dataowner
  //Google Datastore OR Conditions are not supported
  //Workaround separate parallel queries get records matching Requester and Dataowner then combine results
  async.parallel({
    //Get entity keys matching Requester
    requesterKeys: function(callback) {
      getKeysOnly('TransferRequest', 'requester_username', loggedin_username, (treqs_by_requester) => {
        //Callback pass in response as parameter
        callback(null, treqs_by_requester)
      });
    },
    //Get entity keys matching Dataowner
    dataownerKeys: function(callback) {
      getKeysOnly('TransferRequest', 'dataowner_username', loggedin_username, (treqs_by_dataowner) => {
        callback(null, treqs_by_dataowner)
      });
    }
  }, function(err, getEntities) {
    if (err) {
      console.log('Transfer Request History JSON unable to get entity keys Transfer Request. Error message: ', err);
      return;
    } else {
      //Combine two arrays of entity keys into a single de-duplicated array of entity keys
      let entity_keys_union = unionEntityKeys(getEntities.requesterKeys, getEntities.dataownerKeys);
      //Get key values from entity key 'symbol' object type
      let entity_keys_only = entity_keys_union.map((ent) => {
        return ent[datastore.KEY];
      });
      //Pass in array of entity keys to get full entities
      datastore.get(entity_keys_only, function(err, entities) {
        if(err) {
          console.log('Transfer Request History JSON unable to lookup multiple entities by key for Transfer Request. Error message: ', err);
          return;
          //If query works and returns any entities
        } else {
          processEntitiesToDisplay(res, entities);
        }
      });
    }
  });

};

/*
 * Get keys-only entities by kind and property
 * @kind string name of kind
 * @property_type string property filtering by in query
 * @filter_value string of filter value to match in query
 * getEntitiesCallback callback to collect results
 */
function getKeysOnly(kind, property_type, filter_value, getEntitiesCallback) {
  //Create datastore query
  const keys_query = datastore.createQuery(kind);
  //Set query conditions
  keys_query.filter(property_type, filter_value);
  //Select KEY only
  keys_query.select('__key__');
  datastore.runQuery(keys_query, function(err, entities) {
    if(err) {
      console.log('Get Keys Only query unable to return data results. Error message: ', err);
      return;
    } else {
      getEntitiesCallback(entities);
    }
  });
}

/*
 * Union two arrays of entity keys de-duplicate based on ID value
 * @arr1 array of entity keys
 * @arr2 array of entity keys
 */
function unionEntityKeys(arr1, arr2) {
  //Create new array
  let arr3 = [];
  //For each element in array 1
  for(let i in arr1) {
    let shared = false;
      for (let j in arr2)
        //If ID in array 1 is same as array 2 then this is a duplicate
        if (arr2[j][datastore.KEY]['id'] == arr1[i][datastore.KEY]['id']) {
          shared = true;
          break;
        }
      //If IDs are not the same add element to new array
      if(!shared) {
        arr3.push(arr1[i])
      }
    }
  //Concat array 2 and new array 3
  arr3 = arr3.concat(arr2);
  return arr3;
}
lf5gs5x2

lf5gs5x23#

我只是想写信给那些偶然发现这个的人...
There is a workaround for some cases of not having the OR operator if you can restructure your data a bit, using Array properties: https://cloud.google.com/datastore/docs/concepts/entities#array_properties
来自文档:
例如,在使用等式过滤器执行查询时,数组属性可能会很有用:如果实体的任何属性值与过滤器中指定的值匹配,则实体满足查询
因此,如果您需要查询具有多个潜在值之一的所有实体,将每个实体的所有可能性放入Array属性,然后为查询索引它,应该会产生您想要的结果。(但是,您需要维护该附加属性,或者如果该Array实现可以满足您的所有需要,则用该Array实现替换现有属性。)

相关问题