Skip to content

Commit d16ee00

Browse files
committed
Self Join && Join ON not equal
#65, #66 Issues. The explicit self JOIN ON syntax is supported - however, the self join syntax where the JOIN ON is not explicitly stated is NOT supported at this time. Previously only JOIN ON equality was supported ("="), I have changed to add support for other conditions as well as calculated fields.
1 parent 25e8da7 commit d16ee00

File tree

9 files changed

+6423
-5803
lines changed

9 files changed

+6423
-5803
lines changed

coverage/tests.lcov

Lines changed: 5826 additions & 5545 deletions
Large diffs are not rendered by default.

dist/gssql.js

Lines changed: 218 additions & 126 deletions
Large diffs are not rendered by default.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@demmings/gssql",
3-
"version": "1.3.45",
3+
"version": "1.3.46",
44
"description": "Google Sheets QUERY function replacement using real SQL select syntax.",
55
"main": "testGsSql.js",
66
"files": ["./src", "src", "img", "dist"],

src/JoinTables.js

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
import { Table } from './Table.js';
55
import { BindData } from './Sql.js';
6-
import { DerivedTable, TableFields, TableField, CalculatedField, SqlServerFunctions } from './Views.js';
6+
import { DerivedTable, TableFields, TableField, CalculatedField, SqlServerFunctions, FieldComparisons } from './Views.js';
77
export { JoinTables, JoinTablesRecordIds };
88
// *** DEBUG END ***/
99

@@ -74,19 +74,20 @@ class JoinTables { // skipcq: JS-0128
7474
/** @property {DerivedTable} - result table after tables are joined */
7575
this.derivedTable = new DerivedTable();
7676

77-
ast.JOIN.forEach(joinTable => this.joinNextTable(joinTable, ast.FROM.table.toUpperCase()));
77+
ast.JOIN.forEach(joinTable => this.joinNextTable(joinTable, ast.FROM.table.toUpperCase(), ast.FROM.as));
7878
}
7979

