sqlparse是Python的一个解析SQL语言的库,安装和文档我也不累赘说了,提供一下官网的地址python-sqlparse,在官网给出的github库里面,有一个提取表名的example,挺好用的,大家可以直接使用。
官方的文档比较简单,所以我希望在这篇文章里面补充一些信息,帮助大家理解文档和使用这个工具包。
sqlparse有几个最简单的工具:split,format,parse,分别是提取sql单个语句、格式化sql的语句以及解析sql,这几个函数官网和其他文章都有大量的例子,也就不再介绍。
先给出这篇文章使用的sql例子:
CREATE TABLE TABLE_TO_CREATE NOLOGGING AS
SELECT DISTINCT
A.COLA,
B.COLB,
DECODE(A.DECODE_CONDITION, 1, '是', '否') DECODED,
ROW_NUMBER() OVER(PARTITION BY A.CLASS_CONDITION ORDER BY A.RAND_CONDITION DESC) RN
FROM FSCRM.TABLE_A A,
(SELECT * FROM TABLE_C C WHERE C.SOMETHING='SOMETHING' AND C.NUM=1234) B
WHERE A.COMPARE_CONDITION=B.COMPARE_CONDITION
AND A.NUM NOT IN (1, 2, 3)
AND NOT EXISTS (SELECT D.COLD FROM TABLE_D WHERE A.COLA=D.COLD)
ORDER BY A.ORDER_CONDITION
/* COMMENTS */
;
了解过sqlparse的人都知道,使用parse后,sql语句被解析成一棵树。这棵树跟常见的树不一样,因为它的父节点,是完全包含了子节点的信息。例如在上面的例子:
In [1]: import sqlparse
In [2]: with open('sample.sql', 'r', encoding='utf8') as sql_file:
...: file_parse = sqlparse.parse(sql_file.read().strip())
...:
In [3]: file_parse
Out[3]: (,)
In [4]: for token in file_parse[0].tokens:
...: print(type(token), token.ttype, token.value)
...:
Token.Keyword.DDL CREATE
Token.Text.Whitespace
Token.Keyword TABLE
Token.Text.Whitespace
None TABLE_TO_CREATE NOLOGGING
Token.Text.Whitespace
Token.Keyword AS
Token.Text.Whitespace.Newline
Token.Keyword.DML SELECT
Token.Text.Whitespace
Token.Keyword DISTINCT
Token.Text.Whitespace.Newline
Token.Text.Whitespace
None A.COLA,
B.COLB,
DECODE
None (A.DECODE_CONDITION, 1, '是', '否')
ECODED,
ROW_NUMBER
None () OVER
None (PARTITION BY A.CLASS_CONDITION ORDER BY
A.RAND_CONDITION DESC) RN
Token.Text.Whitespace.Newline
Token.Keyword FROM
Token.Text.Whitespace
None FSCRM.TABLE_A A,
(SELECT * FROM TABLE_C C WHERE C.SOMETHING='SOMETHING' AND C.NU
=1234) B
Token.Text.Whitespace
None WHERE A.COMPARE_CONDITION=B.COMPARE_CONDITION
AND A.NUM NOT IN (1, 2, 3)
AND NOT EXISTS (SELECT D.COLD FROM TABLE_D WHERE A.COLA=D.COLD)
Token.Keyword ORDER
Token.Text.Whitespace
Token.Keyword BY
Token.Text.Whitespace
None A.ORDER_CONDITION
/* COMMENTS */
Token.Punctuation ;
这里要提醒一下的就是,从上面的out[3]大家可以看到parse语句返回的是一个tuple,就算只有一个statement,也是一个tuple,所以我们在使用的时候需要加上下标[0]。
解析出来的statement其实也是token的一个子类,而如果token的is_group是True的话(这个group跟sql的group不是一回事),每个token都有tokens属性,存了一个子token的list。当我们遍历后会发现,里面的数据类型除了基本类型Token,还有Where,Identifier,IdentifierList等等,这些都是继承了Token的子类,但大多没有额外的方法,基本就是多了一些声明包含关键字的常量,都是类属性。而我们查看ttype这个属性,主要是标注了一些关键字或者标点符号如空格、括号、分号等。我们进行选择的时候可以通过ttype做个初步的筛选。而value存的是这个token的整个语句,即便是它是一个group,里面有很多子token,它也是完全存了所有子token拼成的整个语句。所以当我们使用子token的parent属性时,很容易就可以找到父结点的整句sql语句。
下面我们来介绍Identifier和IdentifierList:
In [10]: identifier_list = file_parse[0].tokens[13]
In [11]: identifier_list
Out[11]:
In [12]: identifier_list.get_identifiers()
Out[12]:
In [13]: for identifier in identifier_list.get_identifiers():
...: print(type(identifier), identifier.ttype, identifier.value)
...:
None A.COLA
None B.COLB
None DECODE
In [14]: for identifier in identifier_list.get_identifiers():
...: print(type(identifier), identifier.ttype, identifier.value, identi
...: fier.get_real_name())
...:
None A.COLA COLA
None B.COLB COLB
None DECODE DECODE
从上述代码可以看到,IdentifierList比普通的token多了一个方法get_identifiers方法,返回的是一个Identifier的列表,而每个Identifier可以使用get_real_name来获取真名,这个用在提取字段名和表名的时候非常有用。
最后一个想介绍的就是Where类型,这个类型下面主要是一系列的条件:
In [4]: where = file_parse[0].tokens[22]
In [5]: where
Out[5]:
In [6]: for token in where.tokens:
...: print(type(token), token.ttype, token.value)
...:
Token.Keyword WHERE
Token.Text.Whitespace
None A.COMPARE_CONDITION=B.COMPARE_CONDITION
Token.Text.Whitespace.Newline
Token.Keyword AND
Token.Text.Whitespace
None A.NUM
Token.Text.Whitespace
Token.Keyword NOT
Token.Text.Whitespace
Token.Keyword IN
Token.Text.Whitespace
None (1, 2, 3)
Token.Text.Whitespace.Newline
Token.Keyword AND
Token.Text.Whitespace
Token.Keyword NOT
Token.Text.Whitespace
Token.Keyword EXISTS
Token.Text.Whitespace
None (SELECT D.COLD FROM TABLE_D WHERE A.COLA
=D.COLD)
Token.Text.Whitespace.Newline
这里重点要看的是,对于比较语句,有个特别的token子类叫Comparison,但是诸如not in、not exist等特殊语句,在这里面是拆分成几个token的,这点比较不友好。我们在编程的时候如果要计算条件数,不能直接数token数,计算and的个数比较好。
最后在提几个小细节,如果我们不想按层次去遍历语句,工具包提供了一个方法flatten,直接可以把所有子、孙结点平铺输出:
In [9]: for id, item in enumerate(where.flatten()):
...: print(id, item.value)
...:
0 WHERE
1
2 A
3 .
4 COMPARE_CONDITION
5 =
6 B
7 .
8 COMPARE_CONDITION
9
10 AND
11
12 A
13 .
14 NUM
15
16 NOT
17
18 IN
19
20 (
21 1
22 ,
23
24 2
25 ,
26
27 3
28 )
29
30 AND
31
32 NOT
33
34 EXISTS
35
36 (
37 SELECT
38
39 D
40 .
41 COLD
42
43 FROM
44
45 TABLE_D
46
47 WHERE
48
49 A
50 .
51 COLA
52 =
53 D
54 .
55 COLD
56 )
57
这个种方式的用途之一可以一次过查找所有子查询,然后通过parent语句找出父节点:
In [11]: for item in file_parse[0].flatten():
...: if item.ttype is sqlparse.tokens.Keyword.DML and item.value.upper(
...: ) == 'SELECT':
...: print(item.parent)
...:
CREATE TABLE TABLE_TO_CREATE NOLOGGING AS
SELECT DISTINCT
A.COLA,
B.COLB,
DECODE(A.DECODE_CONDITION, 1, '是', '否') DECODED,
ROW_NUMBER() OVER(PARTITION BY A.CLASS_CONDITION ORDER BY A.RAND
_CONDITION DESC) RN
FROM FSCRM.TABLE_A A,
(SELECT * FROM TABLE_C C WHERE C.SOMETHING='SOMETHING' AND C.NUM
=1234) B
WHERE A.COMPARE_CONDITION=B.COMPARE_CONDITION
AND A.NUM NOT IN (1, 2, 3)
AND NOT EXISTS (SELECT D.COLD FROM TABLE_D WHERE A.COLA=D.COLD)
ORDER BY A.ORDER_CONDITION
/* COMMENTS */
;
(SELECT * FROM TABLE_C C WHERE C.SOMETHING='SOMETHING' AND C.NUM=1234)
(SELECT D.COLD FROM TABLE_D WHERE A.COLA=D.COLD)
以上就是sqlparse的解析过程,实践出真知,大家可以在解析的过程中多输出各个token的类型、属性,借此来深入了解这个工具包。希望本文对你有所帮助。