# Python vs. PL/SQL vs. Shell: quando a escolha técnica impacta o negócio

## Introdução

Ao longo da minha trajetória trabalhando com Oracle Database, administração de ambientes críticos e plataformas de dados, percebi que muitos problemas técnicos não estão necessariamente relacionados à tecnologia utilizada, mas à forma como escolhemos utilizá-la.

Um exemplo interessante são os arrays. Python utiliza listas e outras estruturas; PL/SQL dispõe de VARRAY, nested tables e associative arrays; e Bash oferece arrays para automações. Todas permitem organizar e manipular múltiplos valores, mas suas regras e seus ambientes de execução são diferentes.

**A tese deste artigo:** a tecnologia define como podemos processar dados; a arquitetura determina onde faz sentido processá-los; e a gestão deve garantir que essa escolha seja eficiente, segura e sustentável.

Vamos partir do mesmo conjunto de identificadores de discos — `sdb`, `sdc`, `sdd`, `sde` e `sdf` — e avançar da sintaxe para decisões reais de engenharia.

## 1\. O que é um array — e o que não é?

No Python, `list` é uma sequência mutável e dinâmica, que pode misturar tipos. `tuple` é uma sequência imutável; `dict` associa chaves a valores; `array.array` oferece armazenamento tipado; e NumPy fornece arrays multidimensionais especializados (biblioteca externa). Essas estruturas não são intercambiáveis.

No PL/SQL, **collection** engloba VARRAY, nested table e associative array. Cada tipo tem semântica própria de limites, índices e interação com SQL.

No Bash, arrays indexados e associativos armazenam valores textuais usados em automações. Não são equivalentes a tabelas relacionais do Oracle.

## 2\. Python: lista dinâmica e índice zero

```python
discos = ["sdb", "sdc", "sdd", "sde", "sdf"]
print(discos[0])
print(len(discos))
discos.append("sdg")
for indice, disco in enumerate(discos):
    print(indice, disco)
```

**Saída esperada:**

```text
sdb
5
0 sdb
1 sdc
2 sdd
3 sde
4 sdf
5 sdg
```

A lista cresce sem limite máximo declarado (sujeita à memória disponível). O índice inicial é zero; acessar uma posição inexistente provoca `IndexError`.

Para demonstrar a diferença entre lista e array tipado:

```python
from array import array
numeros = array("i", [10, 20, 30])
print(numeros[0])  # 10
# numeros.append("texto")  # TypeError: valor incompatível
```

## 3\. PL/SQL: VARRAY com tipo e capacidade definidos

```sql
SET SERVEROUTPUT ON
DECLARE
    TYPE t_discos IS VARRAY(10) OF VARCHAR2(10);
    v_discos t_discos := t_discos('sdb','sdc','sdd','sde','sdf');
BEGIN
    DBMS_OUTPUT.PUT_LINE(v_discos(1));
    DBMS_OUTPUT.PUT_LINE(v_discos.COUNT);
    v_discos.EXTEND;
    v_discos(v_discos.LAST) := 'sdg';
    FOR i IN 1 .. v_discos.COUNT LOOP
        DBMS_OUTPUT.PUT_LINE(i || ' ' || v_discos(i));
    END LOOP;
END;
/
```

**Saída esperada:**

```text
sdb
5
1 sdb
2 sdc
3 sdd
4 sde
5 sdf
6 sdg
PL/SQL procedure successfully completed.
```

O VARRAY inicia em 1 e respeita o limite declarado de 10 elementos. Não se pode acrescentar indefinidamente com `EXTEND`.

### Armadilha de indexação: Python x VARRAY

Em Python, `discos[0]` acessa o primeiro elemento. No VARRAY PL/SQL, `v_discos(0)` não é um índice válido e gera `ORA-06532: Subscript outside of limit` para a coleção inicializada acima.

**Atenção:** isso **não** significa que todo array PL/SQL começa em 1. Associative arrays indexados por `PLS_INTEGER` podem utilizar 0 e até índices negativos.

## 4\. Outras coleções PL/SQL: índices esparsos e semântica

### Associative array

```sql
SET SERVEROUTPUT ON
DECLARE
    TYPE t_discos IS TABLE OF VARCHAR2(10) INDEX BY PLS_INTEGER;
    v_discos t_discos;
    i PLS_INTEGER;
BEGIN
    v_discos(-1) := 'sdb';
    v_discos(0) := 'sdc';
    v_discos(30) := 'sdd';
    i := v_discos.FIRST;
    WHILE i IS NOT NULL LOOP
        DBMS_OUTPUT.PUT_LINE(i || ' ' || v_discos(i));
        i := v_discos.NEXT(i);
    END LOOP;
END;
/
```

**Saída esperada:**

