代码之家  ›  专栏  ›  技术社区  ›  pengz

谷歌数据存储结合(联合)多组实体结果以实现或条件

  •  2
  • pengz  · 技术社区  · 8 年前

    我正在与NodeJS一起使用数据存储数据库在谷歌应用程序引擎上工作。

    由于以下事实: Datastore does not have support the OR operator ,我需要运行多个查询并合并结果。

    我计划运行多个查询,然后将结果合并到单个实体对象数组中。我已经有一个查询在工作了。

    问题:组合数据存储返回的两组(或更多)实体(包括重复数据消除)的合理有效方法是什么?我相信从集合论的角度来看,这将是一个“联合”操作。

    以下是基本查询大纲,将使用一些不同的过滤器多次运行,以实现所需的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

    2 回复  |  直到 8 年前
        1
  •  3
  •   Dan Cornilescu    8 年前

    IMHO最有效的方法是在第一阶段使用仅密钥查询,然后将获得的密钥组合到单个列表中(包括重复数据消除),然后仅通过密钥查找获得实体。从…起 Projection queries :

    仅关键字查询

    结果实体的键,而不是实体本身,位于 与检索整个实体相比,延迟和成本更低。

    通常,先进行仅键查询,然后执行 从结果中获取实体子集,而不是执行 通用查询,它可能获取比实际需要更多的实体。

    下面介绍了如何创建仅限键的查询:

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

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

    • 您无法正确地消除重复,因为您无法区分具有相同值的不同实体和出现在多个查询结果中的相同实体之间的差异
    • 按属性值比较实体可能很复杂,而且与仅比较实体键相比,计算速度肯定较慢/成本更高
    • 如果您不能在一个请求中处理所有结果,那么在没有实际使用它们的情况下读取这些结果将产生不必要的数据存储成本
    • 当只处理实体键时,在多个请求中拆分实体处理(例如通过任务队列)要简单得多

    还有一些缺点:

    • 它可能会慢一点,因为您需要两次访问数据存储:一次访问键,一次访问实际实体
    • 您不能利用通过非仅关键帧投影查询仅获取所需的属性
        2
  •  1
  •   pengz    8 年前

    以下是我根据公认答案中提供的建议创建的解决方案。

    /*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;
    }