60 |
60 |
@property
|
61 |
61 |
def members(self):
|
62 |
62 |
assert self.type != 'date'
|
|
63 |
value = self.value
|
|
64 |
value_label = self.value_label or value
|
|
65 |
order_by = self.order_by
|
|
66 |
|
63 |
67 |
with self.engine.get_cursor() as cursor:
|
64 |
68 |
sql = self.members_query
|
65 |
69 |
if not sql:
|
66 |
|
if self.dimension.join:
|
67 |
|
join = self.engine_cube.get_join(self.dimension.join[-1])
|
68 |
|
sql = ('SELECT %s AS value, %s::text AS label FROM %s AS "%s" '
|
69 |
|
'GROUP BY %s, %s ORDER BY %s' % (
|
70 |
|
self.value, self.value_label or self.value, join.table, join.name,
|
71 |
|
self.value, self.value_label or self.value, self.order_by or self.value))
|
|
70 |
table_expression = self.engine_cube.fact_table
|
|
71 |
if self.join:
|
|
72 |
table_expression = self.engine_cube.build_table_expression(
|
|
73 |
self.join, self.engine_cube.fact_table)
|
|
74 |
sql = 'SELECT %s AS value, %s::text AS label ' % (value, value_label)
|
|
75 |
sql += 'FROM %s ' % table_expression
|
|
76 |
if order_by:
|
|
77 |
if not isinstance(order_by, list):
|
|
78 |
order_by = [order_by]
|
72 |
79 |
else:
|
73 |
|
sql = ('SELECT %s AS value, %s::text AS label FROM {fact_table} '
|
74 |
|
'GROUP BY %s, %s ORDER BY %s' % (
|
75 |
|
self.value, self.value_label or self.value, self.value,
|
76 |
|
self.value_label or self.value, self.order_by or self.value))
|
|
80 |
order_by = [value]
|
|
81 |
group_by = [value]
|
|
82 |
if value_label not in group_by:
|
|
83 |
group_by.append(value_label)
|
|
84 |
for order_value in order_by:
|
|
85 |
if order_value not in group_by:
|
|
86 |
group_by.append(order_value)
|
|
87 |
sql += 'GROUP BY %s ' % ', '.join(group_by)
|
|
88 |
sql += 'ORDER BY (%s) ' % ', '.join(order_by)
|
77 |
89 |
sql = sql.format(fact_table=self.engine_cube.fact_table)
|
78 |
90 |
self.engine.log.debug('SQL: %s', sql)
|
79 |
91 |
cursor.execute(sql)
|
... | ... | |
196 |
208 |
return ProxyList(obj.engine, obj, self.attribute, self.cls, chain=self.chain)
|
197 |
209 |
|
198 |
210 |
|
199 |
|
def build_table_expression(join_tree, table_name, alias=None, top=True, other_conditions=None):
|
200 |
|
'''Recursively build the table expression from the join tree,
|
201 |
|
starting from the fact table'''
|
202 |
|
|
203 |
|
sql = table_name
|
204 |
|
if alias:
|
205 |
|
sql += ' AS "%s"' % alias
|
206 |
|
add_paren = False
|
207 |
|
for kind in ['left', 'inner', 'right', 'full']:
|
208 |
|
joins = join_tree.get(table_name, {}).get(kind)
|
209 |
|
if not joins:
|
210 |
|
continue
|
211 |
|
add_paren = True
|
212 |
|
join_kinds = {
|
213 |
|
'inner': 'INNER JOIN',
|
214 |
|
'left': 'LEFT OUTER JOIN',
|
215 |
|
'right': 'RIGHT OUTER JOIN',
|
216 |
|
'full': 'FULL OUTER JOIN',
|
217 |
|
}
|
218 |
|
sql += ' %s ' % join_kinds[kind]
|
219 |
|
sub_joins = []
|
220 |
|
conditions = []
|
221 |
|
if other_conditions:
|
222 |
|
conditions = other_conditions
|
223 |
|
other_conditions = None
|
224 |
|
for join_name, join in joins.iteritems():
|
225 |
|
sub_joins.append(
|
226 |
|
build_table_expression(join_tree, join.table, join.name, top=False))
|
227 |
|
conditions.append('"%s".%s = "%s"."%s"' % (
|
228 |
|
alias or table_name,
|
229 |
|
join.master.split('.')[-1],
|
230 |
|
join.name, join.detail))
|
231 |
|
sub_join = ' CROSS JOIN '.join(sub_joins)
|
232 |
|
if len(sub_joins) > 1:
|
233 |
|
sub_join = '(%s)' % sub_join
|
234 |
|
sql += sub_join
|
235 |
|
sql += ' ON %s' % ' AND '.join(conditions)
|
236 |
|
if not top and add_paren:
|
237 |
|
sql = '(%s)' % sql
|
238 |
|
return sql
|
239 |
|
|
240 |
|
|
241 |
211 |
class EngineCube(object):
|
242 |
212 |
dimensions = ProxyListDescriptor('all_dimensions', EngineDimension, chain=JSONDimensions)
|
243 |
213 |
measures = ProxyListDescriptor('measures', EngineMeasure)
|
... | ... | |
293 |
263 |
projections.append('%s AS %s' % (dimension.value_label or dimension.value,
|
294 |
264 |
dimension.name))
|
295 |
265 |
group_by.append(dimension.group_by or dimension.value)
|
296 |
|
order_by.append(dimension.order_by or dimension.value)
|
|
266 |
order_by.extend(dimension.order_by or [dimension.value])
|
|
267 |
|
|
268 |
for order_value in order_by:
|
|
269 |
if order_value not in group_by:
|
|
270 |
group_by.append(order_value)
|
297 |
271 |
|
298 |
272 |
for measure_name in measures:
|
299 |
273 |
measure = self.get_measure(measure_name)
|
... | ... | |
302 |
276 |
sql = 'SELECT ' + ', '.join(projections)
|
303 |
277 |
table_expression = ' %s' % self.cube.fact_table
|
304 |
278 |
if joins:
|
305 |
|
join_tree = {}
|
306 |
|
# Build join tree
|
307 |
|
for join_name in joins:
|
308 |
|
join = self.get_join(join_name)
|
309 |
|
master_table = join.master_table or self.fact_table
|
310 |
|
join_tree.setdefault(master_table, {}).setdefault(join.kind, {})[join.name] = join
|
311 |
|
table_expression = build_table_expression(join_tree,
|
312 |
|
self.fact_table,
|
313 |
|
other_conditions=join_conditions)
|
|
279 |
table_expression = self.build_table_expression(
|
|
280 |
joins, self.fact_table, other_conditions=join_conditions)
|
314 |
281 |
sql += ' FROM %s' % table_expression
|
315 |
282 |
where_conditions = 'true'
|
316 |
283 |
if where:
|
... | ... | |
349 |
316 |
'value': value,
|
350 |
317 |
} for cell, value in zip(cells, row)]
|
351 |
318 |
|
|
319 |
def build_table_expression(self, joins, table_name, other_conditions=None):
|
|
320 |
'''Recursively build the table expression from the join tree,
|
|
321 |
starting from the fact table'''
|
|
322 |
|
|
323 |
join_tree = {}
|
|
324 |
# Build join tree
|
|
325 |
for join_name in joins:
|
|
326 |
join = self.get_join(join_name)
|
|
327 |
master_table = join.master_table or self.fact_table
|
|
328 |
join_tree.setdefault(master_table, {}).setdefault(join.kind, {})[join.name] = join
|
|
329 |
|
|
330 |
def build_table_expression_helper(join_tree, table_name, alias=None, top=True, other_conditions=None):
|
|
331 |
sql = table_name
|
|
332 |
if alias:
|
|
333 |
sql += ' AS "%s"' % alias
|
|
334 |
add_paren = False
|
|
335 |
for kind in ['left', 'inner', 'right', 'full']:
|
|
336 |
joins = join_tree.get(alias or table_name, {}).get(kind)
|
|
337 |
if not joins:
|
|
338 |
continue
|
|
339 |
add_paren = True
|
|
340 |
join_kinds = {
|
|
341 |
'inner': 'INNER JOIN',
|
|
342 |
'left': 'LEFT OUTER JOIN',
|
|
343 |
'right': 'RIGHT OUTER JOIN',
|
|
344 |
'full': 'FULL OUTER JOIN',
|
|
345 |
}
|
|
346 |
sql += ' %s ' % join_kinds[kind]
|
|
347 |
sub_joins = []
|
|
348 |
conditions = []
|
|
349 |
if other_conditions:
|
|
350 |
conditions = other_conditions
|
|
351 |
other_conditions = None
|
|
352 |
for join_name, join in joins.iteritems():
|
|
353 |
sub_joins.append(
|
|
354 |
build_table_expression_helper(join_tree, join.table, alias=join.name, top=False))
|
|
355 |
conditions.append('"%s".%s = "%s"."%s"' % (
|
|
356 |
alias or table_name,
|
|
357 |
join.master.split('.')[-1],
|
|
358 |
join.name, join.detail))
|
|
359 |
sub_join = ' CROSS JOIN '.join(sub_joins)
|
|
360 |
if len(sub_joins) > 1:
|
|
361 |
sub_join = '(%s)' % sub_join
|
|
362 |
sql += sub_join
|
|
363 |
sql += ' ON %s' % ' AND '.join(conditions)
|
|
364 |
if not top and add_paren:
|
|
365 |
sql = '(%s)' % sql
|
|
366 |
return sql
|
|
367 |
return build_table_expression_helper(
|
|
368 |
join_tree, table_name, other_conditions=other_conditions)
|
|
369 |
|
352 |
370 |
|
353 |
371 |
class Engine(object):
|
354 |
372 |
def __init__(self, warehouse):
|