SQLGlot เป็นตัวแยกวิเคราะห์ SQL, Transpiler, Optimizer และ Engine ที่ไม่ขึ้นต่อกัน สามารถใช้ในการจัดรูปแบบ SQL หรือแปลระหว่าง 24 ภาษาถิ่นที่แตกต่างกัน เช่น DuckDB, Presto / Trino, Spark / Databricks, Snowflake และ BigQuery มีจุดมุ่งหมายเพื่ออ่านอินพุตและเอาต์พุต SQL ที่หลากหลายตามไวยากรณ์และความหมายที่ถูกต้องของ SQL ในภาษาถิ่นเป้าหมาย
มันเป็นตัวแยกวิเคราะห์ SQL ทั่วไปที่ครอบคลุมมากพร้อมชุดทดสอบที่แข็งแกร่ง มันยังมีประสิทธิภาพค่อนข้างมากในขณะที่เขียนด้วยภาษา Python ล้วนๆ
คุณสามารถปรับแต่ง parser วิเคราะห์แบบสอบถาม แผนผังนิพจน์การสำรวจ และสร้าง SQL โดยทางโปรแกรมได้อย่างง่ายดาย
ข้อผิดพลาดทางไวยากรณ์จะถูกเน้นไว้ และความเข้ากันไม่ได้ของภาษาถิ่นสามารถเตือนหรือเพิ่มขึ้นได้ ขึ้นอยู่กับการกำหนดค่า อย่างไรก็ตาม SQLGlot ไม่ได้มีจุดมุ่งหมายที่จะเป็นตัวตรวจสอบ SQL ดังนั้นจึงอาจตรวจไม่พบข้อผิดพลาดทางไวยากรณ์บางอย่าง
เรียนรู้เพิ่มเติมเกี่ยวกับ SQLGlot ในเอกสารประกอบ API และไพรเมอร์แผนผังนิพจน์
เรายินดีเป็นอย่างยิ่งที่จะมีส่วนร่วมกับ SQLGlot; อ่านคู่มือการสนับสนุนและเอกสารการเริ่มต้นใช้งานเพื่อเริ่มต้น!
จาก PyPI:
pip3 install " sqlglot[rs] "
# Without Rust tokenizer (slower):
# pip3 install sqlglot
หรือด้วยการชำระเงินในท้องถิ่น:
make install
ข้อกำหนดสำหรับการพัฒนา (ไม่บังคับ):
make install-dev
ให้หมายเลขเวอร์ชัน MAJOR
MINOR
. PATCH
, SQLGlot ใช้กลยุทธ์การกำหนดเวอร์ชันต่อไปนี้:
PATCH
จะเพิ่มขึ้นเมื่อมีการแก้ไขที่เข้ากันได้แบบย้อนหลังหรือการเพิ่มคุณสมบัติMINOR
จะเพิ่มขึ้นเมื่อมีการแก้ไขที่เข้ากันไม่ได้แบบย้อนหลังหรือการเพิ่มฟีเจอร์MAJOR
จะเพิ่มขึ้นเมื่อมีการแก้ไขหรือคุณลักษณะเพิ่มเติมที่เข้ากันไม่ได้แบบย้อนหลังอย่างมีนัยสำคัญ เราชอบที่จะได้ยินจากคุณ เข้าร่วมช่อง Slack ชุมชนของเรา!
ฉันพยายามแยกวิเคราะห์ SQL ที่ควรถูกต้องแต่ล้มเหลว เหตุใดจึงเกิดขึ้น
parse_one(sql, dialect="spark")
(หรืออีกทางหนึ่ง: read="spark"
) หากไม่มีการระบุภาษาถิ่น parse_one
จะพยายามแยกวิเคราะห์แบบสอบถามตาม "ภาษา SQLGlot" ซึ่งได้รับการออกแบบให้เป็นชุดที่เหนือกว่าของภาษาถิ่นที่รองรับทั้งหมด หากคุณพยายามระบุภาษาถิ่นแล้ว แต่ยังใช้งานไม่ได้ โปรดแจ้งปัญหาฉันพยายามส่งออก SQL แต่ภาษาถิ่นไม่ถูกต้อง!
parse_one(sql, dialect="spark").sql(dialect="duckdb")
(หรืออีกทางหนึ่ง: transpile(sql, read="spark", write="duckdb")
).ฉันพยายามแยกวิเคราะห์ SQL ที่ไม่ถูกต้องและใช้งานได้ แม้ว่าจะทำให้เกิดข้อผิดพลาดก็ตาม! เหตุใดจึงไม่ตรวจสอบ SQL ของฉัน
เกิดอะไรขึ้นกับ sqlglot.dataframe
แปลจากภาษาหนึ่งไปอีกภาษาหนึ่งได้อย่างง่ายดาย ตัวอย่างเช่น ฟังก์ชันวันที่/เวลาจะแตกต่างกันไปตามภาษาถิ่นและอาจจัดการได้ยาก:
import sqlglot
sqlglot . transpile ( "SELECT EPOCH_MS(1618088028295)" , read = "duckdb" , write = "hive" )[ 0 ]
' SELECT FROM_UNIXTIME(1618088028295 / POW(10, 3)) '
SQLGlot ยังสามารถแปลรูปแบบเวลาที่กำหนดเองได้:
import sqlglot
sqlglot . transpile ( "SELECT STRFTIME(x, '%y-%-m-%S')" , read = "duckdb" , write = "hive" )[ 0 ]
" SELECT DATE_FORMAT(x, 'yy-M-ss') "
ตัวคั่นตัวระบุและประเภทข้อมูลสามารถแปลได้เช่นกัน:
import sqlglot
# Spark SQL requires backticks (`) for delimited identifiers and uses `FLOAT` over `REAL`
sql = """WITH baz AS (SELECT a, c FROM foo WHERE a = 1) SELECT f.a, b.b, baz.c, CAST("b"."a" AS REAL) d FROM foo f JOIN bar b ON f.a = b.a LEFT JOIN baz ON f.a = baz.a"""
# Translates the query into Spark SQL, formats it, and delimits all of its identifiers
print ( sqlglot . transpile ( sql , write = "spark" , identify = True , pretty = True )[ 0 ])
WITH ` baz ` AS (
SELECT
` a ` ,
` c `
FROM ` foo `
WHERE
` a ` = 1
)
SELECT
` f ` . ` a ` ,
` b ` . ` b ` ,
` baz ` . ` c ` ,
CAST( ` b ` . ` a ` AS FLOAT) AS ` d `
FROM ` foo ` AS ` f `
JOIN ` bar ` AS ` b `
ON ` f ` . ` a ` = ` b ` . ` a `
LEFT JOIN ` baz `
ON ` f ` . ` a ` = ` baz ` . ` a `
ความคิดเห็นจะถูกเก็บรักษาไว้บนพื้นฐานที่ดีที่สุด:
sql = """
/* multi
line
comment
*/
SELECT
tbl.cola /* comment 1 */ + tbl.colb /* comment 2 */,
CAST(x AS SIGNED), # comment 3
y -- comment 4
FROM
bar /* comment 5 */,
tbl # comment 6
"""
# Note: MySQL-specific comments (`#`) are converted into standard syntax
print ( sqlglot . transpile ( sql , read = 'mysql' , pretty = True )[ 0 ])
/* multi
line
comment
*/
SELECT
tbl . cola /* comment 1 */ + tbl . colb /* comment 2 */ ,
CAST(x AS INT ), /* comment 3 */
y /* comment 4 */
FROM bar /* comment 5 */ , tbl /* comment 6 */
คุณสามารถสำรวจ SQL ด้วยตัวช่วยนิพจน์เพื่อทำสิ่งต่างๆ เช่น ค้นหาคอลัมน์และตารางในคิวรี:
from sqlglot import parse_one , exp
# print all column references (a and b)
for column in parse_one ( "SELECT a, b + 1 AS c FROM d" ). find_all ( exp . Column ):
print ( column . alias_or_name )
# find all projections in select statements (a and c)
for select in parse_one ( "SELECT a, b + 1 AS c FROM d" ). find_all ( exp . Select ):
for projection in select . expressions :
print ( projection . alias_or_name )
# find all tables (x, y, z)
for table in parse_one ( "SELECT * FROM x JOIN y JOIN z" ). find_all ( exp . Table ):
print ( table . name )
อ่านไพรเมอร์ ast เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับระบบภายในของ SQLGlot
เมื่อ parser ตรวจพบข้อผิดพลาดในรูปแบบไวยากรณ์ ParseError
จะเกิดขึ้น:
import sqlglot
sqlglot . transpile ( "SELECT foo FROM (SELECT baz FROM t" )
sqlglot.errors.ParseError: Expecting ). Line 1, Col: 34.
SELECT foo FROM (SELECT baz FROM t
~
ข้อผิดพลาดทางไวยากรณ์ที่มีโครงสร้างสามารถเข้าถึงได้สำหรับการใช้งานทางโปรแกรม:
import sqlglot
try :
sqlglot . transpile ( "SELECT foo FROM (SELECT baz FROM t" )
except sqlglot . errors . ParseError as e :
print ( e . errors )
[{
'description' : 'Expecting )' ,
'line' : 1 ,
'col' : 34 ,
'start_context' : 'SELECT foo FROM (SELECT baz FROM ' ,
'highlight' : 't' ,
'end_context' : '' ,
'into_expression' : None
}]
อาจไม่สามารถแปลคำถามบางคำระหว่างภาษาถิ่นบางภาษาได้ ในกรณีเหล่านี้ SQLGlot อาจส่งคำเตือนและจะดำเนินการแปลอย่างดีที่สุดตามค่าเริ่มต้น:
import sqlglot
sqlglot . transpile ( "SELECT APPROX_DISTINCT(a, 0.1) FROM foo" , read = "presto" , write = "hive" )
APPROX_COUNT_DISTINCT does not support accuracy
' SELECT APPROX_COUNT_DISTINCT(a) FROM foo '
ลักษณะการทำงานนี้สามารถเปลี่ยนแปลงได้โดยการตั้งค่าแอตทริบิวต์ unsupported_level
ตัวอย่างเช่น เราสามารถตั้งค่าเป็น RAISE
หรือ IMMEDIATE
เพื่อให้แน่ใจว่ามีข้อยกเว้นเกิดขึ้นแทน:
import sqlglot
sqlglot . transpile ( "SELECT APPROX_DISTINCT(a, 0.1) FROM foo" , read = "presto" , write = "hive" , unsupported_level = sqlglot . ErrorLevel . RAISE )
sqlglot.errors.UnsupportedError: APPROX_COUNT_DISTINCT does not support accuracy
มีคำถามที่ต้องการข้อมูลเพิ่มเติมเพื่อแปลอย่างถูกต้อง เช่น สคีมาของตารางที่อ้างอิงในนั้น เนื่องจากการเปลี่ยนแปลงบางอย่างไวต่อประเภท ซึ่งหมายความว่าจำเป็นต้องมีการอนุมานประเภทเพื่อให้เข้าใจความหมาย แม้ว่ากฎของตัวเพิ่มประสิทธิภาพ qualify
และ annotate_types
สามารถช่วยได้ แต่จะไม่ได้ใช้ตามค่าเริ่มต้นเนื่องจากกฎเหล่านี้เพิ่มค่าใช้จ่ายและความซับซ้อนที่สำคัญ
โดยทั่วไปการ Transpilation เป็นปัญหาที่ยาก ดังนั้น SQLGlot จึงใช้วิธีการ "ส่วนเพิ่ม" ในการแก้ปัญหา ซึ่งหมายความว่าอาจมีคู่ภาษาถิ่นที่ขาดการสนับสนุนสำหรับอินพุตบางอย่างในปัจจุบัน แต่คาดว่าจะปรับปรุงเมื่อเวลาผ่านไป เรายินดีอย่างยิ่งกับปัญหาหรือการประชาสัมพันธ์ที่มีการจัดทำเอกสารและทดสอบอย่างดี โปรดติดต่อเราหากต้องการคำแนะนำ
SQLGlot รองรับการสร้างนิพจน์ SQL แบบเพิ่มหน่วย:
from sqlglot import select , condition
where = condition ( "x=1" ). and_ ( "y=1" )
select ( "*" ). from_ ( "y" ). where ( where ). sql ()
' SELECT * FROM y WHERE x = 1 AND y = 1 '
เป็นไปได้ที่จะแก้ไขแผนผังการแยกวิเคราะห์:
from sqlglot import parse_one
parse_one ( "SELECT x FROM y" ). from_ ( "z" ). sql ()
' SELECT x FROM z '
นิพจน์ที่แยกวิเคราะห์ยังสามารถแปลงแบบวนซ้ำได้โดยใช้ฟังก์ชันการแมปกับแต่ละโหนดในแผนผัง:
from sqlglot import exp , parse_one
expression_tree = parse_one ( "SELECT a FROM x" )
def transformer ( node ):
if isinstance ( node , exp . Column ) and node . name == "a" :
return parse_one ( "FUN(a)" )
return node
transformed_tree = expression_tree . transform ( transformer )
transformed_tree . sql ()
' SELECT FUN(a) FROM x '
SQLGlot สามารถเขียนแบบสอบถามใหม่เป็นรูปแบบ "ปรับให้เหมาะสม" ใช้เทคนิคต่างๆ มากมายเพื่อสร้าง Canonical AST ใหม่ AST นี้สามารถใช้เพื่อกำหนดมาตรฐานให้กับการสืบค้นหรือจัดเตรียมพื้นฐานสำหรับการนำกลไกไปใช้จริง ตัวอย่างเช่น:
import sqlglot
from sqlglot . optimizer import optimize
print (
optimize (
sqlglot . parse_one ( """
SELECT A OR (B OR (C AND D))
FROM x
WHERE Z = date '2021-01-01' + INTERVAL '1' month OR 1 = 0
""" ),
schema = { "x" : { "A" : "INT" , "B" : "INT" , "C" : "INT" , "D" : "INT" , "Z" : "STRING" }}
). sql ( pretty = True )
)
SELECT
(
" x " . " a " <> 0 OR " x " . " b " <> 0 OR " x " . " c " <> 0
)
AND (
" x " . " a " <> 0 OR " x " . " b " <> 0 OR " x " . " d " <> 0
) AS " _col_0 "
FROM " x " AS " x "
WHERE
CAST( " x " . " z " AS DATE ) = CAST( ' 2021-02-01 ' AS DATE )
คุณสามารถดูเวอร์ชัน AST ของ SQL ที่แยกวิเคราะห์ได้โดยการเรียก repr
:
from sqlglot import parse_one
print ( repr ( parse_one ( "SELECT a + 1 AS z" )))
Select (
expressions = [
Alias (
this = Add (
this = Column (
this = Identifier ( this = a , quoted = False )),
expression = Literal ( this = 1 , is_string = False )),
alias = Identifier ( this = z , quoted = False ))])
SQLGlot สามารถคำนวณความแตกต่างทางความหมายระหว่างสองนิพจน์และการเปลี่ยนแปลงเอาต์พุตในรูปแบบของลำดับการดำเนินการที่จำเป็นในการแปลงนิพจน์ต้นทางให้เป็นนิพจน์เป้าหมาย:
from sqlglot import diff , parse_one
diff ( parse_one ( "SELECT a + b, c, d" ), parse_one ( "SELECT c, a - b, d" ))
[
Remove ( expression = Add (
this = Column (
this = Identifier ( this = a , quoted = False )),
expression = Column (
this = Identifier ( this = b , quoted = False )))),
Insert ( expression = Sub (
this = Column (
this = Identifier ( this = a , quoted = False )),
expression = Column (
this = Identifier ( this = b , quoted = False )))),
Keep (
source = Column ( this = Identifier ( this = a , quoted = False )),
target = Column ( this = Identifier ( this = a , quoted = False ))),
...
]
ดูเพิ่มเติมที่: Semantic Diff สำหรับ SQL
สามารถเพิ่มภาษาถิ่นได้โดยการแบ่งคลาสย่อย Dialect
:
from sqlglot import exp
from sqlglot . dialects . dialect import Dialect
from sqlglot . generator import Generator
from sqlglot . tokens import Tokenizer , TokenType
class Custom ( Dialect ):
class Tokenizer ( Tokenizer ):
QUOTES = [ "'" , '"' ]
IDENTIFIERS = [ "`" ]
KEYWORDS = {
** Tokenizer . KEYWORDS ,
"INT64" : TokenType . BIGINT ,
"FLOAT64" : TokenType . DOUBLE ,
}
class Generator ( Generator ):
TRANSFORMS = { exp . Array : lambda self , e : f"[ { self . expressions ( e ) } ]" }
TYPE_MAPPING = {
exp . DataType . Type . TINYINT : "INT64" ,
exp . DataType . Type . SMALLINT : "INT64" ,
exp . DataType . Type . INT : "INT64" ,
exp . DataType . Type . BIGINT : "INT64" ,
exp . DataType . Type . DECIMAL : "NUMERIC" ,
exp . DataType . Type . FLOAT : "FLOAT64" ,
exp . DataType . Type . DOUBLE : "FLOAT64" ,
exp . DataType . Type . BOOLEAN : "BOOL" ,
exp . DataType . Type . TEXT : "STRING" ,
}
print ( Dialect [ "custom" ])
<class '__main__.Custom'>
SQLGlot สามารถตีความการสืบค้น SQL โดยที่ตารางจะแสดงเป็นพจนานุกรม Python กลไกไม่ควรเร็ว แต่มีประโยชน์สำหรับการทดสอบหน่วยและการรัน SQL แบบเนทิฟในอ็อบเจ็กต์ Python นอกจากนี้ รากฐานยังสามารถรวมเข้ากับเคอร์เนลประมวลผลที่รวดเร็ว เช่น Arrow และ Pandas ได้อย่างง่ายดาย
ตัวอย่างด้านล่างแสดงการดำเนินการของแบบสอบถามที่เกี่ยวข้องกับการรวมและการรวม:
from sqlglot . executor import execute
tables = {
"sushi" : [
{ "id" : 1 , "price" : 1.0 },
{ "id" : 2 , "price" : 2.0 },
{ "id" : 3 , "price" : 3.0 },
],
"order_items" : [
{ "sushi_id" : 1 , "order_id" : 1 },
{ "sushi_id" : 1 , "order_id" : 1 },
{ "sushi_id" : 2 , "order_id" : 1 },
{ "sushi_id" : 3 , "order_id" : 2 },
],
"orders" : [
{ "id" : 1 , "user_id" : 1 },
{ "id" : 2 , "user_id" : 2 },
],
}
execute (
"""
SELECT
o.user_id,
SUM(s.price) AS price
FROM orders o
JOIN order_items i
ON o.id = i.order_id
JOIN sushi s
ON i.sushi_id = s.id
GROUP BY o.user_id
""" ,
tables = tables
)
user_id price
1 4.0
2 3.0
ดูเพิ่มเติม: การเขียนเอ็นจิ้น Python SQL ตั้งแต่เริ่มต้น
SQLGlot ใช้ pdoc เพื่อให้บริการเอกสาร API
เวอร์ชันที่โฮสต์อยู่บนเว็บไซต์ SQLGlot หรือคุณสามารถสร้างในเครื่องด้วย:
make docs-serve
make style # Only linter checks
make unit # Only unit tests (or unit-rs, to use the Rust tokenizer)
make test # Unit and integration tests (or test-rs, to use the Rust tokenizer)
make check # Full test suite & linter checks
การวัดประสิทธิภาพทำงานบน Python 3.10.12 ในไม่กี่วินาที
แบบสอบถาม | sqlglot | sqlglotrs | sqlfluff | sqltree | sqlparse | moz_sql_parser | เอสคิวออกไซด์ |
---|---|---|---|---|---|---|---|
ทีพีซี | 0.00944 (1.0) | 0.00590 (0.625) | 0.32116 (33.98) | 0.00693 (0.734) | 0.02858 (3.025) | 0.03337 (3.532) | 0.00073 (0.077) |
สั้น | 0.00065 (1.0) | 0.00044 (0.687) | 0.03511 (53.82) | 0.00049 (0.759) | 0.00163 (2.506) | 0.00234 (3.601) | 0.00005 (0.073) |
ยาว | 0.00889 (1.0) | 0.00572 (0.643) | 0.36982 (41.56) | 0.00614 (0.690) | 0.02530 (2.844) | 0.02931 (3.294) | 0.00059 (0.066) |
คลั่งไคล้ | 0.02918 (1.0) | 0.01991 (0.682) | 1.88695 (64.66) | 0.02003 (0.686) | 7.46894 (255.9) | 0.64994 (22.27) | 0.00327 (0.112) |
SQLGlot ใช้ dateutil เพื่อลดความซับซ้อนของนิพจน์ไทม์เดลต้าตามตัวอักษร เครื่องมือเพิ่มประสิทธิภาพจะไม่ลดความซับซ้อนของนิพจน์ดังต่อไปนี้หากไม่พบโมดูล:
x + interval ' 1 ' month