Todos os produtos
Search
Central de documentação

Realtime Compute for Apache Flink:Joins no Flink SQL

Última atualização: Nov 03, 2026

O Flink SQL suporta joins complexos em tabelas dinâmicas com diversas semânticas de consulta e tipos de join. Evite criar um produto cartesiano, pois essa operação não tem suporte e causa falha nas consultas. O sistema não otimiza a ordem do join por padrão. Para obter melhor desempenho, liste as tabelas na cláusula FROM da menor para a maior frequência de atualização.

Visão geral dos joins

Tipo de join

Descrição

Restrições

Regular joins

O regular join é o tipo mais versátil. Novos registros ou alterações em qualquer lado do join ficam visíveis e afetam todo o resultado.

A sintaxe do regular join é flexível e aceita operações INSERT, UPDATE e DELETE nas tabelas de entrada. No entanto, esse join mantém os dados de entrada de ambos os lados em seu estado, que pode crescer indefinidamente. Para evitar tamanho excessivo de estado, defina um tempo de vida (TTL) do estado, mas isso pode comprometer a precisão dos resultados.

Interval joins

O interval join retorna o produto cartesiano de registros que atendem tanto à condição de join quanto às restrições de tempo.

Exige pelo menos uma condição de igualdade e uma condição de limite de tempo aplicável a ambos os lados. O intervalo de tempo pode ser definido como uma única condição (como <, <=, >= ou >), usando uma condição BETWEEN , ou como uma condição de igualdade em atributos de tempo do mesmo tipo (processing time ou event time) em ambas as tabelas.

Temporal joins

O temporal join associa um stream a uma tabela versionada. Ele usa event time ou processing time para correlacionar dados à versão correta da tabela em um ponto específico no tempo.

Ambas as tabelas devem usar a mesma semântica de tempo (processing time ou event time). Atenção ao ciclo de vida do resultado do join: a condição geralmente se baseia em um timestamp específico.

Lookup joins

O lookup join normalmente enriquece uma tabela com dados consultados de um sistema externo. Esse join exige que uma tabela tenha um atributo de processing time e que a outra seja apoiada por uma tabela de dimensões.

Uma tabela deve ter um atributo de processing time, enquanto a outra precisa usar um conector source de lookup. Também é necessária uma condição de igualdade entre as duas tabelas.

Lateral joins

O lateral join conecta uma tabela à saída de uma função com valor de tabela. Cada linha da tabela à esquerda é associada a todas as linhas produzidas pela chamada correspondente à função.

Requer uma condição constante ON TRUE na cláusula, como JOIN LATERAL TABLE(table_func(order_id)) t(res) ON TRUE.

Regular joins

Os quatro tipos comuns de regular joins são:

  • INNER JOIN: Retorna registros que satisfazem a condição de join em ambas as tabelas (interseção).

  • LEFT JOIN: Retorna todos os registros da tabela à esquerda, mesmo sem correspondência na tabela à direita (preserva a tabela esquerda).

  • RIGHT JOIN: Retorna todos os registros da tabela à direita, mesmo sem correspondentes na tabela à esquerda (preserva a tabela direita).

  • FULL OUTER JOIN: Retorna a união das duas tabelas, incluindo registros correspondentes e não correspondentes.

Diagrama do regular join

image

Exemplo de regular join

  1. Faça login no console do Realtime Compute for Apache Flink.

  2. Na lista de workspaces, localize o workspace desejado e clique em Console na coluna Actions.

  3. No painel de navegação à esquerda, clique em Development > ETL.

  4. Clique no ícone image e, em seguida, clique em New Blank Stream Draft. Insira um name, selecione uma engine version e clique em Create.

    Este exemplo mostra como usar um join para correlacionar linhas entre tabelas, associando codinomes de super-heróis aos seus nomes reais.
    CREATE TEMPORARY TABLE NOC (
      agent_id STRING,
      codename STRING
    )
    WITH (
      'connector' = 'faker',   -- The Faker connector generates simulated data.
      'fields.agent_id.expression' = '#{regexify ''(1|2|3|4|5){1}''}',  -- Generates a random number from 1 to 5.
      'fields.codename.expression' = '#{superhero.name}',   -- A built-in Faker function that randomly generates a superhero name.
      'number-of-rows' = '10'   -- Specifies that 10 rows of data are generated.
    );
    
    CREATE TEMPORARY TABLE RealNames (
      agent_id STRING,
      name     STRING
    )
    WITH (
      'connector' = 'faker',
      'fields.agent_id.expression' = '#{regexify ''(1|2|3|4|5){1}''}',
      'fields.name.expression' = '#{Name.full_name}',  -- A built-in Faker function that randomly generates a name.
      'number-of-rows' = '10'
    );
    
    SELECT
        name,
        codename
    FROM NOC
    INNER JOIN RealNames ON NOC.agent_id = RealNames.agent_id;  -- If the agent_id (1-5) in both tables is equal, the name and codename are returned.
  5. No canto superior direito, clique em Debug, selecione um cluster de depuração e clique em OK. Caso não tenha um session cluster, consulte Create a session cluster.

    Após a conclusão da depuração, o resultado será uma tabela com duas colunas, name e codename, exibindo os dados de saída do Regular Join.

