add estatistica
This commit is contained in:
parent
226d819909
commit
23256245a0
37
OpenCand.API/Controllers/EstatisticaController.cs
Normal file
37
OpenCand.API/Controllers/EstatisticaController.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
|
using OpenCand.API.Config;
|
||||||
|
using OpenCand.API.Model;
|
||||||
|
using OpenCand.API.Services;
|
||||||
|
|
||||||
|
namespace OpenCand.API.Controllers
|
||||||
|
{
|
||||||
|
[EnableRateLimiting(RateLimitingConfig.DefaultPolicy)]
|
||||||
|
public class EstatisticaController : BaseController
|
||||||
|
{
|
||||||
|
private readonly EstatisticaService estatisticaService;
|
||||||
|
|
||||||
|
public EstatisticaController(EstatisticaService estatisticaService)
|
||||||
|
{
|
||||||
|
this.estatisticaService = estatisticaService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("configuration")]
|
||||||
|
public async Task<ConfigurationModel> GetConfiguration()
|
||||||
|
{
|
||||||
|
return await estatisticaService.GetConfigurationModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("enriquecimento")]
|
||||||
|
public async Task<List<MaioresEnriquecimento>> GetMaioresEnriquecimentos()
|
||||||
|
{
|
||||||
|
return await estatisticaService.GetMaioresEnriquecimentos();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("values-sum")]
|
||||||
|
public async Task<List<GetValueSumResponse>> GetValuesSum([FromBody] GetValueSumRequest getValueSumRequest)
|
||||||
|
{
|
||||||
|
return await estatisticaService.GetValueSum(getValueSumRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
OpenCand.API/Model/ConfigurationModel.cs
Normal file
10
OpenCand.API/Model/ConfigurationModel.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace OpenCand.API.Model
|
||||||
|
{
|
||||||
|
public class ConfigurationModel
|
||||||
|
{
|
||||||
|
public List<string> Partidos { get; set; }
|
||||||
|
public List<string> SiglasUF { get; set; }
|
||||||
|
public List<int> Anos { get; set; }
|
||||||
|
public List<string> Cargos { get; set; }
|
||||||
|
}
|
||||||
|
}
|
38
OpenCand.API/Model/EstatisticaModels.cs
Normal file
38
OpenCand.API/Model/EstatisticaModels.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
namespace OpenCand.API.Model
|
||||||
|
{
|
||||||
|
public class MaioresEnriquecimento
|
||||||
|
{
|
||||||
|
public Guid IdCandidato { get; set; }
|
||||||
|
public string Nome { get; set; }
|
||||||
|
public float PatrimonioInicial { get; set; }
|
||||||
|
public int AnoInicial { get; set; }
|
||||||
|
public float PatrimonioFinal { get; set; }
|
||||||
|
public int AnoFinal { get; set; }
|
||||||
|
public float Enriquecimento { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GetValueSumRequest
|
||||||
|
{
|
||||||
|
public string Type { get; set; } // "bem", "despesa", "receita"
|
||||||
|
public string GroupBy { get; set; } // "candidato", "partido", "uf", or "cargo"
|
||||||
|
public GetValueSumRequestFilter? Filter { get; set; }
|
||||||
|
public class GetValueSumRequestFilter
|
||||||
|
{
|
||||||
|
public string? Partido { get; set; } // Optional, can be null
|
||||||
|
public string? Uf { get; set; } // Optional, can be null
|
||||||
|
public int? Ano { get; set; } // Optional, can be null
|
||||||
|
public string? Cargo { get; set; } // Optional, can be null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GetValueSumResponse
|
||||||
|
{
|
||||||
|
public Guid? IdCandidato { get; set; }
|
||||||
|
public string? Sgpartido { get; set; }
|
||||||
|
public string? SiglaUf { get; set; }
|
||||||
|
public string? Cargo { get; set; }
|
||||||
|
public string? Nome { get; set; }
|
||||||
|
public int Ano { get; set; }
|
||||||
|
public decimal Valor { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -52,22 +52,21 @@ namespace OpenCand.API
|
|||||||
|
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
} private static void SetupServices(WebApplicationBuilder builder)
|
}
|
||||||
|
|
||||||
|
private static void SetupServices(WebApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
builder.Services.Configure<FotosSettings>(builder.Configuration.GetSection("FotosSettings"));
|
builder.Services.Configure<FotosSettings>(builder.Configuration.GetSection("FotosSettings"));
|
||||||
builder.Services.AddMemoryCache();
|
builder.Services.AddMemoryCache();
|
||||||
|
|
||||||
// Register repositories with IMemoryCache
|
builder.Services.AddScoped(provider => new OpenCandRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
|
||||||
builder.Services.AddScoped<OpenCandRepository>(provider =>
|
builder.Services.AddScoped(provider => new CandidatoRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
|
||||||
new OpenCandRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
|
builder.Services.AddScoped(provider => new BemCandidatoRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
|
||||||
builder.Services.AddScoped<CandidatoRepository>(provider =>
|
builder.Services.AddScoped(provider => new DespesaReceitaRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
|
||||||
new CandidatoRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
|
builder.Services.AddScoped(provider => new EstatisticaRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
|
||||||
builder.Services.AddScoped<BemCandidatoRepository>(provider =>
|
|
||||||
new BemCandidatoRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
|
|
||||||
builder.Services.AddScoped<DespesaReceitaRepository>(provider =>
|
|
||||||
new DespesaReceitaRepository(provider.GetRequiredService<IConfiguration>(), provider.GetService<IMemoryCache>()));
|
|
||||||
|
|
||||||
builder.Services.AddScoped<OpenCandService>();
|
builder.Services.AddScoped<OpenCandService>();
|
||||||
|
builder.Services.AddScoped<EstatisticaService>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
87
OpenCand.API/Repository/EstatisticaRepository.cs
Normal file
87
OpenCand.API/Repository/EstatisticaRepository.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Npgsql;
|
||||||
|
using OpenCand.API.Model;
|
||||||
|
using OpenCand.Core.Models;
|
||||||
|
using OpenCand.Repository;
|
||||||
|
|
||||||
|
namespace OpenCand.API.Repository
|
||||||
|
{
|
||||||
|
public class EstatisticaRepository : BaseRepository
|
||||||
|
{
|
||||||
|
private readonly IConfiguration configuration;
|
||||||
|
|
||||||
|
public EstatisticaRepository(IConfiguration configuration, IMemoryCache? cache = null) : base(configuration, cache)
|
||||||
|
{
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<MaioresEnriquecimento>> GetMaioresEnriquecimentos()
|
||||||
|
{
|
||||||
|
string cacheKey = GenerateCacheKey("GetMaioresEnriquecimento");
|
||||||
|
|
||||||
|
var result = await GetOrSetCacheAsync(cacheKey, async () =>
|
||||||
|
{
|
||||||
|
using (var connection = new NpgsqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
return (await connection.QueryAsync<MaioresEnriquecimento>(@"
|
||||||
|
WITH patrimonio_anual AS (
|
||||||
|
SELECT idcandidato, ano, SUM(valor) AS valor_total_ano
|
||||||
|
FROM bem_candidato
|
||||||
|
GROUP BY idcandidato, ano
|
||||||
|
),
|
||||||
|
extremos_declaracao AS (
|
||||||
|
SELECT idcandidato, MIN(ano) AS anoInicial, MAX(ano) AS anoFinal
|
||||||
|
FROM patrimonio_anual
|
||||||
|
GROUP BY idcandidato
|
||||||
|
HAVING COUNT(DISTINCT ano) >= 2
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
c.nome,
|
||||||
|
ed.anoInicial,
|
||||||
|
pi.valor_total_ano AS patrimonioInicial,
|
||||||
|
ed.anoFinal,
|
||||||
|
pf.valor_total_ano AS patrimonioFinal,
|
||||||
|
(pf.valor_total_ano - pi.valor_total_ano) AS enriquecimento
|
||||||
|
FROM extremos_declaracao ed
|
||||||
|
JOIN candidato c ON ed.idcandidato = c.idcandidato
|
||||||
|
JOIN patrimonio_anual pi ON ed.idcandidato = pi.idcandidato AND ed.anoInicial = pi.ano
|
||||||
|
JOIN patrimonio_anual pf ON ed.idcandidato = pf.idcandidato AND ed.anoFinal = pf.ano
|
||||||
|
ORDER BY enriquecimento DESC
|
||||||
|
LIMIT 25;")
|
||||||
|
).AsList();
|
||||||
|
}
|
||||||
|
}); return result ?? new List<MaioresEnriquecimento>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ConfigurationModel> GetConfiguration()
|
||||||
|
{
|
||||||
|
string cacheKey = GenerateCacheKey("GetConfigurationModel");
|
||||||
|
|
||||||
|
var result = await GetOrSetCacheAsync(cacheKey, async () =>
|
||||||
|
{
|
||||||
|
var result = new ConfigurationModel();
|
||||||
|
|
||||||
|
using (var connection = new NpgsqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
result.Partidos = (await connection.QueryAsync<string>(@"SELECT DISTINCT sigla FROM partido;")).AsList();
|
||||||
|
result.SiglasUF = (await connection.QueryAsync<string>(@"SELECT DISTINCT siglauf FROM candidato_mapping;")).AsList();
|
||||||
|
result.Anos = (await connection.QueryAsync<int>(@"SELECT DISTINCT ano FROM candidato_mapping;")).AsList();
|
||||||
|
result.Cargos = (await connection.QueryAsync<string>(@"SELECT DISTINCT cargo FROM candidato_mapping;")).AsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result ?? new ConfigurationModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<GetValueSumResponse>> GetValueSum(string query, Dictionary<string, object>? parameters = null)
|
||||||
|
{
|
||||||
|
using (var connection = new NpgsqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
return (await connection.QueryAsync<GetValueSumResponse>(query, parameters)).AsList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,8 @@ using OpenCand.Core.Models;
|
|||||||
using OpenCand.Repository;
|
using OpenCand.Repository;
|
||||||
|
|
||||||
namespace OpenCand.API.Repository
|
namespace OpenCand.API.Repository
|
||||||
{ public class OpenCandRepository : BaseRepository
|
{
|
||||||
|
public class OpenCandRepository : BaseRepository
|
||||||
{
|
{
|
||||||
private readonly IConfiguration configuration;
|
private readonly IConfiguration configuration;
|
||||||
|
|
||||||
@ -35,7 +36,9 @@ namespace OpenCand.API.Repository
|
|||||||
});
|
});
|
||||||
|
|
||||||
return result ?? new OpenCandStats();
|
return result ?? new OpenCandStats();
|
||||||
} public async Task<DataAvailabilityStats> GetDataAvailabilityAsync()
|
}
|
||||||
|
|
||||||
|
public async Task<DataAvailabilityStats> GetDataAvailabilityAsync()
|
||||||
{
|
{
|
||||||
string cacheKey = GenerateCacheKey("DataAvailabilityStats");
|
string cacheKey = GenerateCacheKey("DataAvailabilityStats");
|
||||||
|
|
||||||
|
178
OpenCand.API/Services/EstatisticaService.cs
Normal file
178
OpenCand.API/Services/EstatisticaService.cs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
using OpenCand.API.Model;
|
||||||
|
using OpenCand.API.Repository;
|
||||||
|
using OpenCand.Repository;
|
||||||
|
|
||||||
|
namespace OpenCand.API.Services
|
||||||
|
{
|
||||||
|
public class EstatisticaService
|
||||||
|
{
|
||||||
|
private readonly OpenCandRepository openCandRepository;
|
||||||
|
private readonly CandidatoRepository candidatoRepository;
|
||||||
|
private readonly BemCandidatoRepository bemCandidatoRepository;
|
||||||
|
private readonly DespesaReceitaRepository despesaReceitaRepository;
|
||||||
|
private readonly EstatisticaRepository estatisticaRepository;
|
||||||
|
private readonly IConfiguration configuration;
|
||||||
|
private readonly ILogger<OpenCandService> logger;
|
||||||
|
|
||||||
|
public EstatisticaService(
|
||||||
|
OpenCandRepository openCandRepository,
|
||||||
|
CandidatoRepository candidatoRepository,
|
||||||
|
BemCandidatoRepository bemCandidatoRepository,
|
||||||
|
DespesaReceitaRepository despesaReceitaRepository,
|
||||||
|
EstatisticaRepository estatisticaRepository,
|
||||||
|
IConfiguration configuration,
|
||||||
|
ILogger<OpenCandService> logger)
|
||||||
|
{
|
||||||
|
this.openCandRepository = openCandRepository;
|
||||||
|
this.candidatoRepository = candidatoRepository;
|
||||||
|
this.bemCandidatoRepository = bemCandidatoRepository;
|
||||||
|
this.despesaReceitaRepository = despesaReceitaRepository;
|
||||||
|
this.estatisticaRepository = estatisticaRepository;
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ConfigurationModel> GetConfigurationModel()
|
||||||
|
{
|
||||||
|
return await estatisticaRepository.GetConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<MaioresEnriquecimento>> GetMaioresEnriquecimentos()
|
||||||
|
{
|
||||||
|
return await estatisticaRepository.GetMaioresEnriquecimentos();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<GetValueSumResponse>> GetValueSum(GetValueSumRequest request)
|
||||||
|
{
|
||||||
|
ValidateRequest(request);
|
||||||
|
|
||||||
|
var sqlBuilder = new SqlQueryBuilder();
|
||||||
|
var query = sqlBuilder.BuildQuery(request);
|
||||||
|
var parameters = sqlBuilder.GetParameters();
|
||||||
|
|
||||||
|
return await estatisticaRepository.GetValueSum(query, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateRequest(GetValueSumRequest request)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.Type))
|
||||||
|
throw new ArgumentException("Type is required.");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(request.GroupBy))
|
||||||
|
throw new ArgumentException("GroupBy is required.");
|
||||||
|
|
||||||
|
var validTypes = new[] { "bem", "despesa", "receita" };
|
||||||
|
if (!validTypes.Contains(request.Type.ToLower()))
|
||||||
|
throw new ArgumentException($"Invalid type specified. Valid values are: {string.Join(", ", validTypes)}");
|
||||||
|
|
||||||
|
var validGroupBy = new[] { "candidato", "partido", "uf", "cargo" };
|
||||||
|
if (!validGroupBy.Contains(request.GroupBy.ToLower()))
|
||||||
|
throw new ArgumentException($"Invalid groupBy specified. Valid values are: {string.Join(", ", validGroupBy)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SqlQueryBuilder
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, object> _parameters = new();
|
||||||
|
private int _paramCounter = 0;
|
||||||
|
|
||||||
|
public Dictionary<string, object> GetParameters() => _parameters;
|
||||||
|
|
||||||
|
public string BuildQuery(GetValueSumRequest request)
|
||||||
|
{
|
||||||
|
var selectClause = BuildSelectClause(request.GroupBy);
|
||||||
|
var fromClause = BuildFromClause(request.Type);
|
||||||
|
var joinClause = BuildJoinClause(request.GroupBy);
|
||||||
|
var whereClause = BuildWhereClause(request.Filter);
|
||||||
|
var groupByClause = BuildGroupByClause(request.GroupBy);
|
||||||
|
var orderByClause = "ORDER BY SUM(src.valor) DESC";
|
||||||
|
var limitClause = "LIMIT 10";
|
||||||
|
|
||||||
|
return $"{selectClause} {fromClause} {joinClause} {whereClause} {groupByClause} {orderByClause} {limitClause}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildSelectClause(string groupBy)
|
||||||
|
{
|
||||||
|
return groupBy.ToLower() switch
|
||||||
|
{
|
||||||
|
"candidato" => "SELECT src.idcandidato, c.nome, src.ano, SUM(src.valor) as valor",
|
||||||
|
"partido" => "SELECT cm.sgpartido, src.ano, SUM(src.valor) as valor",
|
||||||
|
"uf" => "SELECT cm.siglauf, src.ano, SUM(src.valor) as valor",
|
||||||
|
"cargo" => "SELECT cm.cargo, src.ano, SUM(src.valor) as valor",
|
||||||
|
_ => throw new ArgumentException("Invalid group by specified.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildFromClause(string type)
|
||||||
|
{
|
||||||
|
return type.ToLower() switch
|
||||||
|
{
|
||||||
|
"bem" => "FROM bem_candidato src",
|
||||||
|
"despesa" => "FROM despesas_candidato src",
|
||||||
|
"receita" => "FROM receitas_candidato src",
|
||||||
|
_ => throw new ArgumentException("Invalid type specified.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildJoinClause(string groupBy)
|
||||||
|
{
|
||||||
|
return groupBy.ToLower() switch
|
||||||
|
{
|
||||||
|
"candidato" => "JOIN candidato c ON src.idcandidato = c.idcandidato JOIN candidato_mapping cm ON src.idcandidato = cm.idcandidato AND src.ano = cm.ano",
|
||||||
|
"partido" or "uf" or "cargo" => "JOIN candidato_mapping cm ON src.idcandidato = cm.idcandidato AND src.ano = cm.ano",
|
||||||
|
_ => throw new ArgumentException("Invalid group by specified.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildWhereClause(GetValueSumRequest.GetValueSumRequestFilter? filter)
|
||||||
|
{
|
||||||
|
if (filter == null)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var conditions = new List<string>();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.Partido))
|
||||||
|
{
|
||||||
|
var paramName = $"partido{++_paramCounter}";
|
||||||
|
conditions.Add($"cm.sgpartido = @{paramName}");
|
||||||
|
_parameters[paramName] = filter.Partido;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.Uf))
|
||||||
|
{
|
||||||
|
var paramName = $"uf{++_paramCounter}";
|
||||||
|
conditions.Add($"cm.siglauf = @{paramName}");
|
||||||
|
_parameters[paramName] = filter.Uf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.Ano.HasValue)
|
||||||
|
{
|
||||||
|
var paramName = $"ano{++_paramCounter}";
|
||||||
|
conditions.Add($"src.ano = @{paramName}");
|
||||||
|
_parameters[paramName] = filter.Ano.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.Cargo))
|
||||||
|
{
|
||||||
|
var paramName = $"cargo{++_paramCounter}";
|
||||||
|
conditions.Add($"cm.cargo = @{paramName}");
|
||||||
|
_parameters[paramName] = filter.Cargo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conditions.Count > 0 ? $"WHERE {string.Join(" AND ", conditions)}" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildGroupByClause(string groupBy)
|
||||||
|
{
|
||||||
|
return groupBy.ToLower() switch
|
||||||
|
{
|
||||||
|
"candidato" => "GROUP BY src.idcandidato, c.nome, src.ano",
|
||||||
|
"partido" => "GROUP BY cm.sgpartido, src.ano",
|
||||||
|
"uf" => "GROUP BY cm.siglauf, src.ano",
|
||||||
|
"cargo" => "GROUP BY cm.cargo, src.ano",
|
||||||
|
_ => throw new ArgumentException("Invalid group by specified.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -98,7 +98,7 @@ CREATE INDEX idx_partido_numero ON partido (numero);
|
|||||||
|
|
||||||
---- Tables for storing despesas e receitas of candidacies
|
---- Tables for storing despesas e receitas of candidacies
|
||||||
CREATE TABLE despesas_candidato (
|
CREATE TABLE despesas_candidato (
|
||||||
idreceita UUID NOT NULL DEFAULT gen_random_uuid(),
|
iddespesa UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
idcandidato UUID NOT NULL,
|
idcandidato UUID NOT NULL,
|
||||||
ano INT NOT NULL,
|
ano INT NOT NULL,
|
||||||
turno VARCHAR(2) NOT NULL,
|
turno VARCHAR(2) NOT NULL,
|
||||||
@ -115,7 +115,7 @@ CREATE TABLE despesas_candidato (
|
|||||||
descricao TEXT,
|
descricao TEXT,
|
||||||
origemdespesa TEXT,
|
origemdespesa TEXT,
|
||||||
valor NUMERIC(20, 2),
|
valor NUMERIC(20, 2),
|
||||||
CONSTRAINT pk_despesas_candidato PRIMARY KEY (idreceita),
|
CONSTRAINT pk_despesas_candidato PRIMARY KEY (iddespesa),
|
||||||
CONSTRAINT fk_despesas_candidato_candidato FOREIGN KEY (idcandidato) REFERENCES candidato(idcandidato) ON DELETE CASCADE ON UPDATE CASCADE
|
CONSTRAINT fk_despesas_candidato_candidato FOREIGN KEY (idcandidato) REFERENCES candidato(idcandidato) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
CREATE INDEX idx_despesas_candidato_idcandidato ON despesas_candidato (idcandidato);
|
CREATE INDEX idx_despesas_candidato_idcandidato ON despesas_candidato (idcandidato);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user