|
@@ -4,18 +4,66 @@
|
|
|
# All rights reserved
|
|
|
#
|
|
|
# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
|
|
|
+from collections import (
|
|
|
+ OrderedDict,
|
|
|
+)
|
|
|
+from typing import Tuple
|
|
|
from psycopg.sql import (
|
|
|
Identifier,
|
|
|
SQL,
|
|
|
Literal,
|
|
|
- Placeholder,
|
|
|
- Composed,
|
|
|
-)
|
|
|
-from collections import (
|
|
|
- OrderedDict,
|
|
|
+ Composable,
|
|
|
)
|
|
|
+from .util import get_select, get_from
|
|
|
|
|
|
-def get_where(unit, product=None, category=None, group=None, organic=None, limit='90 days'):
|
|
|
+def get_selectors(
|
|
|
+ unit: str,
|
|
|
+ product: str,
|
|
|
+ window: SQL
|
|
|
+) -> OrderedDict[Tuple[str, Composable]]:
|
|
|
+ return OrderedDict([
|
|
|
+ ('id', Identifier('transactions', 'id')),
|
|
|
+ ('ts_raw', SQL("""(transactions.ts AT TIME ZONE 'UTC')::timestamp without time zone""")),
|
|
|
+ ('%d/%m/%y %_I%P', SQL("""(transactions.ts AT TIME ZONE 'UTC')::timestamp without time zone""")),
|
|
|
+ ('code', Identifier('stores', 'code')),
|
|
|
+ ('$/unit', SQL("""TRUNC(
|
|
|
+ price / quantity / convert_unit(units.name, {unit}, {product}), 4
|
|
|
+)""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
+ ('last', SQL(f"""TRUNC(last_value(
|
|
|
+ price / quantity / convert_unit(units.name, {{unit}}, {{product}})
|
|
|
+) OVER {window}, 4)
|
|
|
+""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
+ ('avg', SQL(f"""TRUNC(sum(price) OVER {window} / sum(
|
|
|
+ quantity * convert_unit(units.name, {{unit}}, {{product}})
|
|
|
+) OVER {window}, 4)
|
|
|
+""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
+ ('min', SQL(f"""TRUNC(min(
|
|
|
+ price / quantity / convert_unit(units.name, {{unit}}, {{product}})
|
|
|
+) OVER {window}, 4)
|
|
|
+""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
+ ('max', SQL(f"""TRUNC(max(
|
|
|
+ price / quantity / convert_unit(units.name, {{unit}}, {{product}})
|
|
|
+) OVER {window}, 4)
|
|
|
+""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
+ ('price', SQL("""TRUNC(price, 4)""")),
|
|
|
+ ('quantity', SQL("""TRUNC(
|
|
|
+ quantity * convert_unit(units.name, {unit}, {product}), 4
|
|
|
+)""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
+ ('product', Identifier('products', 'name')),
|
|
|
+ ('category', Identifier('categories', 'name')),
|
|
|
+ ('group', Identifier('groups', 'name')),
|
|
|
+ ('organic', Identifier('organic')),
|
|
|
+ ])
|
|
|
+
|
|
|
+JOINS = OrderedDict([
|
|
|
+ ('units', ('id', 'unit_id')),
|
|
|
+ ('stores', ('id', 'store_id')),
|
|
|
+ ('products', ('id', 'product_id')),
|
|
|
+ ('categories', ('id', 'category_id')),
|
|
|
+ ('groups', ('id', 'group_id')),
|
|
|
+])
|
|
|
+
|
|
|
+def get_where(product=None, category=None, group=None, organic=None, limit='90 days'):
|
|
|
where = [ ]
|
|
|
if product is not None:
|
|
|
where.append(SQL(' ').join([
|
|
@@ -48,9 +96,6 @@ def get_where(unit, product=None, category=None, group=None, organic=None, limit
|
|
|
interval=SQL("{literal}::interval").format(literal=Literal(limit))
|
|
|
)
|
|
|
)
|
|
|
- #where.append(SQL(
|
|
|
- # 'convert_unit(units.name, {unit}, {product}) IS NOT NULL'
|
|
|
- #).format(unit=Literal(unit), product=Literal(product)))
|
|
|
return SQL('').join([
|
|
|
SQL("WHERE"
|
|
|
"\n "),
|
|
@@ -58,93 +103,28 @@ def get_where(unit, product=None, category=None, group=None, organic=None, limit
|
|
|
])
|
|
|
|
|
|
def get_historic_prices_statement(unit, sort=None, product=None, category=None, group=None, organic=None, limit='90 days'):
|
|
|
- partition = f"(PARTITION BY {'organic,' if organic is not None else ''} product_id ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)"
|
|
|
+ window = f"""(
|
|
|
+PARTITION BY {'organic,' if organic is not None else ''} product_id
|
|
|
+ORDER BY ts
|
|
|
+ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
|
|
|
+)"""
|
|
|
organic_sort = f"{'organic,' if organic is not None else ''}"
|
|
|
sort_sql = SQL('').join([
|
|
|
SQL('{sort} {direction},').format(
|
|
|
sort=Identifier(f'{sort}'),
|
|
|
- direction = SQL('DESC' if sort == 'ts' else 'ASC')
|
|
|
+ direction=SQL('DESC' if sort == 'ts' else 'ASC')
|
|
|
),
|
|
|
]) if sort is not None else SQL('')
|
|
|
|
|
|
- select = OrderedDict([
|
|
|
- ('id', Identifier('transactions','id')),
|
|
|
- ('ts_raw', SQL("""(transactions.ts AT TIME ZONE 'UTC')::timestamp without time zone""")),
|
|
|
- ('%d/%m/%y %_I%P', SQL("""(transactions.ts AT TIME ZONE 'UTC')::timestamp without time zone""")),
|
|
|
- ('code', Identifier('stores', 'code')),
|
|
|
- ('$/unit', SQL("""TRUNC(
|
|
|
- price / quantity / convert_unit(units.name, {unit}, {product}), 4
|
|
|
-)""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
- ('last', SQL(f"""TRUNC(last_value(
|
|
|
- price / quantity / convert_unit(units.name, {{unit}}, {{product}})
|
|
|
-) OVER {partition}, 4)
|
|
|
-""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
- ('avg', SQL(f"""TRUNC(sum(price) OVER {partition} / sum(
|
|
|
- quantity * convert_unit(units.name, {{unit}}, {{product}})
|
|
|
-) OVER {partition}, 4)
|
|
|
-""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
- ('min', SQL(f"""TRUNC(min(
|
|
|
- price / quantity / convert_unit(units.name, {{unit}}, {{product}})
|
|
|
-) OVER {partition}, 4)
|
|
|
-""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
- ('max', SQL(f"""TRUNC(max(
|
|
|
- price / quantity / convert_unit(units.name, {{unit}}, {{product}})
|
|
|
-) OVER {partition}, 4)
|
|
|
-""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
- ('price', SQL("""TRUNC(price, 4)""")),
|
|
|
- ('quantity', SQL("""TRUNC(
|
|
|
- quantity * convert_unit(units.name, {unit}, {product}), 4
|
|
|
-)""").format(unit=Literal(unit), product=Literal(product))),
|
|
|
- ('product', Identifier('products','name')),
|
|
|
- ('category', Identifier('categories', 'name')),
|
|
|
- ('group', Identifier('groups', 'name')),
|
|
|
- ('organic', Identifier('organic')),
|
|
|
- ])
|
|
|
statement = SQL('\n').join([
|
|
|
- SQL('').join([
|
|
|
- SQL("SELECT"
|
|
|
- "\n "),
|
|
|
- SQL(','
|
|
|
- "\n ").join([
|
|
|
- SQL(' ').join([v, SQL('AS'), Identifier(f'{k}')]) for k, v in select.items()
|
|
|
- ])
|
|
|
- ]),
|
|
|
- SQL('').join([
|
|
|
- SQL("FROM"
|
|
|
- "\n "),
|
|
|
- SQL("\n JOIN ").join([
|
|
|
- SQL("transactions"),
|
|
|
- SQL("{table} ON {table}.{key} = {index}").format(
|
|
|
- table=Identifier('units'),
|
|
|
- key=Identifier('id'),
|
|
|
- index=Identifier('unit_id'),
|
|
|
- ),
|
|
|
- SQL("{table} ON {table}.{key} = {index}").format(
|
|
|
- table=Identifier('stores'),
|
|
|
- key=Identifier('id'),
|
|
|
- index=Identifier('store_id')
|
|
|
- ),
|
|
|
- SQL("{table} ON {table}.{key} = {index}").format(
|
|
|
- table=Identifier('products'),
|
|
|
- key=Identifier('id'),
|
|
|
- index=Identifier('product_id')
|
|
|
- ),
|
|
|
- SQL("{table} ON {table}.{key} = {index}").format(
|
|
|
- table=Identifier('categories'),
|
|
|
- key=Identifier('id'),
|
|
|
- index=Identifier('category_id')
|
|
|
- ),
|
|
|
- SQL("{table} ON {table}.{key} = {index}").format(
|
|
|
- table=Identifier('groups'),
|
|
|
- key=Identifier('id'),
|
|
|
- index=Identifier('group_id')
|
|
|
- ),
|
|
|
- ]),
|
|
|
- ]),
|
|
|
- get_where(unit, product=product, category=category, group=group, organic=organic, limit=limit),
|
|
|
- SQL('ORDER BY {organic_sort} {sort} code, product, category, "group", "$/unit" ASC, ts DESC').format(
|
|
|
+ get_select(get_selectors(unit, product, window)),
|
|
|
+ get_from("transactions", JOINS),
|
|
|
+ get_where(product=product, category=category, group=group, organic=organic, limit=limit),
|
|
|
+ SQL("""
|
|
|
+ORDER BY {organic_sort} {sort} code, product, category, "group", "$/unit" ASC, ts DESC
|
|
|
+""").format(
|
|
|
sort=sort_sql,
|
|
|
organic_sort=SQL(organic_sort),
|
|
|
- ),
|
|
|
+ ),
|
|
|
])
|
|
|
return statement
|