Языки регексов недостаточно эффективны для сопоставления произвольно вложенных конструкций. Для этого вам нужен пусковой автомат (т. Е. Синтаксический анализатор). Доступно несколько таких инструментов, таких как PLY .
Python также предоставляет библиотеку синтаксиса для своего собственного синтаксиса, который может делать то, что вам нужно. Выход очень подробный, однако, и занимает некоторое время, чтобы обернуть голову. Если вы заинтересованы в этом угле, следующее обсуждение пытается объяснить все как можно проще.
>>> import parser, pprint
>>> pprint.pprint(parser.st2list(parser.expr('(((1+0)+1)+1)')))
[258,
[327,
[304,
[305,
[306,
[307,
[308,
[310,
[311,
[312,
[313,
[314,
[315,
[316,
[317,
[318,
[7, '('],
[320,
[304,
[305,
[306,
[307,
[308,
[310,
[311,
[312,
[313,
[314,
[315,
[316,
[317,
[318,
[7, '('],
[320,
[304,
[305,
[306,
[307,
[308,
[310,
[311,
[312,
[313,
[314,
[315,
[316,
[317,
[318,
[7,
'('],
[320,
[304,
[305,
[306,
[307,
[308,
[310,
[311,
[312,
[313,
[314,
[315,
[316,
[317,
[318,
[2,
'1']]]]],
[14,
'+'],
[315,
[316,
[317,
[318,
[2,
'0']]]]]]]]]]]]]]]],
[8,
')']]]]],
[14,
'+'],
[315,
[316,
[317,
[318,
[2,
'1']]]]]]]]]]]]]]]],
[8, ')']]]]],
[14, '+'],
[315,
[316,
[317,
[318, [2, '1']]]]]]]]]]]]]]]],
[8, ')']]]]]]]]]]]]]]]],
[4, ''],
[0, '']]
Вы можете облегчить боль этой короткой функцией:
def shallow(ast):
if not isinstance(ast, list): return ast
if len(ast) == 2: return shallow(ast[1])
return [ast[0]] + [shallow(a) for a in ast[1:]]
>>> pprint.pprint(shallow(parser.st2list(parser.expr('(((1+0)+1)+1)'))))
[258,
[318,
'(',
[314,
[318, '(', [314, [318, '(', [314, '1', '+', '0'], ')'], '+', '1'], ')'],
'+',
'1'],
')'],
'',
'']
Числа поступают из модулей Python symbol
и token
, которые вы можете использовать для построения таблицы поиска из чисел в имена:
map = dict(token.tok_name.items() + symbol.sym_name.items())
Вы даже можете свернуть это сопоставление в shallow()
, чтобы вы могли работать со строками вместо чисел:
def shallow(ast):
if not isinstance(ast, list): return ast
if len(ast) == 2: return shallow(ast[1])
return [map[ast[0]]] + [shallow(a) for a in ast[1:]]
>>> pprint.pprint(shallow(parser.st2list(parser.expr('(((1+0)+1)+1)'))))
['eval_input',
['atom',
'(',
['arith_expr',
['atom',
'(',
['arith_expr',
['atom', '(', ['arith_expr', '1', '+', '0'], ')'],
'+',
'1'],
')'],
'+',
'1'],
')'],
'',
'']
Ваш запрос пока выглядит хорошо. Чтобы проверить, использует ли продукт клиент впервые, вы можете использовать ROW_NUMBER()
, чтобы присвоить ранг каждой записи в группах записей с одинаковым UserId
и одинаковым Product
, упорядоченных по Date
. Когда номер строки 1
, вы знаете, что имеете дело с новым продуктом.
SELECT
UA.UserId,
UA.Product,
UA.Client,
CASE
WHEN ROW_NUMBER() OVER(PARTITION BY UA.UserId, UA.Product ORDER BY UA.Date) = 1
THEN true
ELSE false
END AS IsNewProduct,
CASE
WHEN UA.Date = FLS.Date
THEN true
ELSE false
END AS IsNewClient
FROM UserActivity as UA
LEFT JOIN FirstLastSeen AS FLS
ON UA.UserId = FLS.UserId
AND UA.Product = FLS.Product
AND UA.Client = FLS.Client;
Эта демонстрация на DB Fiddle с вашими примерами возвращает:
| UserId | Product | Client | IsNewProduct | IsNewClient |
| ------ | ------- | ----------- | ------------ | ----------- |
| John | Bank | Mobile App | 1 | 0 |
| John | Bank | Desktop App | 0 | 1 |
| Sally | Gym | Web App | 1 | 1 |