Para mais informações sobre como usar regular joins, consulte Regular join statements.

Interval joins

Um interval join associa registros de dois streams que ocorrem dentro de um intervalo de tempo específico. Esse tipo de join geralmente correlaciona eventos de dois streams próximos temporalmente.

Diagrama do interval join

image

Exemplo de interval join

Este exemplo associa pedidos a envios. Ele filtra os resultados para incluir apenas pedidos enviados até três horas após a realização do pedido.

Crie um rascunho ETL conforme descrito em Regular joins.

CREATE TEMPORARY TABLE orders (
  id INT,
  order_time AS TIMESTAMPADD(HOUR, CAST(FLOOR(RAND()*(1-5+1)+5)*(-1) AS INT), CURRENT_TIMESTAMP)    -- Randomly gets a time from 2, 3, or 4 hours ago based on the local time.
)
WITH (
  'connector' = 'datagen',     -- The datagen connector can periodically generate random data.
  'rows-per-second'='10',      -- The rate of random data generation, 10 rows/s.
  'fields.id.kind'='sequence', -- The sequence generator.
  'fields.id.start'='1',       -- The sequence starts at 1.
  'fields.id.end'='100'        -- The sequence ends at 100.
);

CREATE TEMPORARY TABLE shipments (
  order_id INT,
  shipment_time AS TIMESTAMPADD(HOUR, CAST(FLOOR(RAND()*(1-5+1))+1 AS INT), CURRENT_TIMESTAMP)   -- Randomly gets a time from 0, 1, or 2 hours ago based on the local time.
)
WITH (
  'connector' = 'datagen',
  'rows-per-second'='5',
  'fields.order_id.kind'='sequence',
  'fields.order_id.start'='1',
  'fields.order_id.end'='100'
);

SELECT
  o.id AS order_id,
  o.order_time,
  s.shipment_time,
  TIMESTAMPDIFF(HOUR,o.order_time,s.shipment_time) AS hour_diff    -- The time difference between order_time and shipment_time.
FROM orders o
JOIN shipments s ON o.id = s.order_id   
WHERE 
    o.order_time BETWEEN s.shipment_time - INTERVAL '3' HOUR AND s.shipment_time;  -- Filters for orders placed within three hours before the shipment time.

No canto superior direito, clique em Debug e, em seguida, clique em OK. Os resultados da depuração são os seguintes.

image

Para mais informações sobre interval joins, consulte Interval join statements.

Temporal joins

O temporal join é utilizado com tabelas dinâmicas, cujo conteúdo muda ao longo do tempo. Ele associa um stream de eventos à versão da tabela dinâmica válida no momento de cada evento.

Diagrama do temporal join

image

Exemplo de temporal join

Este exemplo demonstra um cenário de negócios em que as taxas de câmbio variam em diferentes períodos e os pedidos devem ser calculados usando a taxa vigente no momento da transação.

(Opcional) Gerar dados simulados

  1. Crie um rascunho ETL conforme descrito em Regular joins.

  1. Use o conector Faker para gerar dados simulados e gravá-los no Kafka como uma tabela dinâmica de taxas de câmbio usando o conector Upsert Kafka.

CREATE TEMPORARY TABLE currency_rates (
  `currency_code` STRING,
  `eur_rate` DECIMAL(6,4),
  `rate_time` TIMESTAMP(3),
  WATERMARK FOR `rate_time` AS rate_time - INTERVAL '15' SECOND,
  PRIMARY KEY (currency_code) NOT ENFORCED
) WITH (
  'connector' = 'upsert-kafka',
  'topic' = 'currency_rates',
  'properties.bootstrap.servers' = '${secret_values.kafkahost}',
  'key.format' = 'raw',
  'value.format' = 'json'
);

