【问题标题】:google sheets query left join one-to-many谷歌表格查询左连接一对多
【发布时间】:2021-03-29 12:13:58
【问题描述】:

我有 2 个表,我正在尝试使用 google 查询语言或任何可以输出结果集的公式执行左连接。

表1

表2

结果集

我怎样才能做到这一点?

问候

【问题讨论】:

  • 我冒昧地编辑了问题的标题以标记它要求两个表之间的一对多关系(因为 ID=3 在第二个中出现了两次表,你想要两行)。

标签: google-sheets google-query-language


【解决方案1】:

我还看到了许多具有复杂公式的解决方案,使用 VLOOKUPINDEXMATCH 等。

我决定编写一个用户函数来组合表,或者按照我的说法,对数据库进行非规范化。我编写了函数DENORMALIZE() 来支持INNERLEFTRIGHTFULL 加入。通过嵌套函数调用,理论上可以加入无限的表。

DENORMALIZE(range1, range2, primaryKey, foreignKey, [joinType])

参数:

  • range1,主表为命名范围、a1Notation 或数组
  • range2,相关表为命名范围、a1Notation 或数组
  • primaryKey,主表唯一标识,列以“1”开头
  • foreignKey,关联表中加入主表的key,列以“1”开头
  • joinType,连接类型,“Inner”、“Left”、“Right”、“Full”,可选,默认为“Inner”,不区分大小写

返回:二维数组的结果

结果集示例:

=QUERY(denormalize("Employees","Orders",1,3), "SELECT * WHERE Col2 = 'Davolio' AND Col8=2", FALSE)
EmpID LastName FirstName OrderID CustomerID EmpID OrderDate ShipperID
1 Davolio Nancy 10285 63 1 8/20/1996 2
1 Davolio Nancy 10292 81 1 8/28/1996 2
1 Davolio Nancy 10304 80 1 9/12/1996 2

其他例子:

=denormalize("Employees","Orders",1,3)
=denormalize("Employees","Orders",1,3,"full")
=QUERY(denormalize("Employees","Orders",1,3,"left"), "SELECT * ", FALSE)
=QUERY(denormalize("Employees","Orders",1,3), "SELECT * WHERE Col2 = 'Davolio'", FALSE)
=QUERY(denormalize("Employees","Orders",1,3), "SELECT * WHERE Col2 = 'Davolio' AND Col8=2", FALSE)
=denormalize("Orders","OrderDetails",1,2)
// multiple joins 
=denormalize("Employees",denormalize("Orders","OrderDetails",1,2),1,3)
=QUERY(denormalize("Employees",denormalize("Orders","OrderDetails",1,2),1,3), "SELECT *", FALSE)
=denormalize(denormalize("Employees","Orders",1,3),"OrderDetails",1,2)
=QUERY(denormalize("Employees",denormalize("Orders","OrderDetails",1,2),1,3), "SELECT *", FALSE)
=QUERY(denormalize(denormalize("Employees","Orders",1,3),"OrderDetails",4,2), "SELECT *", FALSE)

function denormalize(range1, range2, primaryKey, foreignKey, joinType) {
  var i = 0;
  var j = 0;
  var index = -1;
  var lFound = false;
  var aDenorm = [];
  var hashtable = [];
  var aRange1 = "";
  var aRange2 = "";
  joinType = DefaultTo(joinType, "INNER").toUpperCase();
  // the 6 lines below are used for debugging
  //range1 = "Employees";
  //range1 = "Employees!A2:C12";
  //range2 = "Orders";
  //primaryKey = 1;
  //foreignKey = 3;
  //joinType = "LEFT";
  // Sheets starts numbering columns starting with "1", arrays are zero-based
  primaryKey -= 1;
  foreignKey -= 1;
  // check if range is not an array
  if (typeof range1 !== 'object') {
    // Determine if range is a1Notation and load data into an array
    if (range1.indexOf(":") !== -1) {
      aRange1 = ss.getRange(range1).getValues();
    } else {
      aRange1 = ss.getRangeByName(range1).getValues();
    } 
  } else {
    aRange1 = range1;
  }
  
  if (typeof range2 !== 'object') {
    if (range2.indexOf(":") !== -1) {
      aRange2 = ss.getRange(range2).getValues();
    } else {
      aRange2 = ss.getRangeByName(range2).getValues();
    }
  } else {
    aRange2 = range2;
  }
  
  // make similar structured temp arrays with NULL elements
  var tArray1 = MakeArray(aRange1[0].length);
  var tArray2 = MakeArray(aRange2[0].length);
  var lenRange1 = aRange1.length;
  var lenRange2 = aRange2.length;
  hashtable = getHT(aRange1, lenRange1, primaryKey);
  for(i = 0; i < lenRange2; i++)  {
    index = hashtable.indexOf(aRange2[i][foreignKey]);
    if (index !== -1) {
      aDenorm.push(aRange1[index].concat(aRange2[i]));
    }
  }
  // add left and full no matches
  if (joinType == "LEFT" || joinType == "FULL") {
    for(i = 0; i < lenRange1; i++)  {
      //index = aDenorm.indexOf(aRange1[i][primaryKey]);
      index = aScan(aDenorm, aRange1[i][primaryKey], primaryKey)
      if (index == -1) {
        aDenorm.push(aRange1[i].concat(tArray2));
      }
    }
  }
  // add right and full no matches
  if (joinType == "RIGHT" || joinType == "FULL") {
    for(i = 0; i < lenRange2; i++)  {
      index = aScan(aDenorm, aRange2[i][foreignKey], primaryKey)
      if (index == -1) {
        aDenorm.push(tArray1.concat(aRange2[i]));
      }
    }
  }
    return aDenorm;
}

