SQLGlot — это анализатор, транспилятор, оптимизатор и механизм SQL без каких-либо зависимостей. Его можно использовать для форматирования SQL или перевода между 24 различными диалектами, такими как DuckDB, Presto / Trino, Spark / Databricks, Snowflake и BigQuery. Он предназначен для чтения широкого спектра входных данных SQL и вывода синтаксически и семантически правильного SQL на целевых диалектах.
Это очень комплексный универсальный синтаксический анализатор SQL с надежным набором тестов. Он также весьма эффективен, хотя и написан исключительно на Python.
Вы можете легко настроить синтаксический анализатор, анализировать запросы, просматривать деревья выражений и программно создавать SQL.
Синтаксические ошибки выделяются, а о несовместимости диалектов можно предупредить или вызвать тревогу в зависимости от конфигурации. Однако SQLGlot не претендует на роль валидатора SQL, поэтому он может не обнаружить определенные синтаксические ошибки.
Дополнительные сведения о SQLGlot см. в документации API и в руководстве по дереву выражений.
Вклады в SQLGlot очень приветствуются; прочитайте руководство по вкладу и документ о регистрации, чтобы начать работу!
Из ПиПИ:
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 )
Прочтите руководство по as, чтобы узнать больше о внутреннем устройстве SQLGlot.
Когда парсер обнаруживает ошибку в синтаксисе, он выдает 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
могут помочь в этом, они не используются по умолчанию, поскольку добавляют значительные накладные расходы и сложность.
Транспиляция, как правило, представляет собой сложную задачу, поэтому SQLGlot использует «поэтапный» подход к ее решению. Это означает, что могут существовать пары диалектов, в которых в настоящее время отсутствует поддержка некоторых входных данных, но ожидается, что со временем ситуация улучшится. Мы высоко ценим хорошо документированные и проверенные проблемы или PR, поэтому не стесняйтесь обращаться, если вам нужна помощь!
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 может переписывать запросы в «оптимизированную» форму. Он выполняет различные методы для создания нового канонического 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 ))),
...
]
См. также: Семантическое различие для 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 для упрощения буквальных выражений timedelta. Оптимизатор не будет упрощать выражения, подобные приведенным ниже, если модуль не найден:
x + interval ' 1 ' month