@@ -30,105 +30,220 @@ yy.Select.prototype.compilePivot = function (query) {
3030 } ) ;
3131 }
3232
33- // Function for PIVOT post production
33+ // Function for PIVOT post production (returned by compilePivot)
3434 return function ( ) {
35- var query = this ;
36- var cols = query . columns
37- . filter ( function ( col ) {
38- return col . columnid != columnid && col . columnid != exprcolid ;
39- } )
40- . map ( function ( col ) {
41- return col . columnid ;
42- } ) ;
35+ var query = this ; // Keep reference to the query object
36+
37+ // Exit if no data to pivot
38+ if ( ! query . data || query . data . length === 0 ) {
39+ query . columns = [ ] ; // Reset columns if no data
40+ return ;
41+ }
42+
43+ // --- Determine grouping columns robustly ---
44+ var firstRowKeys = Object . keys ( query . data [ 0 ] ) ;
45+ var cols = firstRowKeys . filter ( function ( key ) {
46+ return key !== columnid && key !== exprcolid ;
47+ } ) ;
48+ // -----------------------------------------
49+
50+ var newcols = [ ] ; // Stores the new column headers (e.g., 'MON', 'TUE')
51+ var gnewcols = { } ; // Tracks unique new columns encountered
52+ var gr = { } ; // Stores the grouped & pivoted results (key: gx, value: g)
53+ var ga = { } ; // Stores counts for AVG calculation (key: gx, value: {day: count})
54+ var data = [ ] ; // The final pivoted data array
4355
44- var newcols = [ ] ;
45- var gnewcols = { } ;
46- var gr = { } ;
47- var ga = { } ;
48- var data = [ ] ;
4956 query . data . forEach ( function ( d ) {
50- if ( ! inlist || inlist . indexOf ( d [ columnid ] ) > - 1 ) {
51- var gx = cols
52- . map ( function ( colid ) {
53- return d [ colid ] ;
54- } )
55- . join ( '`' ) ;
56- var g = gr [ gx ] ;
57- if ( ! g ) {
58- g = { } ;
59- gr [ gx ] = g ;
60- data . push ( g ) ;
61- cols . forEach ( function ( colid ) {
62- g [ colid ] = d [ colid ] ;
63- } ) ;
64- }
57+ // Skip row if the pivot column value is not in the IN list (if provided)
58+ if ( inlist && inlist . indexOf ( d [ columnid ] ) === - 1 ) {
59+ return ;
60+ }
6561
66- if ( ! ga [ gx ] ) {
67- ga [ gx ] = { } ;
68- }
62+ var gx = cols
63+ . map ( function ( colid ) {
64+ return d [ colid ] === undefined || d [ colid ] === null ? '' : d [ colid ] ;
65+ } )
66+ . join ( '`' ) ;
6967
70- if ( ga [ gx ] [ d [ columnid ] ] ) {
71- ga [ gx ] [ d [ columnid ] ] ++ ;
72- } else {
73- ga [ gx ] [ d [ columnid ] ] = 1 ;
74- }
68+ var g = gr [ gx ] ;
69+ if ( ! g ) {
70+ g = { } ;
71+ gr [ gx ] = g ;
72+ data . push ( g ) ;
73+ cols . forEach ( function ( colid ) {
74+ g [ colid ] = d [ colid ] ;
75+ } ) ;
76+ }
7577
76- if ( ! gnewcols [ d [ columnid ] ] ) {
77- gnewcols [ d [ columnid ] ] = true ;
78- newcols . push ( d [ columnid ] ) ;
78+ if ( ! ga [ gx ] ) {
79+ ga [ gx ] = { } ;
80+ }
81+
82+ var pivotColValue = d [ columnid ] ;
83+ var aggValue = d [ exprcolid ] ;
84+
85+ // Increment count only for non-null/undefined values when calculating AVG
86+ if ( ga [ gx ] [ pivotColValue ] ) {
87+ if ( aggValue !== null && typeof aggValue !== 'undefined' ) {
88+ ga [ gx ] [ pivotColValue ] ++ ;
7989 }
90+ } else {
91+ ga [ gx ] [ pivotColValue ] = aggValue !== null && typeof aggValue !== 'undefined' ? 1 : 0 ;
92+ }
93+
94+ if ( ! gnewcols [ pivotColValue ] ) {
95+ gnewcols [ pivotColValue ] = true ;
96+ newcols . push ( pivotColValue ) ;
97+ }
8098
81- if ( aggr == 'SUM' || aggr == 'AVG' || aggr == 'TOTAL' ) {
82- if ( typeof g [ d [ columnid ] ] == 'undefined' ) g [ d [ columnid ] ] = 0 ;
83- g [ d [ columnid ] ] += + d [ exprcolid ] ;
84- } else if ( aggr == 'COUNT' ) {
85- if ( typeof g [ d [ columnid ] ] == 'undefined' ) g [ d [ columnid ] ] = 0 ;
86- g [ d [ columnid ] ] ++ ;
87- } else if ( aggr == 'MIN' ) {
88- if ( typeof g [ d [ columnid ] ] == 'undefined' ) g [ d [ columnid ] ] = d [ exprcolid ] ;
89- if ( d [ exprcolid ] < g [ d [ columnid ] ] ) g [ d [ columnid ] ] = d [ exprcolid ] ;
90- } else if ( aggr == 'MAX' ) {
91- if ( typeof g [ d [ columnid ] ] == 'undefined' ) g [ d [ columnid ] ] = d [ exprcolid ] ;
92- if ( d [ exprcolid ] > g [ d [ columnid ] ] ) g [ d [ columnid ] ] = d [ exprcolid ] ;
93- } else if ( aggr == 'FIRST' ) {
94- if ( typeof g [ d [ columnid ] ] == 'undefined' ) g [ d [ columnid ] ] = d [ exprcolid ] ;
95- } else if ( aggr == 'LAST' ) {
96- g [ d [ columnid ] ] = d [ exprcolid ] ;
97- } else if ( alasql . aggr [ aggr ] ) {
98- // Custom aggregator
99- alasql . aggr [ aggr ] ( g [ d [ columnid ] ] , d [ exprcolid ] ) ;
99+ // Apply the specified aggregation
100+ if ( aggr == 'SUM' || aggr == 'AVG' || aggr == 'TOTAL' ) {
101+ if ( aggValue !== null && typeof aggValue !== 'undefined' ) {
102+ g [ pivotColValue ] =
103+ typeof g [ pivotColValue ] === 'undefined' || g [ pivotColValue ] === null
104+ ? Number ( aggValue )
105+ : g [ pivotColValue ] + Number ( aggValue ) ;
106+ } else if ( typeof g [ pivotColValue ] === 'undefined' ) {
107+ g [ pivotColValue ] = null ;
108+ }
109+ } else if ( aggr == 'COUNT' ) {
110+ if ( exprcolid === '*' || ( aggValue !== null && typeof aggValue !== 'undefined' ) ) {
111+ g [ pivotColValue ] = ( g [ pivotColValue ] || 0 ) + 1 ;
112+ } else if ( typeof g [ pivotColValue ] === 'undefined' ) {
113+ g [ pivotColValue ] = 0 ;
114+ }
115+ } else if ( aggr == 'MIN' ) {
116+ if ( aggValue !== null && typeof aggValue !== 'undefined' ) {
117+ if (
118+ typeof g [ pivotColValue ] === 'undefined' ||
119+ g [ pivotColValue ] === null ||
120+ aggValue < g [ pivotColValue ]
121+ ) {
122+ g [ pivotColValue ] = aggValue ;
123+ }
124+ } else if ( typeof g [ pivotColValue ] === 'undefined' ) {
125+ g [ pivotColValue ] = null ;
126+ }
127+ } else if ( aggr == 'MAX' ) {
128+ if ( aggValue !== null && typeof aggValue !== 'undefined' ) {
129+ if (
130+ typeof g [ pivotColValue ] === 'undefined' ||
131+ g [ pivotColValue ] === null ||
132+ aggValue > g [ pivotColValue ]
133+ ) {
134+ g [ pivotColValue ] = aggValue ;
135+ }
136+ } else if ( typeof g [ pivotColValue ] === 'undefined' ) {
137+ g [ pivotColValue ] = null ;
138+ }
139+ } else if ( aggr == 'FIRST' ) {
140+ if ( typeof g [ pivotColValue ] === 'undefined' ) {
141+ g [ pivotColValue ] = aggValue ;
142+ }
143+ } else if ( aggr == 'LAST' ) {
144+ g [ pivotColValue ] = aggValue ;
145+ } else if ( alasql . aggr [ aggr ] ) {
146+ if ( typeof g [ pivotColValue ] === 'undefined' ) {
147+ g [ pivotColValue ] = alasql . aggr [ aggr ] ( aggValue , undefined , 1 ) ;
100148 } else {
101- throw new Error ( 'Wrong aggregator in PIVOT clause' ) ;
149+ g [ pivotColValue ] = alasql . aggr [ aggr ] ( aggValue , g [ pivotColValue ] , 2 ) ;
102150 }
151+ } else {
152+ throw new Error ( 'Unknown aggregator in PIVOT clause: ' + aggr ) ;
103153 }
104154 } ) ;
105155
156+ // Finalize AVG calculation
106157 if ( aggr == 'AVG' ) {
107158 for ( var gx in gr ) {
108159 var d = gr [ gx ] ;
109- for ( var colid in d ) {
110- if ( cols . indexOf ( colid ) == - 1 && colid != exprcolid ) {
111- d [ colid ] = d [ colid ] / ga [ gx ] [ colid ] ;
160+ for ( var pivotColValue in ga [ gx ] ) {
161+ if ( d . hasOwnProperty ( pivotColValue ) && d [ pivotColValue ] !== null ) {
162+ var count = ga [ gx ] [ pivotColValue ] ;
163+ if ( count > 0 ) {
164+ d [ pivotColValue ] = d [ pivotColValue ] / count ;
165+ } else {
166+ d [ pivotColValue ] = null ;
167+ }
112168 }
113169 }
114170 }
115171 }
116172
117- // console.log(query.columns,newcols);
118- // columns
173+ // --- Rebuild query.columns ---
119174 query . data = data ;
120175
121- if ( inlist ) newcols = inlist ;
176+ if ( inlist ) {
177+ newcols = inlist ;
178+ } else {
179+ newcols . sort ( ) ;
180+ }
181+
182+ // Find original column definition - might be basic if SELECT * was used
183+ let aggColDef = query . columns . find ( col => col . columnid === exprcolid ) ;
184+ // If SELECT * was used, aggColDef might be missing, find it from the table definition if possible
185+ if ( ! aggColDef && query . sources && query . sources . length > 0 ) {
186+ let sourceTableId = query . sources [ 0 ] . tableid ;
187+ let sourceDbId = query . sources [ 0 ] . databaseid ;
188+ if (
189+ sourceTableId &&
190+ sourceDbId &&
191+ alasql . databases [ sourceDbId ] ?. tables ?. [ sourceTableId ] ?. xcolumns
192+ ) {
193+ aggColDef = alasql . databases [ sourceDbId ] . tables [ sourceTableId ] . xcolumns [ exprcolid ] ;
194+ }
195+ }
196+ // Provide a fallback if still not found
197+ aggColDef = aggColDef || { columnid : exprcolid , dbtypeid : 'OBJECT' } ;
122198
123- var ncol = query . columns . filter ( function ( col ) {
124- return col . columnid == exprcolid ;
125- } ) [ 0 ] ;
199+ // Keep only the grouping columns initially
126200 query . columns = query . columns . filter ( function ( col ) {
127- return ! ( col . columnid == columnid || col . columnid == exprcolid ) ;
201+ return cols . includes ( col . columnid ) ;
128202 } ) ;
129- newcols . forEach ( function ( colid ) {
130- var nc = cloneDeep ( ncol ) ;
131- nc . columnid = colid ;
203+
204+ // Add the new pivoted columns
205+ newcols . forEach ( function ( newColId ) {
206+ var nc = cloneDeep ( aggColDef ) ;
207+ nc . columnid = newColId ;
208+
209+ // ---- Final Refined Type Logic ----
210+ const originalType = ( aggColDef . dbtypeid || 'OBJECT' ) . toUpperCase ( ) ;
211+ const integerTypes = [
212+ 'INT' ,
213+ 'INTEGER' ,
214+ 'SMALLINT' ,
215+ 'BIGINT' ,
216+ 'SERIAL' ,
217+ 'SMALLSERIAL' ,
218+ 'BIGSERIAL' ,
219+ ] ;
220+ const numericTypes = [ ...integerTypes , 'NUMBER' , 'FLOAT' , 'DECIMAL' , 'NUMERIC' , 'MONEY' ] ;
221+
222+ if ( aggr === 'COUNT' ) {
223+ nc . dbtypeid = 'INT' ;
224+ } else if ( aggr === 'AVG' ) {
225+ // Keep INT type if original was INT-like, otherwise use FLOAT
226+ if ( integerTypes . includes ( originalType ) ) {
227+ nc . dbtypeid = aggColDef . dbtypeid ; // Preserve original INT-like type
228+ } else {
229+ nc . dbtypeid = 'FLOAT' ;
230+ }
231+ } else if ( aggr === 'SUM' || aggr === 'TOTAL' ) {
232+ // Preserve numeric types, default to FLOAT otherwise
233+ if ( numericTypes . includes ( originalType ) ) {
234+ nc . dbtypeid = aggColDef . dbtypeid ;
235+ } else {
236+ nc . dbtypeid = 'FLOAT' ; // Default for non-numeric or unknown sums
237+ }
238+ } else if ( aggr === 'MIN' || aggr === 'MAX' || aggr === 'FIRST' || aggr === 'LAST' ) {
239+ // Preserve original type as comparisons work across types
240+ nc . dbtypeid = aggColDef . dbtypeid ;
241+ }
242+ // For custom aggregators (AGGR, REDUCE), inherit type from clone, ensure fallback
243+ else if ( ! nc . dbtypeid ) {
244+ nc . dbtypeid = 'OBJECT' ;
245+ }
246+
132247 query . columns . push ( nc ) ;
133248 } ) ;
134249 } ;
0 commit comments