CREATE TEMPORARY TABLE transactions (
  `id` STRING,
  `currency_code` STRING,
  `total` DECIMAL(10,2),
  `transaction_time` TIMESTAMP(3),
  WATERMARK FOR `transaction_time` AS transaction_time - INTERVAL '30' SECOND
) WITH (
  'connector' = 'kafka',
  'topic' = 'transactions',
  'properties.bootstrap.servers' = '${secret_values.kafkahost}',
  'key.format' = 'raw',
  'key.fields' = 'id',
  'value.format' = 'json'
);

CREATE TEMPORARY TABLE currency_rates_faker (
  `currency_code` STRING,
  `eur_rate` DECIMAL(6,4),
  `rate_time` TIMESTAMP(3)
)
WITH (
  'connector' = 'faker',
  'fields.currency_code.expression' = '#{Currency.code}',
  'fields.eur_rate.expression' = '#{Number.randomDouble ''4'',''0'',''10''}',
  'fields.rate_time.expression' = '#{date.past ''15'',''SECONDS''}',
  'rows-per-second' = '2'
);

CREATE TEMPORARY TABLE transactions_faker (
  `id` STRING,
  `currency_code` STRING,
  `total` DECIMAL(10,2),
  `transaction_time` TIMESTAMP(3)
)
WITH (
  'connector' = 'faker',
  'fields.id.expression' = '#{Internet.UUID}',
  'fields.currency_code.expression' = '#{Currency.code}',
  'fields.total.expression' = '#{Number.randomDouble ''2'',''10'',''1000''}',
  'fields.transaction_time.expression' = '#{date.past ''30'',''SECONDS''}',
  'rows-per-second' = '2'
);

BEGIN STATEMENT SET;

INSERT INTO currency_rates
SELECT * FROM currency_rates_faker;

INSERT INTO transactions
SELECT * FROM transactions_faker;

END;
  1. No canto superior direito, clique em Deploy para implantar o job.

  2. No painel de navegação à esquerda, clique em O&M > Deployments. Localize o job desejado, clique em Start na coluna Actions, selecione Initial Mode e clique em Start.

Crie um novo rascunho ETL conforme descrito na seção Regular joins para ler os dados simulados durante a depuração.

CREATE TEMPORARY TABLE currency_rates (
  `currency_code` STRING,
  `eur_rate` DECIMAL(6,4),
  `rate_time` TIMESTAMP(3),
  WATERMARK FOR `rate_time` AS rate_time - INTERVAL '15' SECOND,
  PRIMARY KEY (currency_code) NOT ENFORCED
) WITH (
  'connector' = 'upsert-kafka',
  'topic' = 'currency_rates',
  'properties.bootstrap.servers' = '${secret_values.kafkahost}',
  'properties.auto.offset.reset' = 'earliest',
  'properties.group.id' = 'currency_rates',
  'key.format' = 'raw',
  'value.format' = 'json'
);

CREATE TEMPORARY TABLE transactions (
  `id` STRING,
  `currency_code` STRING,
  `total` DECIMAL(10,2),
  `transaction_time` TIMESTAMP(3),
  WATERMARK FOR `transaction_time` AS transaction_time - INTERVAL '30' SECOND
) WITH (
  'connector' = 'kafka',
  'topic' = 'transactions',
  'properties.bootstrap.servers' = '${secret_values.kafkahost}',
  'properties.auto.offset.reset' = 'earliest',
  'properties.group.id' = 'transactions',
  'key.format' = 'raw',
  'key.fields' = 'id',
  'value.format' = 'json'
);

SELECT
  t.id,
  t.total * c.eur_rate AS total_eur,
  c.eur_rate,
  t.total,
  c.currency_code,
  c.rate_time,
  t.transaction_time
FROM transactions t
JOIN currency_rates FOR SYSTEM_TIME AS OF t.transaction_time AS c
ON t.currency_code = c.currency_code 
;

No canto superior direito, clique em Debug e, em seguida, clique em OK.

A taxa de câmbio mudou duas vezes, às 20:16:11 e às 20:35:22. Uma transação ocorreu às 20:35:14. Nesse momento, a taxa de câmbio ainda não havia sido atualizada. Portanto, o cálculo dessa transação deve usar a taxa de câmbio que entrou em vigor às 20:16:11.

Lookup joins

Um lookup join enriquece um stream de dados com dados estáticos ou de mudança lenta provenientes de um sistema externo. Por exemplo, é possível associar um stream de pedidos em tempo real a dados de referência, como informações de produtos armazenadas em um banco de dados relacional. Esse join exige que uma tabela tenha um atributo de processing time e que a outra esteja conectada por meio de um conector de lookup, como o conector MySQL.

Diagrama do lookup join

