Skip to content

Commit 4ab6674

Browse files
committed
Merge pull request tylertreat#80 from sagarrakshe/render_query_modifications
Render query modifications
2 parents 9deda4e + 0221215 commit 4ab6674

File tree

3 files changed

+228
-53
lines changed

3 files changed

+228
-53
lines changed

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,32 @@ conditions = [
9999
}
100100
]
101101

102+
grouping = ['Timestamp']
103+
104+
having = [
105+
{
106+
'field': 'Timestamp',
107+
'type': 'INTEGER',
108+
'comparators': [
109+
{
110+
'condition': '==',
111+
'negate': False,
112+
'value': 1399478981
113+
}
114+
]
115+
}
116+
]
117+
118+
order_by ={'fields': ['Timestamp'], 'direction': 'desc'}
119+
102120
query = render_query(
103121
'dataset',
104122
['table'],
105123
select=selects,
106124
conditions=conditions,
107-
groupings=['Timestamp'],
108-
order_by={'field': 'Timestamp', 'direction': 'desc'}
125+
groupings=grouping,
126+
having=having,
127+
order_by=order_by
109128
)
110129

111130
job_id, _ = client.query(query)

bigquery/query_builder.py

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
def render_query(dataset, tables, select=None, conditions=None,
5-
groupings=None, order_by=None):
5+
groupings=None, having=None, order_by=None):
66
"""Render a query that will run over the given tables using the specified
77
parameters.
88
@@ -46,12 +46,13 @@ def render_query(dataset, tables, select=None, conditions=None,
4646
if None in (dataset, tables):
4747
return None
4848

49-
query = "%s %s %s %s %s" % (
49+
query = "%s %s %s %s %s %s" % (
5050
_render_select(select),
5151
_render_sources(dataset, tables),
5252
_render_conditions(conditions),
5353
_render_groupings(groupings),
54-
_render_order(order_by),
54+
_render_having(having),
55+
_render_order(order_by)
5556
)
5657

5758
return query
@@ -133,8 +134,21 @@ def _render_sources(dataset, tables):
133134
a string that represents the from part of a query.
134135
"""
135136

136-
return "FROM " + ", ".join(
137-
["[%s.%s]" % (dataset, table) for table in tables])
137+
if isinstance(tables, dict):
138+
if tables.get('date_range', False):
139+
try:
140+
dataset_table = '.'.join([dataset, tables['table']])
141+
return "FROM (TABLE_DATE_RANGE([{}], TIMESTAMP('{}'),"\
142+
" TIMESTAMP('{}'))) ".format(dataset_table,
143+
tables['from_date'],
144+
tables['to_date'])
145+
except KeyError as exp:
146+
logging.warn('Missing parameter %s in selecting sources' %
147+
(exp))
148+
149+
else:
150+
return "FROM " + ", ".join(
151+
["[%s.%s]" % (dataset, table) for table in tables])
138152

139153

140154
def _render_conditions(conditions):
@@ -206,6 +220,15 @@ def _render_condition(field, field_type, comparators):
206220
else:
207221
value = _render_condition_value(value, field_type)
208222
value = "(" + value + ")"
223+
elif condition == "BETWEEN":
224+
if isinstance(value, (tuple, list, set)) and len(value) == 2:
225+
value = ' AND '.join(
226+
sorted([_render_condition_value(v, field_type)
227+
for v in value])
228+
)
229+
elif isinstance(value, (tuple, list, set)) and len(value) != 2:
230+
logging.warn('Invalid condition passed in: %s' % condition)
231+
209232
else:
210233
value = _render_condition_value(value, field_type)
211234

@@ -242,38 +265,76 @@ def _render_condition_value(value, field_type):
242265
value = 1 if value else 0
243266
elif field_type in ("STRING", "INTEGER", "FLOAT"):
244267
value = "'%s'" % (value)
268+
elif field_type in ("TIMESTAMP"):
269+
value = "'%s'" % (str(value))
245270
return "%s(%s)" % (field_type, value)
246271

247272

248-
def _render_order(order):
249-
"""Render the order by part of a query.
273+
def _render_groupings(fields):
274+
"""Render the group by part of a query.
250275
251276
Args:
252-
order: a dictionary with two keys, field and direction.
253-
Such that the dictionary should be formatted as
254-
{'field':'TimeStamp, 'direction':'desc'}.
277+
fields: a list of fields to group by.
255278
256279
Returns:
257-
a string that represents the order by part of a query.
280+
a string that represents the group by part of a query.
258281
"""
259282

260-
if not order or 'field' not in order or 'direction' not in order:
261-
return ''
283+
if not fields:
284+
return ""
262285

263-
return "ORDER BY %s %s" % (order['field'], order['direction'])
286+
return "GROUP BY " + ", ".join(fields)
264287

265288

266-
def _render_groupings(fields):
267-
"""Render the group by part of a query.
289+
def _render_having(having_conditions):
290+
"""Render the having part of a query.
268291
269292
Args:
270-
fields: a list of fields to group by.
293+
conditions: a list of dictionary items to filter the rows.
294+
Each dict should be formatted as {'field': 'start_time',
295+
'value': {'value': 1, 'negate': False}, 'comparator': '>',
296+
'type': 'FLOAT'} which is represetned as
297+
'start_time > FLOAT('1')' in the query.
271298
272299
Returns:
273-
a string that represents the group by part of a query.
300+
a string that represents the having part of a query.
274301
"""
302+
if not having_conditions:
303+
return ""
275304

276-
if not fields:
305+
rendered_conditions = []
306+
307+
for condition in having_conditions:
308+
field = condition.get('field')
309+
field_type = condition.get('type')
310+
comparators = condition.get('comparators')
311+
312+
if None in (field, field_type, comparators) or not comparators:
313+
logging.warn('Invalid condition passed in: %s' % condition)
314+
continue
315+
316+
rendered_conditions.append(
317+
_render_condition(field, field_type, comparators))
318+
319+
if not rendered_conditions:
277320
return ""
278321

279-
return "GROUP BY " + ", ".join(fields)
322+
return "HAVING %s" % (" AND ".join(rendered_conditions))
323+
324+
325+
def _render_order(order):
326+
"""Render the order by part of a query.
327+
328+
Args:
329+
order: a dictionary with two keys, fields and direction.
330+
Such that the dictionary should be formatted as
331+
{'fields': ['TimeStamp'], 'direction':'desc'}.
332+
333+
Returns:
334+
a string that represents the order by part of a query.
335+
"""
336+
337+
if not order or 'fields' not in order or 'direction' not in order:
338+
return ''
339+
340+
return "ORDER BY %s %s" % (", ".join(order['fields']), order['direction'])

0 commit comments

Comments
 (0)