google 工作表查询左联接 one-to-many
google sheets query left join one-to-many
我有 2 个表,我正在尝试使用 google 查询语言或任何可以输出结果集的公式来执行左连接。
表 1
表 2
结果集
我怎样才能做到这一点?
此致
在 B2 中使用:
=ARRAYFORMULA(IFNA(VLOOKUP(Table1!A2:A, {Table2!B:B, Table2!A:A}, 2, 0)))
好的,这里是一个 内部联接,开始于:
=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)))})
备注
这是一种特殊情况,其中第一个 table 仅包含键 (ID),并且您只需要键加上第二个 table 中 ID 匹配的行的另一列.添加更多由管道符号(或任何其他选择的字符)分隔的列会很简单,但这些必须是硬编码的:我不知道这种方法有什么方法可以自动包含来自两个列的所有列tables.
这与答案 here 形成对比,答案 here 会自动合并来自两个 table 的列,但不允许一对多关系。
我也看到很多解决方案使用VLOOKUP
、INDEX
、MATCH
等复杂的公式
我决定编写一个用户函数来组合 tables,或者如我所指,对数据库进行反规范化。我编写了函数 DENORMALIZE()
来支持 INNER
、LEFT
、RIGHT
和 FULL
连接。通过嵌套函数调用,理论上可以无限加入 tables。
DENORMALIZE(range1, range2, primaryKey, foreignKey, [joinType])
参数:
range1
,主要table作为命名范围,a1Notation或数组
range2
,相关的table作为命名范围,a1Notation或数组
primaryKey
,主要 table 的唯一标识符,列以“1”开头
foreignKey
,相关 table 中的键加入主 table,列以“1”开头
joinType
,联接类型,“内”、“左”、“右”、“全”,可选,默认为“内”,不区分大小写
Returns:结果为二维数组
结果集示例:
=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 sheet 中的数据和示例:
https://docs.google.com/spreadsheets/d/1vziuF8gQcsOxTLEtlcU2cgTAYL1eIaaMTAoIrAS7mnE/edit?usp=sharing
我有 2 个表,我正在尝试使用 google 查询语言或任何可以输出结果集的公式来执行左连接。
表 1
表 2
结果集
我怎样才能做到这一点?
此致
在 B2 中使用:
=ARRAYFORMULA(IFNA(VLOOKUP(Table1!A2:A, {Table2!B:B, Table2!A:A}, 2, 0)))
好的,这里是一个 内部联接,开始于:
=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)))})
备注
这是一种特殊情况,其中第一个 table 仅包含键 (ID),并且您只需要键加上第二个 table 中 ID 匹配的行的另一列.添加更多由管道符号(或任何其他选择的字符)分隔的列会很简单,但这些必须是硬编码的:我不知道这种方法有什么方法可以自动包含来自两个列的所有列tables.
这与答案 here 形成对比,答案 here 会自动合并来自两个 table 的列,但不允许一对多关系。
我也看到很多解决方案使用VLOOKUP
、INDEX
、MATCH
等复杂的公式
我决定编写一个用户函数来组合 tables,或者如我所指,对数据库进行反规范化。我编写了函数 DENORMALIZE()
来支持 INNER
、LEFT
、RIGHT
和 FULL
连接。通过嵌套函数调用,理论上可以无限加入 tables。
DENORMALIZE(range1, range2, primaryKey, foreignKey, [joinType])
参数:
range1
,主要table作为命名范围,a1Notation或数组range2
,相关的table作为命名范围,a1Notation或数组primaryKey
,主要 table 的唯一标识符,列以“1”开头foreignKey
,相关 table 中的键加入主 table,列以“1”开头joinType
,联接类型,“内”、“左”、“右”、“全”,可选,默认为“内”,不区分大小写
Returns:结果为二维数组
结果集示例:
=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 sheet 中的数据和示例:
https://docs.google.com/spreadsheets/d/1vziuF8gQcsOxTLEtlcU2cgTAYL1eIaaMTAoIrAS7mnE/edit?usp=sharing