現在、クエリの「FROM」句に焦点を当てて、SQLステートメントに存在するテーブルをリストするSQLParseを使用してプロセスを作成しようとしています。また、FROM 句内でネストされたクエリ (またはサブクエリ) を識別し、プロセスを再度実行して、そのネストされたクエリ内のテーブルを識別しようとしています。
このサンプルクエリの使用
from sqlparse.sql import IdentifierList, Identifier, Function, Where, Parenthesis, TokenList
from sqlparse.tokens import Keyword, DML, Punctuation
sql_2 = """select * from luv_main.test_table left join (select * from luv_all.fake_Table where (a = b)) x where a = 4 order by A, B, C"""
以下は、動作しているコードです。
full_tables = []
tables = []
from_seen = False
for item in parsed.tokens:
#stop the process if the Where statement is reached
if isinstance(item, Where):
from_seen = False
if from_seen:
#multiple tables with Join statements in between, or one table. Doesn't consider subqueries
if isinstance(item, Identifier):
#checks to see if there is a parenthesis, meaning a subquery
if 'SELECT' in item.value.upper():
subquery = item.value
#returns the db name
tables.append(item.get_parent_name())
#returns the table name
tables.append(item.get_real_name())
#returns the alias
tables.append(item.get_alias())
full_tables.append(tables)
tables = []
# if multiple tables separated by comma's will be an identifier list. Doesn't consider subqueries
if isinstance(item, IdentifierList):
for identifier in item.get_identifiers():
#returns the db name
tables.append(identifier.get_parent_name())
#returns the table name
tables.append(identifier.get_real_name())
#returns the alias
tables.append(identifier.get_alias())
full_tables.append(tables)
tables = []
else:
if item.ttype is Keyword and item.value.upper() == 'FROM':
from_seen = True
print(full_tables)
print(len(full_tables))
これはそのままクエリから始まり、select という単語を検索してサブクエリを識別します。
#process of removing outer-most parentheses and identifying aliases that sit outside that window
#new subquery string ready to parse
res_sub = ""
#capture the alias
alias = ""
#record the number of parentheses as they open and close
paren_cnt = 0
for char in subquery:
#if ( and there's already been a ( , include it
if char == '(' and paren_cnt > 0:
res_sub += char
#if (, add to the count
if char == '(':
paren_cnt += 1
# if ) and there's at least 2 (, include it
if char == ')' and paren_cnt > 1:
res_sub += char
# if ), subtract from the count
if char == ')':
paren_cnt -= 1
# capture the script
if char != '(' and char != ')' and paren_cnt >0:
res_sub += char
# capture the alias
if char != '(' and char != ')' and char != ' ' and paren_cnt == 0:
alias += char
subparsed = sqlparse.parse(res_sub)[0]
これにより、最も外側の括弧が削除され、新しい SQL ステートメントとして解析されます。これはすべて機能しており、この解析済みステートメントを前のコード ブロックから手動で実行すると、期待どおりに機能します。
次に、これを別の関数に入れようとしました:
- 最初にクエリを解析し、次を呼び出します。
- FROM 句をスキャンしてテーブルを返す関数ですが、サブクエリを識別する場合は次を呼び出します。
- スクリプトの最も外側の括弧を削除し、最初の関数を呼び出してプロセスを介して送り返す関数。
しかし、実行しようとするsqlparse.parse(res_sub)[0]
とタプルインデックスが範囲外になります。タプルではなく、str である必要があります。これは、sqlparse.sql.Statement に解析されます。
一連の関数に入れたからといって、なぜ動作が異なるのかわかりません。以下の関数コード:
def parse(sql):
parsed = sqlparse.parse(sql)[0]
#call function to assess the FROM statement of the query
assess_from_clause(parsed)
def assess_from_clause(parsed):
full_tables = []
tables = []
from_seen = False
for item in parsed.tokens:
#stop the process if the Where statement is reached
if isinstance(item, Where):
from_seen = False
#checks to see if there is a parenthesis, meaning a subquery
if 'SELECT' in item.value.upper():
subquery = item.value
subquery_parsing(subquery)
if from_seen:
#multiple tables with Join statements in between, or one table. Doesn't consider subqueries
if isinstance(item, Identifier):
#returns the db name
tables.append(item.get_parent_name())
#returns the table name
tables.append(item.get_real_name())
#returns the alias
tables.append(item.get_alias())
full_tables.append(tables)
tables = []
# if multiple tables separated by comma's will be an identifier list. Doesn't consider subqueries
if isinstance(item, IdentifierList):
for identifier in item.get_identifiers():
#returns the db name
tables.append(identifier.get_parent_name())
#returns the table name
tables.append(identifier.get_real_name())
#returns the alias
tables.append(identifier.get_alias())
full_tables.append(tables)
tables = []
else:
if item.ttype is Keyword and item.value.upper() == 'FROM':
from_seen = True
print(full_tables)
def subquery_parsing(subquery):
#new subquery string ready to parse
res_sub = ''
#capture the alias
alias = ''
#record the number of parentheses as they open and close
paren_cnt = 0
for char in subquery:
#if ( and there's already been a ( , include it
if char == '(' and paren_cnt > 0:
res_sub += char
#if (, add to the count
if char == '(':
paren_cnt += 1
# if ) and there's at least 2 (, include it
if char == ')' and paren_cnt > 1:
res_sub += char
# if ), subtract from the count
if char == ')':
paren_cnt -= 1
# capture the script
if char != '(' and char != ')' and paren_cnt >0:
res_sub += char
# capture the alias
if char != '(' and char != ')' and char != ' ' and paren_cnt == 0:
alias += char
parse(res_sub)
私は Python に習熟していないことを強調しておく必要があります。
ありがとう