【问题标题】:Analog to SQL 'JOIN' for Javascript objects?类似于 Javascript 对象的 SQL 'JOIN'?
【发布时间】:2014-04-30 20:55:34
【问题描述】:

对于表示为 Javascript 对象数组的表,SQL 'JOIN' 的实用模拟是什么? Javascript Array.join 和 D3.js 'd3.merge` 不是同一个概念。

例如SELECT * FROM authors LEFT JOIN books ON authors.id = books.author_id?

第一个表:

var authors = 
[  { id: 1, name: 'adam'},
   { id: 2, name: 'bob'},
   { id: 3, name: 'charlie'}, ...
]

第二张桌子:

var books = 
[  { author_id: 1, title: 'Coloring for beginners'}, 
   { author_id: 1, title: 'Advanced coloring'}, 
   { author_id: 2, title: '50 Hikes in New England'},
   { author_id: 2, title: '50 Hikes in Illinois'},
   { author_id: 3, title: 'String Theory for Dummies'}, ...
]

表格是使用 D3.js d3.csv() 从 CSV 加载的,所以已经有 D3.js,对其他库开放,但如果不太远,通常更喜欢直接编码。

我看到 Native way to merge objects in Javascript 使用了 RethinkDB,这似乎有点过头了,但这就是想法。

