SQL является основным способом взаимодействия с реляционными базами данных, такими как PostgreSQL. При создании API, REST или GraphQL, для базы данных PostgreSQL необходимо знать SQL для получения и передачи данных. Часто разработчикам помогают инструменты ORM (Object Relational Mapping) для взаимодействия с базой данных, и эти инструменты абстрагируют некоторые знания SQL, необходимые для создания API.
Даже если эти инструменты помогут вам начать работу, они не помогут вам создать эффективный SQL. Вы будете не первым разработчиком, написавшим неэффективный запрос, который перегружает базу данных или выводит ее из строя. В StepZen мы создали инструменты и сервисы для декларативного создания GraphQL API, включая улучшение способов подключения и взаимодействия с базами данных. Это снимает часть трудностей, связанных с написанием эффективных SQL-запросов.
В этой статье я покажу, как автоматически генерировать GraphQL API для вашей базы данных PostgreSQL и соединять данные декларативно, используя только GraphQL SDL. Если вы думали, что SQL уже был декларативным, думаю, вы оцените пользовательские директивы, которые есть в StepZen.
Создание GraphQL API из PostgreSQL
С помощью StepZen вы можете создать GraphQL API для любого источника данных, включая базы данных PostgreSQL. Если у вас уже есть база данных, вы можете импортировать ее схему в GraphQL API с помощью StepZen CLI. В противном случае вы можете использовать учетные данные из нашей демонстрационной базы данных на этой странице.
Подключитесь к базе данных с помощью psql
из терминала/командной строки:
`psql -h postgresql.introspection.stepzen.net -d introspection -U testUserIntrospection`
С паролем: HurricaneStartingSample1934
.
После подключения вы сможете общаться с базой данных PostgreSQL. Используя команду dt
, вы сможете просмотреть таблицы, которые есть в этой базе данных:
```
{% endraw %}
bash
List of relations
Schema | Name | Type | Owner
--------+-----------------+-------+----------
public | address | table | postgres
public | customer | table | postgres
public | customeraddress | table | postgres
public | lineitem | table | postgres
public | order | table | postgres
public | product | table | postgres
(6 rows)
{% raw %}
As you can see in the result above, the database has six tables. We can query every table using SQL or get the data from this database with StepZen. Therefore we first need to generate a GraphQL API based on introspection of this database.
To generate a GraphQL API for this database, you need to install the StepZen CLI:
```bash
`npm i -g stepzen`
И выполнить команду:
`stepzen import postgresql`
CLI запросит учетные данные базы данных, которые вы можете скопировать из нашего примера начала работы, если у вас нет собственной базы данных PostgreSQL.
Примечание: CLI спросит, хотите ли вы связать типы с помощью
@materializer
. Выберите здесь «да».
Когда CLI закончит импортировать вашу схему PostgreSQL и создаст схему GraphQL, вы найдете новый файл postgresql/index.graphql
. Этот файл содержит схему GraphQL для вашей базы данных и содержит объявления типов и набор операций. Чтобы развернуть схему GraphQL и создать API, вы можете выполнить команду stepzen start
.
После этого StepZen развернет схему GraphQL и вернет вашу конечную точку прямо в терминале.
Продолжим в следующем разделе, где мы рассмотрим объединение данных из разных таблиц в схеме GraphQL.
Соединение таблиц с помощью @materializer
Схема GraphQL, созданная для базы данных PostgreSQL путем выполнения команды stepzen import postgresql
, уже содержит запрос, который объединяет данные из разных таблиц базы данных. Допустим, вы хотите получить адрес клиента, который хранится в отдельной таблице; вам нужно объединить разные таблицы в SQL.
При использовании SQL для получения клиента с идентификатором 1
и адреса этого клиента необходимо написать SQL-запрос следующим образом:
`SELECT T."city", T."countryregion", T."id", T."postalcode", T."stateprovince", T."street" FROM "public"."address" T, "public"."customeraddress" V WHERE V."customerid" = 1 AND V."addressid" = T."id"`
Этот SQL-запрос объединяет данные из таблиц customer
и customeraddress
.
В StepZen вы можете использовать этот же «сырой» SQL-запрос для создания GraphQL-запроса для объединения этих таблиц. Но вы также можете объединить эти данные более декларативно, не составляя SQL-запрос. Например, при использовании GraphQL-запроса getCustomer,
StepZen получает информацию из таблицы
customers, как показано в директиве @dbquery
ниже:
getCustomer(id: Int!): Customer
@dbquery(
type: "postgresql"
schema: "public"
table: "customer"
configuration: "postgresql_config"
)
Но посмотрите на его тип ответа Customer.
Обратите внимание, что запрашиваемые поля содержат информацию об адресе и заказах, сделанных этим клиентом. Тип ответа Customer
содержит соединения с этими таблицами, используя пользовательскую директиву @materializer
.
type Customer {
addressList: [Address] @materializer(query: "getAddressUsingCustomeraddress")
email: String!
id: Int!
name: String!
orderList: [Order] @materializer(query: "getOrderUsingCustomerid")
}
Когда вы делаете запрос getCustomer
, StepZen сначала получает информацию из таблицы
клиентов. Когда вы включаете поля addressList
или orderList
, он использует GraphQL-запросы, связанные в @materializer
, чтобы получить данные для этих полей.
Следующий GraphQL-запрос получает данные для полей id
и email
из таблицы customer
в базе данных PostgreSQL; и данные для addressList
путем выполнения запроса getAddressUsingCustomerid
. Значение поля Customer.id
передается в этот запрос в качестве аргумента.
{
getCustomer(id: "1") {
id
email
addressList {
street
city
}
}
}
Запрос getAddressUsingCustomerid
использует необработанный SQL-запрос для получения адреса на основе идентификатора клиента. Это просто, поскольку таблица customeraddress
содержит поле customerid
:
getAddressUsingCustomerid(id: Int!): [Address]
@dbquery(
type: "postgresql"
query: """
SELECT T."city", T."countryregion", T."id", T."postalcode", T."stateprovince", T."street"
FROM "public"."address" T, "public"."customeraddress" V
WHERE V."customerid" = $1
AND V."addressid" = T."id"
"""
configuration: "postgresql_config"
)
Помимо включения необработанного SQL-запроса или использования @materializer
вы также можете использовать другую пользовательскую директиву (@sequence
) для выполнения последовательности запросов и сбора результатов, как вы увидите в следующем разделе.
Сбор данных с помощью @sequence
Иногда данные, которые вы хотите объединить, находятся не непосредственно в другой таблице, а в таблице, связанной через таблицу, содержащую поля, которые вы хотите объединить. Предположим, вы хотите объединить таблицы order
и product
из базы данных, которую мы используем в этой статье; вы увидите, что ни в одной из таблиц нет ссылки на другую таблицу. Вместо этого база данных содержит таблицу lineitem
, которая включает только поля orderid
и productid
.
Вы можете использовать эту таблицу для объединения данных в необработанном SQL-запросе, чтобы получить информацию о продукте для заказа. И связать ее с новым запросом GraphQL:
getProductsUsingOrderid(orderid: Int!): [Product]
@dbquery(
type: "postgresql"
query: """
SELECT T."id", T."title", T."description", T."image" FROM "public"."product" T, "public"."lineitem" V WHERE V."orderid" = $1 AND T."id" = V."productid"
"""
configuration: "postgresql_config"
)
Этот запрос можно связать с типом Order
с помощью директивы @materializer
. Таким образом, вы можете добавлять информацию о товаре в заказы. Но вы можете сделать то же самое без написания необработанного SQL для соединения таблиц lineitem
и product
.
Вместо этого вы можете использовать пользовательскую директиву @sequence
. С помощью @sequence
вы можете выполнять запросы пошагово и собирать результаты:
graphql
getProductsUsingOrderid(id: Int!): [Product].
@sequence(
шаги: [
{ query: «getLineitemUsingOrderid» }
{ запрос: «getProduct», аргументы: [{ { name: «id», field: «productid» }] }
]
)
Приведенный выше запрос getLineitemUsingOrderid
сначала выполняет запрос getLineitemUsingOrderid
и получает идентификаторы товаров для заказа. Эти идентификаторы передаются в запрос getProduct
для сбора информации о продукте. Таким образом, вы можете получить данные о продукте без написания SQL-запроса для объединения данных из разных таблиц базы данных.
С помощью этой последовательности можно сделать и многое другое. Допустим, вы хотите ограничить количество полей, возвращаемых новым запросом GraphQL. Тогда вы можете использовать collect
GraphQL-запрос, чтобы собрать только те поля, которые вы хотите раскрыть:
collect(
id: Int!
title: String
): Product @connector(type: "echo")
getProductsUsingOrderid(id: Int!): [Product]
@sequence(
steps: [
{ query: "getLineitemUsingOrderid" }
{ query: "getProduct", arguments: [{ name: "id", field: "productid" }] }
{ query: "collect" }
]
)
Благодаря добавлению этого третьего шага, поля, раскрываемые таблицей product
, теперь ограничены только id
и title
. Вы также можете использовать этот запрос collect
для получения данных из любых других шагов в @sequence
.
Заключение
В этой статье вы узнали, как объединять данные из разных таблиц PostgreSQL без необходимости писать необработанные SQL-запросы. Вместо этого вы можете использовать пользовательские директивы StepZen для декларативного построения и объединения GraphQL API. Мы будем рады услышать, какой проект вы начнете создавать с помощью StepZen и PostgreSQL. Присоединяйтесь к нашему Discord, чтобы быть в курсе новостей нашего сообщества.