SQLGlot ist ein unabhängiger SQL-Parser, Transpiler, Optimierer und eine Engine. Es kann zum Formatieren von SQL oder zum Übersetzen zwischen 24 verschiedenen Dialekten wie DuckDB, Presto/Trino, Spark/Databricks, Snowflake und BigQuery verwendet werden. Ziel ist es, eine Vielzahl von SQL-Eingaben und -Ausgaben zu lesen und SQL in den Zieldialekten syntaktisch und semantisch zu korrigieren.
Es handelt sich um einen sehr umfassenden generischen SQL-Parser mit einer robusten Testsuite. Es ist auch recht leistungsfähig, obwohl es ausschließlich in Python geschrieben ist.
Sie können den Parser einfach anpassen, Abfragen analysieren, Ausdrucksbäume durchlaufen und SQL programmgesteuert erstellen.
Syntaxfehler werden hervorgehoben und Dialektinkompatibilitäten können je nach Konfiguration eine Warnung oder ein Problem darstellen. Allerdings zielt SQLGlot nicht darauf ab, ein SQL-Validator zu sein, daher kann es sein, dass bestimmte Syntaxfehler nicht erkannt werden.
Weitere Informationen zu SQLGlot finden Sie in der API-Dokumentation und im Ausdrucksbaum-Primer.
Beiträge sind in SQLGlot sehr willkommen; Lesen Sie den Beitragsleitfaden und das Onboarding-Dokument, um loszulegen!
Von PyPI:
pip3 install " sqlglot[rs] "
# Without Rust tokenizer (slower):
# pip3 install sqlglot
Oder mit einer Kasse vor Ort:
make install
Voraussetzungen für die Entwicklung (optional):
make install-dev
Angesichts einer Versionsnummer MAJOR
. MINOR
. PATCH
, SQLGlot verwendet die folgende Versionierungsstrategie:
PATCH
Version wird erhöht, wenn abwärtskompatible Korrekturen oder Funktionserweiterungen vorliegen.MINOR
Version wird erhöht, wenn abwärtsinkompatible Korrekturen oder Funktionserweiterungen vorliegen.MAJOR
Version wird erhöht, wenn es erhebliche abwärtsinkompatible Korrekturen oder Funktionserweiterungen gibt. Wir würden uns freuen, von Ihnen zu hören. Treten Sie unserem Community-Slack-Kanal bei!
Ich habe versucht, SQL zu analysieren, das gültig sein sollte, aber es ist fehlgeschlagen. Warum ist das passiert?
parse_one(sql, dialect="spark")
(alternativ: read="spark"
). Wenn kein Dialekt angegeben ist, versucht parse_one
, die Abfrage gemäß dem „SQLGlot-Dialekt“ zu analysieren, der als Obermenge aller unterstützten Dialekte konzipiert ist. Wenn Sie versucht haben, den Dialekt anzugeben, und es immer noch nicht funktioniert, melden Sie bitte ein Problem.Ich habe versucht, SQL auszugeben, aber es ist nicht im richtigen Dialekt!
parse_one(sql, dialect="spark").sql(dialect="duckdb")
(alternativ: transpile(sql, read="spark", write="duckdb")
aus transpile(sql, read="spark", write="duckdb")
).Ich habe versucht, ungültiges SQL zu analysieren, und es hat funktioniert, obwohl es einen Fehler auslösen sollte! Warum wurde mein SQL nicht validiert?
Was ist mit sqlglot.dataframe passiert?
Übersetzen Sie ganz einfach von einem Dialekt in einen anderen. Beispielsweise variieren Datums-/Uhrzeitfunktionen je nach Dialekt und können schwierig zu handhaben sein:
import sqlglot
sqlglot . transpile ( "SELECT EPOCH_MS(1618088028295)" , read = "duckdb" , write = "hive" )[ 0 ]
' SELECT FROM_UNIXTIME(1618088028295 / POW(10, 3)) '
SQLGlot kann sogar benutzerdefinierte Zeitformate übersetzen:
import sqlglot
sqlglot . transpile ( "SELECT STRFTIME(x, '%y-%-m-%S')" , read = "duckdb" , write = "hive" )[ 0 ]
" SELECT DATE_FORMAT(x, 'yy-M-ss') "
Bezeichnertrennzeichen und Datentypen können ebenfalls übersetzt werden:
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 `
Kommentare werden ebenfalls nach bestem Wissen und Gewissen aufbewahrt:
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 */
Sie können SQL mit Ausdruckshelfern erkunden, um beispielsweise Spalten und Tabellen in einer Abfrage zu finden:
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 )
Lesen Sie den Ast-Primer, um mehr über die Interna von SQLGlot zu erfahren.
Wenn der Parser einen Fehler in der Syntax erkennt, löst er einen ParseError
aus:
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
~
Strukturierte Syntaxfehler sind für die programmgesteuerte Verwendung zugänglich:
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
}]
Es kann sein, dass einige Anfragen zwischen bestimmten Dialekten nicht übersetzt werden können. In diesen Fällen gibt SQLGlot möglicherweise eine Warnung aus und führt standardmäßig eine bestmögliche Übersetzung durch:
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 '
Dieses Verhalten kann durch Festlegen des unsupported_level
-Attributs geändert werden. Wir können es beispielsweise entweder auf RAISE
oder IMMEDIATE
setzen, um sicherzustellen, dass stattdessen eine Ausnahme ausgelöst wird:
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
Es gibt Abfragen, für deren genaue Transpilierung zusätzliche Informationen erforderlich sind, beispielsweise die Schemata der darin referenzierten Tabellen. Dies liegt daran, dass bestimmte Transformationen typsensitiv sind, was bedeutet, dass eine Typinferenz erforderlich ist, um ihre Semantik zu verstehen. Obwohl die Optimiererregeln qualify
und annotate_types
hierbei hilfreich sein können, werden sie nicht standardmäßig verwendet, da sie einen erheblichen Mehraufwand und eine erhebliche Komplexität verursachen.
Transpilation ist im Allgemeinen ein schwieriges Problem, daher verwendet SQLGlot einen „inkrementellen“ Ansatz zur Lösung. Dies bedeutet, dass es möglicherweise Dialektpaare gibt, die derzeit für einige Eingaben keine Unterstützung bieten. Es wird jedoch erwartet, dass sich dies im Laufe der Zeit verbessert. Wir schätzen gut dokumentierte und getestete Probleme oder PRs sehr. Wenn Sie Hilfe benötigen, können Sie sich jederzeit an uns wenden!
SQLGlot unterstützt die inkrementelle Erstellung von SQL-Ausdrücken:
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 '
Es ist möglich, einen analysierten Baum zu ändern:
from sqlglot import parse_one
parse_one ( "SELECT x FROM y" ). from_ ( "z" ). sql ()
' SELECT x FROM z '
Geparste Ausdrücke können auch rekursiv transformiert werden, indem auf jeden Knoten im Baum eine Zuordnungsfunktion angewendet wird:
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 kann Abfragen in eine „optimierte“ Form umschreiben. Es führt eine Vielzahl von Techniken aus, um einen neuen kanonischen AST zu erstellen. Dieser AST kann zur Standardisierung von Abfragen oder zur Bereitstellung der Grundlagen für die Implementierung einer tatsächlichen Engine verwendet werden. Zum Beispiel:
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 )
Sie können die AST-Version des analysierten SQL anzeigen, indem Sie repr
aufrufen:
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 kann den semantischen Unterschied zwischen zwei Ausdrücken berechnen und Änderungen in Form einer Folge von Aktionen ausgeben, die erforderlich sind, um einen Quellausdruck in einen Zielausdruck umzuwandeln:
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 ))),
...
]
Siehe auch: Semantischer Unterschied für SQL.
Dialekte können durch Unterklassifizierung von Dialect
hinzugefügt werden:
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 ist in der Lage, SQL-Abfragen zu interpretieren, wobei die Tabellen als Python-Wörterbücher dargestellt werden. Die Engine soll nicht schnell sein, kann aber für Unit-Tests und die native Ausführung von SQL über Python-Objekte hinweg nützlich sein. Darüber hinaus kann die Grundlage problemlos in schnelle Rechenkerne wie Arrow und Pandas integriert werden.
Das folgende Beispiel zeigt die Ausführung einer Abfrage, die Aggregationen und Joins umfasst:
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
Siehe auch: Eine Python-SQL-Engine von Grund auf schreiben.
SQLGlot verwendet pdoc, um seine API-Dokumentation bereitzustellen.
Eine gehostete Version finden Sie auf der SQLGlot-Website, oder Sie können sie lokal erstellen mit:
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
Benchmarks laufen auf Python 3.10.12 in Sekundenschnelle.
Abfrage | sqlglot | sqlglotrs | sqlfluff | sqltree | sqlparse | moz_sql_parser | SQLoxid |
---|---|---|---|---|---|---|---|
tpch | 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) |
kurz | 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) |
lang | 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) |
verrückt | 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 verwendet dateutil, um literale Timedelta-Ausdrücke zu vereinfachen. Der Optimierer vereinfacht Ausdrücke wie die folgenden nicht, wenn das Modul nicht gefunden werden kann:
x + interval ' 1 ' month