【问题讨论】:

    标签: javascript arrays join


    【解决方案1】:

    基本上是这样的:

    // first, build an easier lookup of author data:
    var authormap = {};
    authors.forEach(function(author) {authormap[author.id] = author;});
    
    // now do the "join":
    books.forEach(function(book) {
        book.author = authormap[book.author_id];
    });
    
    // now you can access:
    alert(books[0].author.name);
    

    【讨论】:

    • author 作为book 中的对象塞进去很棒,因为它的思维方式太像SQL,看不到它的优雅之处。
    【解决方案2】:

    您可以使用Alasql JavaScript SQL 库来做到这一点:

    var res = alasql('SELECT * FROM ? authors \
           LEFT JOIN ? books ON authors.id = books.author_id',[authors, books]);
    

    在 jsFiddle 中尝试 this example 处理您的数据。

    您还可以将 CSV 数据直接加载到 SQL 表达式中:

    alasql('SELECT * FROM CSV("authors.csv", {headers:true}) authors \
                LEFT JOIN CSV("books.csv", {headers:true}) books \
                ON authors.id = books.author_id',[], function(res) {
          console.log(res);
     });
    

    【讨论】:

    • alasql 太棒了
    【解决方案3】:

    我也在寻找类似的东西,并使用一些函数式编程解决了它。我冒昧地将几个对象添加到您的初始数组中以处理“NULL”情况。

    var books = [
        {author_id: 1, title: 'Coloring for beginners'},
        {author_id: 1, title: 'Advanced coloring'},
        {author_id: 2, title: '50 Hikes in New England'},
        {author_id: 2, title: '50 Hikes in Illinois'},
        {author_id: 3, title: 'String Theory for Dummies'},
        {author_id: 5, title: 'Map-Reduce for Fun and Profit'}    
    ];
    var authors = [
        {id: 1, name: 'adam'},
        {id: 2, name: 'bob'},
        {id: 3, name: 'charlie'},
        {id: 4, name: 'diane'}
    ];
    

    所以现在你有一本没有作者的书和一个没有书的作者。我的解决方案如下所示:

    var joined = books.map(function(e) {
        return Object.assign({}, e, authors.reduce(function(acc, val) {
            if (val.id == e.author_id) {
                return val
            } else {
                return acc
            }
        }, {}))
    });
    

    map 方法使用e 遍历books 的每个元素,并返回一个数组,其元素是e 的合并对象,其对应的对象在authors 数组中。 Object.assign({},a,b) 照顾 merge 而不修改原始对象。

    books 中每个e 的对应对象是通过对authors 数组应用reduce 方法找到的。从空对象{} 的初始值开始(这是reduce 的第二个参数——它也可能是一个空作者,例如{id:'', name ''}),reduce 方法使用@987654336 遍历authors 的元素@ 并返回以 acc 结尾的对象。当在书籍 author_id 和作者的 id 之间找到匹配时,整个匹配的作者对象最终会出现在 acc 中,并最终由 authors.reduce(...) 返回。

    n.b. - 使用reduce不是那么高效,因为有no way to break out of reduce loop一旦找到匹配,它会继续到数组的末尾

    【讨论】:

      【解决方案4】:

      这是受到@Niet 回答的启发。

      我遇到了重复数据的问题,因此我在将查找表中的记录与当前记录加入之前添加了克隆记录的步骤。

      var authors = [{
          id: 1,
          name: 'adam'
      }, {
          id: 2,
          name: 'bob'
      }, {
          id: 3,
          name: 'charlie'
      }];
      
      var books = [{
          author_id: 1,
          title: 'Coloring for beginners'
      }, {
          author_id: 1,
          title: 'Advanced coloring'
      }, {
          author_id: 2,
          title: '50 Hikes in New England'
      }, {
          author_id: 2,
          title: '50 Hikes in Illinois'
      }, {
          author_id: 3,
          title: 'String Theory for Dummies'
      }];
      
      function joinTables(left, right, leftKey, rightKey) {
      
          rightKey = rightKey || leftKey;
      
          var lookupTable = {};
          var resultTable = [];
          var forEachLeftRecord = function (currentRecord) {
              lookupTable[currentRecord[leftKey]] = currentRecord;
          };
      
          var forEachRightRecord = function (currentRecord) {
              var joinedRecord = _.clone(lookupTable[currentRecord[rightKey]]); // using lodash clone
              _.extend(joinedRecord, currentRecord); // using lodash extend
              resultTable.push(joinedRecord);
          };
      
          left.forEach(forEachLeftRecord);
          right.forEach(forEachRightRecord);
      
          return resultTable;
      }
      var joinResult = joinTables(authors, books, 'id', 'author_id');
      console.log(joinResult);
      

      结果是

      [
          {
              "id": 1,
              "name": "adam",
              "author_id": 1,
              "title": "Coloring for beginners"
          },
          {
              "id": 1,
              "name": "adam",
              "author_id": 1,
              "title": "Advanced coloring"
          },
          {
              "id": 2,
              "name": "bob",
              "author_id": 2,
              "title": "50 Hikes in New England"
          },
          {
              "id": 2,
              "name": "bob",
              "author_id": 2,
              "title": "50 Hikes in Illinois"
          },
          {
              "id": 3,
              "name": "charlie",
              "author_id": 3,
              "title": "String Theory for Dummies"
          }
      ] 
      

      【讨论】:

        【解决方案5】:

        在这种情况下,我需要在两个数据集之间的一致键上连接两个数组,但我对此略有不同。随着我的数据集增长,JSON 对象变得过于笨拙,因此连接两个数组要容易得多:

         var data = new Array(["auth1","newbook1","pubdate1"],["auth2","newbook2","pubdate2"]);
         var currData = new Array(["auth1","newbook3","pubdate3"],["auth2","newbook3","pubdate4"]);
         var currDataMap = currData.map(function(a){return a[0];});
         var newdata = new Array();
         for(i=0;i<data.length;i++){
           if(currDataMap.indexOf(data[i][0])>-1){
             newdata[i] = data[i].concat(currData[currDataMap.indexOf(data[i][0])].slice(1));
          }
        }
        

        输出:

        [
           [auth1, newbook1, pubdate1, newbook3, pubdate3], 
           [auth2, newbook2, pubdate2, newbook3, pubdate4]
        ]
        

        就我而言,我还需要删除没有新数据的行,因此您可能希望排除条件。

        【讨论】:

          【解决方案6】:

          不是最优雅的代码,但我认为如果需要的话,遵循/破解它非常简单。允许 INNER、LEFT 和 RIGHT 连接。

          您只需将函数复制并粘贴到代码中即可获得正确的输出。示例在底部

          function remove_item_from_list(list_, remove_item, all = true) {
            /*
            Removes all occurrences of remove_item from list_
            */
              for (var i = list_.length; i--;) {
                  if (list_[i] === remove_item) {
                      list_.splice(i, 1);
                  }
              }
              return list_
          }
          
          function add_null_keys(dict_, keys_to_add){
            /*
            This function will add the keys in the keys_to_add list to the dict_ object with the vall null
          
            ex: 
            dict_ = {'key_1': 1, 'key_2': 2}
            keys_to_add = ['a', 'b', 'c']
          
            output:
            {'key_1': 1, 'key_2': 2, 'a': NULL, 'b': NULL', 'c':'NULL'}
            */
          
            //get the current keys in the dict
            var current_keys = Object.keys(dict_)
            for (index in keys_to_add){
              key = keys_to_add[index]
              //if the dict doesnt have the key add the key as null
              if(current_keys.includes(key) === false){
                dict_[key] = null
              }
            }
            return dict_
          }
          
          function merge2(dict_1, dict_2, on, how_join){
            /*
            This function is where the actual comparison happens to see if two dictionaries share the same key
          
            We loop through the on_list to see if the various keys that we are joining on between the two dicts match.
          
            If all the keys match we combine the dictionaries.
          
            If the keys do not match and it is an inner join, an undefined object gets returned
            If the keys do not match and it is NOT an inner join, we add all the key values of the second dictionary as null to the first dictionary and return those
            */
          
            var join_dicts = true
          
            //loop through the join on key
            for (index in on){
              join_key = on[index]
          
              //if one of the join keys dont match, then we arent joining the dictionaries
              if (dict_1[join_key] != dict_2[join_key]){
                join_dicts = false
                break
              }
            }
          
            //check to see if we are still joining the dictionaries
            if (join_dicts === true){
              return Object.assign({}, dict_1, dict_2);
            }
          
            else{
              if (how_join !== 'inner'){
                //need to add null keys to dict_1, which is acting as the main side of the join
                var temp = add_null_keys(dict_1, Object.keys(dict_2))
                return temp
              }
            }
          }
          
          function dict_merge_loop_though(left_dict, right_dict, on, how_join){
            /*
            This function loops through the left_dict and compares everything in it to the right_dict
          
            it determines if a join happens, what the join is and returns the information
          
            Figuring out the left/right joins were difficult. I had to add a base_level dict to determine if there was no join
            or if there was a join...its complicated to explain
            */
          
            var master_list = []
            var index = 0
          
            //need to loop through what we are joining on 
            while(index < left_dict.length){
              //grab the left dictionary
              left_dict_ = left_dict[index]
              var index2 = 0
          
              //necessary for left/right join
              var remove_val = add_null_keys(left_dict_, Object.keys(right_dict[index2]))
              var temp_list = [remove_val]
          
              while (index2 < right_dict.length){
                //get the right dictionary so we can compete each dictionary to each other
                right_dict_ = right_dict[index2]
          
                //inner join the two dicts
                if (how_join === 'inner'){
                  var temp_val = merge2(left_dict_, right_dict_, on, how_join)
          
                  //if whats returned is a dict, add it to the master list
                  if (temp_val != undefined){
                    master_list.push(temp_val)
                  }
                }
          
                //means we are right/left joining
                else{
          
                  //left join the two dicts
                  if (how_join === 'left'){
                    var temp_val = merge2(left_dict_, right_dict_, on, how_join)
                  }
          
                  //right join the two dicts
                  else if (how_join === 'right'){
                    var temp_val = merge2(right_dict_, left_dict_, on, how_join)
                  }
                  temp_list.push(temp_val)
                }
          
                //increment this guy
                index2++
              }
          
          
              //Logic for left/right joins to for what to add to master list
              if (how_join !== 'inner'){
                // remove the remove val from the list. All that remains is what should be added
                //to the master return list. If the length of the list is 0 it means that there was no
                //join and that we should add the remove val (with the extra keys being null) to the master
                //return list
                temp_list = remove_item_from_list(temp_list, remove_val)
          
                if (temp_list.length == 0){
                  master_list.push(remove_val)
                }
                else{
                  master_list = master_list.concat(temp_list); 
                }
              }
              //increment to move onto the next thing
              index++
          
            }
          
            return master_list
          }
          
          function merge(left_dict, right_dict, on = [], how = 'inner'){
            /*
            This function will merge two dictionaries together
            You provide a left dictionary, a right dictionary, a list of what key to join on and 
            what type of join you would like to do (right, left, inner)
          
            a list of the merged dictionaries is returned
            */
          
            //get the join type and initialize the master list of dictionaries that will be returned
            var how_join = how.toLowerCase()
            var master_list = []
          
            //inner, right, and left joins are actually pretty similar in theory. The only major difference between
            //left and right joins is the order that the data is processed. So the only difference is we call the
            //merging function with the dictionaries in a different order
            if (how_join === 'inner'){
              master_list = dict_merge_loop_though(left_dict, right_dict, on, how_join)
            }
          
            else if (how_join === 'left'){
              master_list = dict_merge_loop_though(left_dict, right_dict, on, how_join)
            }
          
            else if (how_join === 'right'){
              master_list = dict_merge_loop_though(right_dict, left_dict, on, how_join)
            }
          
            else{
              console.log('---- ERROR ----')
              console.log('The "how" merge type is not correct. Please make sure it is either "inner", "left" or "right"')
              console.log('---- ERROR ----')
            }
          
            return master_list
          } 
          
          /*
          -------------------- EXAMPLE --------------------
          var arr1 = [
              {'id': 1, 'test': 2, 'text':"hello", 'oid': 2},
              {'id': 1, 'test': 1, 'text':"juhu", 'oid': 3},
              {'id': 3, 'test': 3, 'text':"wohoo", 'oid': 4},
              {'id': 4, 'test': 4, 'text':"yeehaw", 'oid': 1}
          ];
          
          var arr2 = [
              {'id': 1,'test': 2, 'name':"yoda"},
              {'id': 1,'test': 1, 'name':"herbert"},
              {'id': 3, 'name':"john"},
              {'id': 4, 'name':"walter"},
              {'id': 5, 'name':"clint"}
          ];
          
          var test = merge(arr1, arr2, on = ['id', 'test'], how = 'left')
          for (index in test){
            dict_ = test[index]
            console.log(dict_)
          }
          
          */
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-10-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多