Otimização de Reserva de Assentos: Localizando Poltronas Consecutivas com SQL

O Desafio Técnico

Imagine o seguinte cenário: um grupo de cinco amigos deseja comprar ingressos para o cinema. O sistema precisa identificar todas as opções de cinco assentos consecutivos vazios na mesma fileira. Além de encontrar a disponibilidade, o sistema deve classificar essas opções com base em uma métrica de "melhor lugar", priorizando a proximidade com o centro da sala e uma visão otimizada (geralmente localizada a dois terços da distância da tela).

Para este problema, utilizaremos uma tabela que registra o status de ocupação de cada assento. O objetivo é resolver o problema usando uma única abordagem SQL robusta, utilizando funções de janela (Window Functions).

Estrutura dos Dados

A tabela de entrada, denominada status_assentos_cinema, possui a seguinte estrutura simplificada:

Coluna Tipo Descrição
cod_assento string Identificador do assento (Formato: 'Fila-Coluna', ex: '5-12')
esta_vendido int Status de ocupação (0: Disponível, 1: Vendido)

Critérios de Otimização

Para definir o "melhor assento", utilizaremos o cálculo da Distância Euclidiana Ponderada em relação a um ponto ideal na sala. Definimos o ponto de visualização perfeita como:

  • Linha ideal: 65% do total de fileiras (fundo da sala).
  • Coluna ideal: 50% da largura da fileira (centro).
  • Peso: Atribuiremos peso 3 para o deslocamento vertical (fileira) e peso 2 para o horizontal (coluna).

Solução em SQL (Hive/Spark SQL)

A consulta abaixo utiliza CTEs (Common Table Epxressions) para decompor o problema em etapas: extração de coordenadas, identificação de sequências e cálculo de ranqueamento.


WITH base_processada AS (
    -- Extrai coordenadas numéricas do identificador de assento
    SELECT
        cod_assento,
        CAST(SPLIT(cod_assento, '-')[0] AS INT) AS num_fila,
        CAST(SPLIT(cod_assento, '-')[1] AS INT) AS num_coluna,
        esta_vendido
    FROM dw_cinema.status_assentos_cinema
),

mapeamento_sequencias AS (
    -- Identifica janelas de 5 assentos e verifica disponibilidade
    SELECT
        cod_assento,
        num_fila,
        num_coluna,
        SUM(CASE WHEN esta_vendido = 0 THEN 1 ELSE 0 END) OVER (
            PARTITION BY num_fila 
            ORDER BY num_coluna ASC 
            ROWS BETWEEN CURRENT ROW AND 4 FOLLOWING
        ) AS contagem_vazios,
        COLLECT_LIST(cod_assento) OVER (
            PARTITION BY num_fila 
            ORDER BY num_coluna ASC 
            ROWS BETWEEN CURRENT ROW AND 4 FOLLOWING
        ) AS lista_assentos,
        MAX(num_fila) OVER () AS total_filas,
        MAX(num_coluna) OVER (PARTITION BY num_fila) AS total_colunas_fila
    FROM base_processada
),

calculo_score_conforto AS (
    -- Aplica a fórmula de distância para ranqueamento
    SELECT
        cod_assento AS assento_inicio,
        CONCAT(num_fila, '-', num_coluna, ' até ', num_fila, '-', num_coluna + 4) AS intervalo_reserva,
        lista_assentos,
        contagem_vazios,
        -- Cálculo da distância Euclidiana ponderada em relação ao ponto ideal (0.65, 0.50)
        SUM(
            (3 * ABS(num_fila - (0.65 * total_filas))) + 
            (2 * ABS(num_coluna - (0.50 * total_colunas_fila)))
        ) OVER (
            PARTITION BY num_fila 
            ORDER BY num_coluna ASC 
            ROWS BETWEEN CURRENT ROW AND 4 FOLLOWING
        ) AS score_proximidade
    FROM mapeamento_sequencias
)

SELECT
    assento_inicio,
    intervalo_reserva,
    lista_assentos,
    score_proximidade
FROM calculo_score_conforto
WHERE contagem_vazios = 5
ORDER BY score_proximidade ASC;

Geração de Dados Sintéticos (Python)

Para validar a lógica, podemos utilizar o script Python abaixo para gerar um dataset simulando um cinema com assentos aleatoriamente ocupados.


import pandas as pd
import numpy as np

# Configurações da sala
filas_total = 10
colunas_total = 20
taxa_ocupacao = 0.40

# Gerar coordenadas
dados_cinema = []
for f in range(1, filas_total + 1):
    for c in range(1, colunas_total + 1):
        dados_cinema.append({
            "cod_assento": f"{f}-{c}",
            "esta_vendido": 0
        })

df_assentos = pd.DataFrame(dados_cinema)

# Simular vendas aleatórias baseadas na frequência
indices_vendidos = np.random.choice(
    df_assentos.index, 
    size=int(taxa_ocupacao * len(df_assentos)), 
    replace=False
)
df_assentos.loc[indices_vendidos, "esta_vendido"] = 1

print(df_assentos.head(10))

Análise Técnica da Lógica

A solução utiliza a cláusula ROWS BETWEEN CURRENT ROW AND 4 FOLLOWING. Esta técnica é superior a auto-joins, pois percorre os dados em uma única passagem (Single Pass), o que é computacionalmente eficiente em ambientes de Big Data como Hive ou Spark. A verificação contagem_vazios = 5 garante que a janela não inclua assentos vendidos ou que a janela não "quebre" no final de uma fileira, onde o tamanho da janela seria naturalmente menor que 5.

Tags: SQL hive Data Analysis Window Functions Otimização

Publicado em 9-26 12:44