Encontrei a solução.
Fiz uma query utilizando a função generate_series() para gerar datas entre os períodos para cada um dos cargos listados.
Ordenei e fiz um case para determinar que quando houvessem datas iguais para os dois tipos de cargo, que trouxesse somente o do cargo predominante, que seria VP quando não houvesse, traz o cargo de DI.
Com isso consegui montar a linha do tempo detalhada, data a data.
Para resumir isso da forma que estava somente com data início e data fim, tive que utilizar função e armazenar os dados em variáveis para fazer inserts e updates no banco.
Segue o script que fiz, creio que ainda caiba melhorias:
--CRIANDO OU REGRIANDO A FUNÇÃO
CREATE OR REPLACE FUNCTION f_gera_linha_tempo(
--LOCAL PARA DECLARAR PASSAGEM DE PARÂMETROS
)
RETURNS bigint
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
AS $BODY$
DECLARE
--DECLARANDO AS VARIÁVEIS
VFuncao CHARACTER VARYING(100);
VFuncaoAux CHARACTER VARYING(100);
VNuFuncao INTEGER;
VNuFuncaoAux INTEGER;
i INTEGER;
rec_linha RECORD;
BEGIN
--INICIALIZANDO AS VARIÁVEIS
VFuncao = '';
VFuncaoAux = '';
VNuFuncao = 0;
VNuFuncaoAux = 0;
i = 0;
--DELETA OS DADOS DA TABELA PARA RECRIAR COM A FUNÇÃO
DELETE FROM tb_linha_tempo;
--INICIA LOOP
FOR rec_linha IN
--CONSULTA QUE CARREGA OS REGISTROS DO LOOP
/* A CONSULTA IRÁ EXPANDIR OS PERÍODOS DIA A DIA USANDO GENERATE_SERIES PARA PODER ISOLAR OS
DIAS QUE SERÃO DESCONSIDERADOS NA SOBREPOSIÇÃO, MANTENDO SOMENTE OS DIAS QUE DEVERÃO SER CONSIDERADOS
NA MONTAGEM DA LINHA DO TEMPO
CADASTRO: (o que eu tenho)
DI1 = 01/01/2023 A 31/03/2023
VP1 = 10/01/2023 A 20/01/2023
VP2 = 10/02/2023 A 20/03/2023
DI2 = 01/04/2023 A 10/04/2023
*/
WITH funcoes AS (
SELECT ROW_NUMBER() OVER (ORDER BY r.datas) AS sequencia,
r.no_funcao,
r.datas
FROM (
SELECT
DISTINCT
a.no_funcao,
CASE WHEN SUBSTRING(a.no_funcao,1,2) = 'DI' THEN 1 ELSE 2 END AS cod_funcao,
generate_series(
a.dt_inicio::DATE,
DATE(a.dt_fim - INTERVAL '1 DAY')::DATE,
'1 day')::DATE AS datas
FROM tb_funcoes AS a
) AS r
ORDER BY datas
),
menor_funcao AS (
SELECT no_funcao, datas, MIN(cod_funcao) AS cod_funcao FROM funcoes GROUP BY 1,2
)
SELECT dir.no_funcao,
dir.datas
FROM funcoes AS dir
INNER JOIN menor_funcao AS mo ON mo.cod_funcao = dir.cod_funcao AND mo.no_funcao = dir.no_funcao AND mo.datas = dir.datas
ORDER BY dir.datas
--COMEÇA A EXECUTAR O LOOP
LOOP
--VARIÁVEIS PRINCIPAIS RECEBEM OS VALORES
VFuncao = rec_linha.no_funcao;
VNuFuncao = rec_linha.cod_funcao;
--SE AS VARIÁVEIS PRINCIPAIS FOREM DIFERENTES DAS AUXILIARES, ENTRA NA CONDIÇÃO
IF (VFuncao != VFuncaoAux AND VNuFuncao != VNuFuncaoAux) THEN
i = i + 1; --VARIÁVEL QUE SERVIRÁ DE ID NA TABELA
--VARIÁVEIS AUXILIARES RECEBEM O MESMO VALOR DA VARIÁVEL PRINCIPAL
VFuncaoAux = rec_linha.no_funcao;
VNuFuncaoAux = rec_linha.cod_funcao;
--INSERE LINHA NA TABELA COM DATA FIM AINDA NULA
INSERT INTO tb_linha_tempo(id_linha, no_funcao, dt_inicio,dt_fim, cod_funcao)
VALUES (i, rec_linha.no_funcao, rec_linha.datas, null, rec_linha.cod_funcao);
ELSE
--SE OCORRÊNCIA PRINCIPAL DIFERENTE DA AUXILIAR, ENTRA NA CONDIÇÃO
IF (VNuFuncao != VNuFuncaoAux) THEN
--VARIÁVEL AUXILIAR RECEBE O VALOR DA OCORRÊNCIA
VNuFuncaoAux = rec_linha.cod_funcao;
i = i + 1; --INCREMENTA O ID
--INSERE LINHA NA TABELA
INSERT INTO tb_linha_tempo(id_linha, no_funcao, cod_funcao)
VALUES (i, rec_linha.no_funcao, rec_linha.datas, null, rec_linha.cod_funcao);
ELSE
--FICA ATUALIZANDO A DATA FIM ATÉ QUE O LOOP MUDE DE OCORRÊNCIA, A ÚLTIMA DATA A SER ATULIZADA É A QUE FECHA O PERÍODO
UPDATE tb_linha_tempo
SET dt_fim = rec_linha.datas
WHERE id_linha = i;
END IF; --segundo SE
END IF; --primeiro SE
END LOOP;
--ATUALIZA A DATA FIM DAS LINHAS COM DT_FIM NULA
--A DT_FIM FICA NULA QUANDO SÓ EXISTE UM REGISTRO DE DATA PARA A OCORRÊNCIA DO EMPREGADO
UPDATE tb_linha_tempo
SET dt_fim = dt_inicio
WHERE dt_fim IS NULL;
RETURN 0;
END;
$BODY$;
ALTER FUNCTION f_gera_linha_tempo()
OWNER TO postgres;
-- SELECT f_gera_linha_tempo()
-- DROP FUNCTION f_gera_linha_tempo()
-- SELECT * FROM tb_linha_tempo ORDER BY 1
-- DELETE FROM tb_linha_tempo