image
Nota
  • Inclua FOR SYSTEM_TIME AS OF PROCTIME() para associar cada registro do stream ao snapshot da tabela de dimensões naquele momento. Se os dados da tabela de dimensões mudarem posteriormente, os resultados do join não serão afetados.

  • A condição ON deve incluir uma condição de igualdade em um campo que a tabela de dimensões possa usar para lookups.

Exemplo de lookup join

Este exemplo mostra como enriquecer dados de pedidos com dados estáticos de um conector externo para adicionar nomes de produtos.

Crie um rascunho ETL conforme descrito em Regular joins.

CREATE TEMPORARY TABLE orders ( 
    order_id STRING,
    product_id INT,
    order_total INT
) WITH (
  'connector' = 'faker',   -- The Faker connector generates simulated data.
  'fields.order_id.expression' = '#{Internet.uuid}',   -- Generates a random UUID.
  'fields.product_id.expression' = '#{number.numberBetween ''1'',''5''}',   -- Generates a random number from 1 to 5.
  'fields.order_total.expression' = '#{number.numberBetween ''1000'',''5000''}',  -- Generates a random number from 1000 to 5000.
  'number-of-rows' = '10'  -- The number of data rows to generate.
);

-- Connect to static product data in MySQL.
CREATE TEMPORARY TABLE products (
 product_id INT,
 product_name STRING
)
WITH(
  'connector' = 'mysql',
  'hostname' = '${secret_values.mysqlhost}',
  'port' = '3306',
  'username' = '${secret_values.username}',
  'password' = '${secret_values.password}',
  'database-name' = 'db2024',
  'table-name' = 'products'
);

SELECT 
    o.order_id,
    p.product_name,
    o.order_total,
  CASE 
    WHEN o.order_total > 3000 THEN 1    
    ELSE 0
  END AS is_importance          -- Adds the is_importance field. The value is 1 if the order total exceeds 3000, indicating an important order.
FROM orders o
JOIN products FOR SYSTEM_TIME AS OF PROCTIME() AS p -- The FOR SYSTEM_TIME AS OF PROCTIME() clause ensures that as the join operator processes rows from `orders`, each row from `orders` is joined with the `products` rows that match the join condition.
ON o.product_id = p.product_id;

A tabela products contém 5 registros de exemplo: 1-cola, 2-orange, 3-milk, 4-tea, 5-coffee.

No canto superior direito, clique em Debug e, em seguida, clique em OK. Os resultados da depuração são os seguintes.

image

Para mais informações sobre lookup joins, consulte JOIN statements for dimension tables.

Lateral joins

Um lateral join utiliza uma subconsulta na cláusula FROM executada para cada linha da consulta externa. Isso pode melhorar a flexibilidade e o desempenho da consulta ao reduzir varreduras de tabelas. Contudo, essa operação pode causar degradação de desempenho se a consulta interna for complexa ou processar um grande volume de dados.

image

Exemplo de lateral join

Este exemplo agrega registros de vendas para identificar os três produtos com maior quantidade total vendida.

Crie um rascunho ETL conforme descrito em Regular joins.

CREATE TEMPORARY TABLE sale (
  sale_id STRING,
  product_id INT,
  sale_num INT
)
WITH (
  'connector' = 'faker',    -- The Faker connector generates simulated data.
  'fields.sale_id.expression' = '#{Internet.uuid}',   -- Generates a random UUID.
  'fields.product_id.expression' = '#{regexify ''(1|2|3|4|5){1}''}',   -- Generates a random number from 1 to 5.
  'fields.sale_num.expression' = '#{number.numberBetween ''1'',''10''}',  -- Generates a random integer from 1 to 10.
  'number-of-rows' = '50'   -- Generates 50 rows of data.
);

CREATE TEMPORARY TABLE products (
 product_id INT,
 product_name STRING,
 PRIMARY KEY(product_id) NOT ENFORCED
)
WITH(
  'connector' = 'mysql',
  'hostname' = '${secret_values.mysqlhost}',
  'port' = '3306',
  'username' = '${secret_values.username}',
  'password' = '${secret_values.password}',
  'database-name' = 'db2024',
  'table-name' = 'products'
);

SELECT 
    p.product_name,
    s.total_sales
FROM  products p
LEFT JOIN LATERAL
    (SELECT SUM(sale_num) AS total_sales FROM sale WHERE sale.product_id = p.product_id) s ON TRUE
    ORDER BY total_sales DESC
    LIMIT 3;

No canto superior direito, clique em Debug e, em seguida, clique em OK. Os resultados da depuração são os seguintes.

image

Documentos relacionados