SQLGlot es un analizador, transpilador, optimizador y motor de SQL sin dependencia. Se puede utilizar para formatear SQL o traducir entre 24 dialectos diferentes como DuckDB, Presto/Trino, Spark/Databricks, Snowflake y BigQuery. Su objetivo es leer una amplia variedad de entradas y salidas de SQL sintáctica y semánticamente correctas en los dialectos específicos.
Es un analizador SQL genérico muy completo con un sólido conjunto de pruebas. También es bastante eficaz, aunque está escrito exclusivamente en Python.
Puede personalizar fácilmente el analizador, analizar consultas, recorrer árboles de expresión y crear SQL mediante programación.
Los errores de sintaxis se resaltan y las incompatibilidades de dialectos pueden advertir o surgir según las configuraciones. Sin embargo, SQLGlot no pretende ser un validador de SQL, por lo que es posible que no detecte ciertos errores de sintaxis.
Obtenga más información sobre SQLGlot en la documentación de la API y en el manual del árbol de expresiones.
Las contribuciones son bienvenidas en SQLGlot; ¡Lea la guía de contribución y el documento de incorporación para comenzar!
De PyPI:
pip3 install " sqlglot[rs] "
# Without Rust tokenizer (slower):
# pip3 install sqlglot
O con una caja local:
make install
Requisitos para el desarrollo (opcional):
make install-dev
Dado un número de versión MAJOR
. MINOR
. PATCH
, SQLGlot utiliza la siguiente estrategia de control de versiones:
PATCH
aumenta cuando hay correcciones compatibles con versiones anteriores o adiciones de funciones.MINOR
se incrementa cuando hay correcciones o adiciones de funciones incompatibles con versiones anteriores.MAJOR
se incrementa cuando hay correcciones significativas incompatibles con versiones anteriores o adiciones de funciones. Nos encantaría saber de usted. ¡Únete a nuestro canal comunitario Slack!
Intenté analizar SQL que debería ser válido pero falló, ¿por qué sucedió eso?
parse_one(sql, dialect="spark")
(alternativamente: read="spark"
). Si no se especifica ningún dialecto, parse_one
intentará analizar la consulta según el "dialecto SQLGlot", que está diseñado para ser un superconjunto de todos los dialectos admitidos. Si intentó especificar el dialecto y aún no funciona, presente un problema.¡Intenté generar SQL pero no está en el dialecto correcto!
parse_one(sql, dialect="spark").sql(dialect="duckdb")
(alternativamente: transpile(sql, read="spark", write="duckdb")
).Intenté analizar SQL no válido y funcionó, ¡aunque debería generar un error! ¿Por qué no validó mi SQL?
¿Qué pasó con sqlglot.dataframe?
Traduce fácilmente de un dialecto a otro. Por ejemplo, las funciones de fecha/hora varían según el dialecto y pueden ser difíciles de manejar:
import sqlglot
sqlglot . transpile ( "SELECT EPOCH_MS(1618088028295)" , read = "duckdb" , write = "hive" )[ 0 ]
' SELECT FROM_UNIXTIME(1618088028295 / POW(10, 3)) '
SQLGlot puede incluso traducir formatos de hora personalizados:
import sqlglot
sqlglot . transpile ( "SELECT STRFTIME(x, '%y-%-m-%S')" , read = "duckdb" , write = "hive" )[ 0 ]
" SELECT DATE_FORMAT(x, 'yy-M-ss') "
Los delimitadores de identificadores y los tipos de datos también se pueden traducir:
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 `
Los comentarios también se conservan según el mejor esfuerzo posible:
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 */
Puede explorar SQL con ayudas de expresión para hacer cosas como buscar columnas y tablas en una consulta:
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 )
Lea el primer manual para obtener más información sobre los aspectos internos de SQLGlot.
Cuando el analizador detecta un error en la sintaxis, genera un 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
~
Los errores de sintaxis estructurados son accesibles para uso programático:
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 posible que no sea posible traducir algunas consultas entre determinados dialectos. Para estos casos, SQLGlot puede emitir una advertencia y procederá a realizar una traducción de mejor esfuerzo de forma predeterminada:
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 '
Este comportamiento se puede cambiar configurando el atributo unsupported_level
. Por ejemplo, podemos configurarlo en RAISE
o IMMEDIATE
para garantizar que se genere una excepción en su lugar:
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
Hay consultas que requieren información adicional para ser transpilada con precisión, como los esquemas de las tablas a las que se hace referencia en ellas. Esto se debe a que ciertas transformaciones son sensibles a los tipos, lo que significa que se necesita inferencia de tipos para comprender su semántica. Aunque las reglas de optimización qualify
y annotate_types
pueden ayudar con esto, no se utilizan de forma predeterminada porque agregan una sobrecarga y complejidad significativas.
La transpilación es generalmente un problema difícil, por lo que SQLGlot emplea un enfoque "incremental" para resolverlo. Esto significa que puede haber pares de dialectos que actualmente carecen de soporte para algunas entradas, pero se espera que esto mejore con el tiempo. Apreciamos mucho los problemas o relaciones públicas bien documentados y probados, así que no dude en comunicarse si necesita orientación.
SQLGlot admite la creación incremental de expresiones 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 '
Es posible modificar un árbol analizado:
from sqlglot import parse_one
parse_one ( "SELECT x FROM y" ). from_ ( "z" ). sql ()
' SELECT x FROM z '
Las expresiones analizadas también se pueden transformar de forma recursiva aplicando una función de mapeo a cada nodo del árbol:
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 puede reescribir consultas en una forma "optimizada". Realiza una variedad de técnicas para crear un nuevo AST canónico. Este AST se puede utilizar para estandarizar consultas o proporcionar las bases para implementar un motor real. Por ejemplo:
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 )
Puede ver la versión AST del SQL analizado llamando 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 puede calcular la diferencia semántica entre dos expresiones y los cambios de salida en forma de secuencia de acciones necesarias para transformar una expresión de origen en una de destino:
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 ))),
...
]
Ver también: Diferencia semántica para SQL.
Los dialectos se pueden agregar subclasificando 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 es capaz de interpretar consultas SQL, donde las tablas se representan como diccionarios de Python. No se supone que el motor sea rápido, pero puede resultar útil para realizar pruebas unitarias y ejecutar SQL de forma nativa en objetos Python. Además, la base se puede integrar fácilmente con núcleos informáticos rápidos, como Arrow y Pandas.
El siguiente ejemplo muestra la ejecución de una consulta que involucra agregaciones y uniones:
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
Ver también: Escribir un motor Python SQL desde cero.
SQLGlot usa pdoc para entregar su documentación API.
Hay una versión alojada en el sitio web de SQLGlot, o puede compilarla localmente con:
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
Las pruebas comparativas se ejecutan en Python 3.10.12 en segundos.
Consulta | sqlglot | sqlglotrs | sqlfluff | sqltree | análisis sql | moz_sql_parser | óxido de sql |
---|---|---|---|---|---|---|---|
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) |
corto | 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) |
largo | 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) |
loco | 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 usa dateutil para simplificar expresiones literales de delta de tiempo. El optimizador no simplificará expresiones como las siguientes si no se puede encontrar el módulo:
x + interval ' 1 ' month