8080
/**
8181
* Updates derived table with join to new table.
8282
* @param {Object} astJoin
8383
* @param {String} leftTableName
84+
* @param {String} leftAlias
8485
*/
85-
joinNextTable(astJoin, leftTableName) {
86+
joinNextTable(astJoin, leftTableName, leftAlias) {
8687
const recIds = this.joinCondition(astJoin, leftTableName);
87-
8888
const joinFieldsInfo = this.joinTableIDs.getJoinFieldsInfo();
89-
this.derivedTable = JoinTables.joinTables(joinFieldsInfo, astJoin, recIds);
89+
90+
this.derivedTable = JoinTables.joinTables(joinFieldsInfo, astJoin, recIds, leftAlias);
9091

9192
// Field locations have changed to the derived table, so update our
9293
// virtual field list with proper settings.
@@ -190,7 +191,7 @@ class JoinTables { // skipcq: JS-0128
190191
for (let i = 0; i < recIds[0].length; i++) {
191192
let temp = [];
192193

193-
recIds.forEach(rec => {temp = temp.concat(rec[i])});
194+
recIds.forEach(rec => { temp = temp.concat(rec[i]) });
194195

195196
if (typeof temp[0] !== 'undefined') {
196197
result[i] = Array.from(new Set(temp));
@@ -225,9 +226,10 @@ class JoinTables { // skipcq: JS-0128
225226
* @param {LeftRightJoinFields} leftRightFieldInfo - left table field of join
226227
* @param {Object} joinTable - AST that contains join type.
227228
* @param {MatchingJoinRecordIDs} recIds
229+
* @param {String} leftAlias
228230
* @returns {DerivedTable} - new derived table after join of left and right tables.
229231
*/
230-
static joinTables(leftRightFieldInfo, joinTable, recIds) {
232+
static joinTables(leftRightFieldInfo, joinTable, recIds, leftAlias) {
231233
let derivedTable = null;
232234
let rightDerivedTable = null;
233235

@@ -238,6 +240,7 @@ class JoinTables { // skipcq: JS-0128
238240
.setRightField(leftRightFieldInfo.rightSideInfo.fieldInfo)
239241
.setLeftRecords(recIds.leftJoinRecordIDs)
240242
.setIsOuterJoin(true)
243+
.setJoinTableAlias(leftAlias, joinTable.as)
241244
.createTable();
242245
break;
243246

@@ -247,6 +250,7 @@ class JoinTables { // skipcq: JS-0128
247250
.setRightField(leftRightFieldInfo.rightSideInfo.fieldInfo)
248251
.setLeftRecords(recIds.leftJoinRecordIDs)
249252
.setIsOuterJoin(false)
253+
.setJoinTableAlias(leftAlias, joinTable.as)
250254
.createTable();
251255
break;
252256

@@ -256,6 +260,7 @@ class JoinTables { // skipcq: JS-0128
256260
.setRightField(leftRightFieldInfo.leftSideInfo.fieldInfo)
257261
.setLeftRecords(recIds.leftJoinRecordIDs)
258262
.setIsOuterJoin(true)
263+
.setJoinTableAlias(leftAlias, joinTable.as)
259264
.createTable();
260265

261266
break;
@@ -266,13 +271,15 @@ class JoinTables { // skipcq: JS-0128
266271
.setRightField(leftRightFieldInfo.rightSideInfo.fieldInfo)
267272
.setLeftRecords(recIds.leftJoinRecordIDs)
268273
.setIsOuterJoin(true)
274+
.setJoinTableAlias(joinTable.as)
269275
.createTable();
270276

271277
rightDerivedTable = new DerivedTable()
272278
.setLeftField(leftRightFieldInfo.rightSideInfo.fieldInfo)
273279
.setRightField(leftRightFieldInfo.leftSideInfo.fieldInfo)
274280
.setLeftRecords(recIds.rightJoinRecordIDs)
275281
.setIsOuterJoin(true)
282+
.setJoinTableAlias(joinTable.as)
276283
.createTable();
277284

278285
derivedTable.tableInfo.concat(rightDerivedTable.tableInfo); // skipcq: JS-D008
@@ -413,6 +420,7 @@ class JoinTablesRecordIds {
413420
* @typedef {Object} LeftRightJoinFields
414421
* @property {JoinSideInfo} leftSideInfo
415422
* @property {JoinSideInfo} rightSideInfo
423+
* @property {String} operator
416424
*
417425
*/
418426

@@ -435,9 +443,11 @@ class JoinTablesRecordIds {
435443

436444
const left = typeof astJoin.cond === 'undefined' ? astJoin.left : astJoin.cond.left;
437445
const right = typeof astJoin.cond === 'undefined' ? astJoin.right : astJoin.cond.right;
446+
const operator = typeof astJoin.cond === 'undefined' ? astJoin.operator : astJoin.cond.operator;
438447

439448
leftFieldInfo = this.getTableInfoFromCalculatedField(left);
440449
rightFieldInfo = this.getTableInfoFromCalculatedField(right);
450+
const isSelfJoin = leftFieldInfo.originalTable === rightFieldInfo.originalTable;
441451

442452
/** @type {JoinSideInfo} */
443453
const leftSideInfo = {
@@ -451,14 +461,15 @@ class JoinTablesRecordIds {
451461
}
452462

453463
// joinTable.table is the RIGHT table, so switch if equal to condition left.
454-
if (typeof leftFieldInfo !== 'undefined' && this.rightTableName === leftFieldInfo.originalTable) {
464+
if (typeof leftFieldInfo !== 'undefined' && this.rightTableName === leftFieldInfo.originalTable && !isSelfJoin) {
455465
return {
456466
leftSideInfo: rightSideInfo,
457-
rightSideInfo: leftSideInfo
467+
rightSideInfo: leftSideInfo,
468+
operator: operator
458469
};
459470
}
460471

461-
return { leftSideInfo, rightSideInfo };
472+
return { leftSideInfo, rightSideInfo, operator };
462473
}
463474

464475
/**
@@ -568,7 +579,7 @@ class JoinTablesRecordIds {
568579
}
569580

570581
/**
571-
* Returns array of each matching record ID from right table for every record in left table.
582+
* Returns array of each CONDITIONAL matching record ID from right table for every record in left table.
572583
* If the right table entry could NOT be found, -1 is set for that record index.
573584
* @param {JoinSideInfo} leftField - left table field
574585
* @param {JoinSideInfo} rightField - right table field
@@ -581,23 +592,37 @@ class JoinTablesRecordIds {
581592
// First record is the column title.
582593
leftRecordsIDs.push([0]);
583594

595+
const conditionFunction = FieldComparisons.getComparisonFunction(this.joinFields.operator);
584596
const leftTableData = leftField.fieldInfo.tableInfo.tableData;
585-
586-
// Map the RIGHT JOIN key to record numbers.
587597
const keyFieldMap = this.createKeyFieldRecordMap(rightField);
588598

589599
for (let leftTableRecordNum = 1; leftTableRecordNum < leftTableData.length; leftTableRecordNum++) {
590600
const keyMasterJoinField = this.getJoinColumnData(leftField, leftTableRecordNum);
601+
let rightRecordIDs = [];
602+
603+
if (this.joinFields.operator === '=') {
604+
// "=" - special case.
605+
// Most common case AND far fewer comparisons - especially if right table is large.
606+
rightRecordIDs = keyFieldMap.has(keyMasterJoinField) ? keyFieldMap.get(keyMasterJoinField) : [];
607+
}
608+
else {
609+
// @ts-ignore
610+
for (const [key, data] of keyFieldMap) {
611+
if (conditionFunction(keyMasterJoinField, key)) {
612+
rightRecordIDs.unshift(...data);
613+
}
614+
}
615+
}
591616

592617
// For the current LEFT TABLE record, record the linking RIGHT TABLE records.
593-
if (!keyFieldMap.has(keyMasterJoinField)) {
618+
if (rightRecordIDs.length === 0) {
594619
if (type !== "inner") {
595620
leftRecordsIDs[leftTableRecordNum] = [-1];
596621
}
597622
}
598623
else if (type !== "outer") {
599624
// Excludes all match recordgs (is outer the right word for this?)
600-
leftRecordsIDs[leftTableRecordNum] = keyFieldMap.get(keyMasterJoinField);
625+
leftRecordsIDs[leftTableRecordNum] = rightRecordIDs;
601626
}
602627
}
603628

@@ -612,9 +637,9 @@ class JoinTablesRecordIds {
612637
*/
613638
getJoinColumnData(fieldInfo, recordNumber) {
614639
let keyMasterJoinField = null;
615-
const tableColumnNumber = fieldInfo.fieldInfo.tableColumn;
616640

617-
if (typeof tableColumnNumber !== 'undefined') {
641+
if (typeof fieldInfo.fieldInfo.getTableColumn === 'function') {
642+
const tableColumnNumber = fieldInfo.fieldInfo.getTableColumn(fieldInfo.fieldInfo.fieldName);
618643
keyMasterJoinField = fieldInfo.fieldInfo.tableInfo.tableData[recordNumber][tableColumnNumber];
619644
}
620645
else {
@@ -632,7 +657,7 @@ class JoinTablesRecordIds {
632657
createKeyFieldRecordMap(rightField) {
633658
let keyFieldMap = null;
634659

635-
if (typeof rightField.fieldInfo.tableColumn !== 'undefined') {
660+
if (typeof rightField.fieldInfo.getTableColumn === 'function') {
636661
keyFieldMap = rightField.fieldInfo.tableInfo.createKeyFieldRecordMap(rightField.fieldInfo.fieldName);
637662
}
638663
else {

0 commit comments

Comments
 (0)