function getHT(aRange, lenRange, key){
var aHashtable = [];
var i = 0;
for (i=0; i < lenRange; i++ ) {
  //aHashtable.push([aRange[i][key], i]);
  aHashtable.push(aRange[i][key]);
  }
return aHashtable;
}

function MakeArray(length) {
  var i = 0;
  var retArray = [];
  for (i=0; i < length; i++) {
    retArray.push("");
  }
  return retArray;
}

function DefaultTo(valueToCheck, valueToDefault) {
return typeof valueToCheck === "undefined" ? valueToDefault : valueToCheck;
}

// Search a multi-dimensional array for a value
function aScan(aValues, searchStr, searchCol) {
var retval = -1;
var i = 0;
var aLen   = aValues.length;
for (i = 0; i < aLen; i++) {
    if (aValues[i][searchCol] == searchStr) {
        retval = i;
        break;
    }
}
return retval;
}

您可以在此处复制包含数据和示例的 google 表格:

https://docs.google.com/spreadsheets/d/1vziuF8gQcsOxTLEtlcU2cgTAYL1eIaaMTAoIrAS7mnE/edit?usp=sharing

【讨论】:

    【解决方案2】:

    好的,这是一个 inner join 开始:

    =ArrayFormula(query(iferror(split(flatten(if(transpose(filter(Table2!B2:B,Table2!B2:B<>""))=filter(Table1!A2:A,Table1!A2:A<>""),filter(Table1!A2:A,Table1!A2:A<>"")&"|"&transpose(filter(Table2!A2:A,Table2!A2:A<>"")),)),"|")),"select Col1,Col2 where Col1 is not null label Col1 '',Col2 ''"))
    

    它构建一个二维数组并填充两组数据匹配的位置,然后将其展平为一维数组并将其拆分回两列。

    我认为您只需添加不匹配的行即可获得左外连接:

    =ArrayFormula({query(iferror(split(flatten(if(transpose(filter(Table2!B2:B,Table2!B2:B<>""))=filter(Table1!A2:A,Table1!A2:A<>""),
    filter(Table1!A2:A,Table1!A2:A<>"")&"|"&transpose(filter(Table2!A2:A,Table2!A2:A<>"")),)),"|")),"select Col1,Col2 where Col1 is not null label Col1 '',Col2 ''");
    filter(Table1!A2:B,isna(vlookup(Table1!A2:A,Table2!B2:B,1,false)))})
    

    注意

    这是一种特殊情况,其中第一个表只包含键 (ID),而您只需要键加上第二个表中的另一列用于 ID 匹配的行。添加更多由管道符号(或任何其他选择的字符)分隔的列会很简单,但这些必须是硬编码的:我不知道这种方法有什么方法可以自动包含两者中的所有列表格。

    这与答案 here 形成对比,后者会自动组合两个表中的列,但不允许一对多关系。

    【讨论】:

    • 嗨,汤姆,很棒的解决方案。我自己拿不到。非常感谢!
    • 谢谢!我将在之前的解决方案中添加一个注释,以表明这是一种可能的一对多关系stackoverflow.com/questions/14796620/…
    【解决方案3】:

    在 B2 中使用:

    =ARRAYFORMULA(IFNA(VLOOKUP(Table1!A2:A, {Table2!B:B, Table2!A:A}, 2, 0)))
    

    【讨论】:

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