```text
-1 sdb
0 sdc
30 sdd
```

A navegação com `FIRST` e `NEXT` é apropriada quando os índices não são contíguos.

### Nested table

```sql
SET SERVEROUTPUT ON
DECLARE
    TYPE t_discos IS TABLE OF VARCHAR2(10);
    v_discos t_discos := t_discos('sdb','sdc','sdd');
    i PLS_INTEGER;
BEGIN
    v_discos.DELETE(2);
    i := v_discos.FIRST;
    WHILE i IS NOT NULL LOOP
        DBMS_OUTPUT.PUT_LINE(i || ' ' || v_discos(i));
        i := v_discos.NEXT(i);
    END LOOP;
END;
/
```

### Collection não é tabela física

Uma collection PL/SQL pode existir somente na memória da sessão ou do bloco. Ela não cria automaticamente uma tabela persistente. Nested tables e VARRAYs definidos como tipos SQL podem participar de operações SQL conforme as regras do Oracle. Associative arrays possuem restrições próprias nessa interface, especialmente quando os tipos são declarados apenas em PL/SQL.

## 5\. Comparação técnica

| Critério | Python list | PL/SQL VARRAY | PL/SQL Nested Table | PL/SQL Associative Array | Bash indexado |
| --- | --- | --- | --- | --- | --- |
| Índice inicial típico | 0 | 1 | 1 | Definido no código | 0 |
| Limite declarado | Não | Sim | Não | Não | Não |
| Índices esparsos | Não | Não | Sim após exclusões | Sim | Sim |
| Tipo dos elementos | Pode ser misto | Declarado | Declarado | Declarado | Texto |
| Aplicação | Aplicações e pipelines | Coleções limitadas | Coleções e integração SQL | Mapas e processamento procedural | Automação operacional |

**Critério de engenharia:** não escolha a estrutura apenas por familiaridade. Considere tipo de acesso, tamanho, mutabilidade, restrições de memória e integração com o ambiente.

## 6\. Processamento em lote: quando collections deixam de ser apenas didáticas

O maior ganho de uma collection PL/SQL pode estar na interação com SQL. O `FORALL` permite enviar operações DML em lote ao mecanismo SQL, reduzindo trocas de contexto em comparação com um loop que executa uma instrução DML por iteração. Isso não garante que `FORALL` seja melhor que uma única instrução SQL em conjunto.

**Laboratório isolado (cria tabela de teste):**

```sql
CREATE TABLE lab_array_lote (
    id NUMBER PRIMARY KEY,
    descricao VARCHAR2(50)
);

DECLARE
    TYPE t_ids IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
    v_ids t_ids;
BEGIN
    FOR i IN 1 .. 1000 LOOP
        v_ids(i) := i;
    END LOOP;

    FORALL i IN INDICES OF v_ids
        INSERT INTO lab_array_lote (id, descricao)
        VALUES (v_ids(i), 'Registro ' || v_ids(i));

    DBMS_OUTPUT.PUT_LINE('Linhas inseridas: ' || SQL%ROWCOUNT);
    ROLLBACK; -- não mantém as linhas do laboratório
END;
/

DROP TABLE lab_array_lote PURGE;
```

Em Python, `python-oracledb` disponibiliza `cursor.executemany()` para enviar operações em lote. Os dois mecanismos não são idênticos: Python executa fora do banco e pode incorrer em custos de rede, enquanto `FORALL` executa no ambiente PL/SQL. A comparação deve considerar arquitetura e workload.

## 7\. Shell Script: arrays para automação operacional

```bash
#!/usr/bin/env bash
discos=("sdb" "sdc" "sdd" "sde" "sdf")
printf 'Primeiro: %s\n' "${discos[0]}"
printf 'Quantidade inicial: %s\n' "${#discos[@]}"
discos+=("sdg")
for indice in "${!discos[@]}"; do
    printf '%s %s\n' "$indice" "${discos[$indice]}"
done
```

**Saída esperada:**

```text
Primeiro: sdb
Quantidade inicial: 5
0 sdb
1 sdc
2 sdd
3 sde
4 sdf
5 sdg
```

### Exemplo operacional com lsblk

```bash
#!/usr/bin/env bash
mapfile -t discos < <(lsblk -dn -o NAME,TYPE | awk '$2 == "disk" {print $1}')
printf 'Discos detectados: %s\n' "${#discos[@]}"
for disco in "${discos[@]}"; do
    printf '/dev/%s\n' "$disco"
done
```

Esse script somente lista dispositivos detectados. **Não identifica discos livres para ASM, LVM ou uso em produção**: é necessário validar multipath, assinaturas, mapeamento e utilização atual.

## 8\. Da sintaxe à arquitetura: onde processar um milhão de registros?

Imagine uma rotina que lê um milhão de linhas no Oracle, aplica transformações e grava resultados. Três papéis podem coexistir:

- **Oracle SQL/PL/SQL:** filtrar, agregar e atualizar dados próximos ao armazenamento. Preferir SQL em conjunto quando adequado; utilizar `BULK COLLECT` e `FORALL` para processamento procedural em lote, com controle de memória.
- **Python:** integrar serviços, aplicar bibliotecas especializadas e mover dados quando necessário, usando lotes e observando latência e consumo de memória.
- **Bash:** iniciar processos, controlar parâmetros, capturar status, registrar logs e integrar o agendamento operacional.

```text
Bash (orquestração e controle)
            |
            v
Python (integração e validação)
            |
            v
Oracle SQL / PL/SQL (processamento próximo aos dados)
            |
            v
Métricas, logs e auditoria
```

É um **fluxo ilustrativo**, não uma sequência obrigatória. Às vezes o Python deve apenas disparar uma operação SQL, sem extrair o conjunto inteiro para a aplicação.

### Quando uma decisão técnica se torna uma decisão de gestão

Em ambientes corporativos, uma decisão aparentemente simples, como escolher onde processar uma coleção, pode produzir impactos que vão além do código.

Transferir milhões de linhas para Python e depois devolvê-las ao Oracle pode ser necessário por requisitos de integração. Mas, se toda a transformação puder ser resolvida por uma operação SQL eficiente, a movimentação adicional merece ser questionada.

Além do tempo de execução, a liderança técnica precisa considerar consumo de recursos, segurança, mecanismos de recuperação, observabilidade e capacidade da equipe de sustentar a solução.

**Uma solução funcional não é necessariamente a solução mais eficiente, econômica ou sustentável para a organização.**

## 9\. Matriz de decisão: técnica, arquitetura e gestão

| Dimensão | Pergunta de decisão | Indicador possível |
| --- | --- | --- |
| Performance | Onde o processamento gera menor custo total? | Tempo, throughput, CPU |
| Infraestrutura | É necessário transferir grandes volumes? | Bytes trafegados, memória |
| Resiliência | Como reprocessar após falha? | Tempo de recuperação, falhas |
| Segurança | Onde os dados sensíveis circulam? | Acessos, auditoria, exposição |
| Operação | Como detectar e diagnosticar incidentes? | Logs, alertas, MTTR |
| Pessoas | A equipe consegue manter a solução? | Esforço, dependência, treinamento |
| Custos | Qual o custo por execução? | Consumo e custo estimado |

Essa matriz não escolhe automaticamente a linguagem. Ela estrutura a discussão e permite registrar as razões da decisão.

## 10\. Performance: o que precisamos medir antes de concluir?

Não há benchmark real neste artigo. Para testar desempenho de forma honesta, separe três cenários:

1. Manipulação de coleções em memória, sem banco.
2. Envio em lote de Python para Oracle com `executemany()`.
3. Processamento dentro do Oracle com SQL em conjunto e, quando apropriado, `BULK COLLECT`/`FORALL`.

Registre versões, volume, tamanho de lote, ambiente, tempo, CPU, memória e tráfego de rede. Não compare um loop Python com uma operação SQL em conjunto como se fossem cargas equivalentes.

## Conclusão técnica e gerencial

Arrays e collections em Python, PL/SQL e Bash compartilham um propósito: organizar valores para processamento. Porém, tipagem, indexação, crescimento, acesso e ambiente de execução mudam significativamente.

O conhecimento técnico permite identificar essas diferenças. A visão de arquitetura ajuda a decidir onde cada etapa deve acontecer. A gestão garante que a solução seja observável, segura, economicamente justificável e sustentável pela equipe.

**Conhecer uma linguagem permite escrever código. Entender suas características e limitações permite tomar decisões técnicas melhores.**

## Referências oficiais

- [Python — Data Structures](https://docs.python.org/3/tutorial/datastructures.html)
- [Python — array](https://docs.python.org/3/library/array.html)
- [Oracle Database 19c — PL/SQL Collections and Records](https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/plsql-collections-and-records.html)
- [Oracle Database 19c — Bulk SQL and Bulk Binding](https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/bulk-sql-and-bulk-binding.html)
- [python-oracledb — Batch Statement Execution](https://python-oracledb.readthedocs.io/en/latest/user_guide/batch_statement.html)
- [GNU Bash Manual — Arrays](https://www.gnu.org/software/bash/manual/html_node/Arrays.html)

---

*Exemplos para estudo e laboratório. Saídas ilustram resultados esperados; não representam medições ou execuções verificadas. Teste códigos e permissões em ambientes controlados antes de adaptá-los para produção.*
