Skip to content

电梯交通量仿真分析7

约 4149 字大约 14 分钟

coding

2025-08-04

7. 数据库设计

7.1 设计原则

7.1.1 数据库选型理由

选择PostgreSQL作为本系统的数据库,基于以下考虑:

  • 强大的JSONB支持:适合存储灵活的仿真配置和结果数据
  • 地理空间数据支持:虽本系统不直接使用,但为未来扩展提供可能
  • ACID合规性:确保仿真结果数据的完整性和一致性
  • 高性能:能够处理大规模仿真产生的大量时序数据
  • 开源与成熟:企业级应用的可靠选择

7.1.2 设计原则

  • 规范化与反规范化平衡:核心配置数据规范化,时序数据适当反规范化
  • 时序数据优化:针对仿真时间切片数据进行特殊优化
  • 查询性能优先:为关键查询模式设计合适的索引
  • 数据完整性:通过约束确保OD矩阵等关键数据的完整性
  • 可扩展性:设计支持未来功能扩展的灵活结构

7.2 核心表结构设计

7.2.1 建筑配置表

building - 存储建筑基本信息

CREATE TABLE building (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255) NOT NULL,
    total_floors INTEGER NOT NULL CHECK (total_floors > 0),
    floor_height NUMERIC(5,2) NOT NULL CHECK (floor_height > 0),
    building_type VARCHAR(50) NOT NULL, -- OFFICE, MALL, RESIDENTIAL, MIXED
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

COMMENT ON TABLE building IS '建筑基本信息表,存储建筑的静态属性';
COMMENT ON COLUMN building.building_type IS '建筑类型:OFFICE(办公楼), MALL(商场), RESIDENTIAL(住宅), MIXED(混合用途)';

floor - 存储楼层详细信息

CREATE TABLE floor (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    building_id UUID NOT NULL REFERENCES building(id) ON DELETE CASCADE,
    floor_number INTEGER NOT NULL CHECK (floor_number >= 1),
    usage VARCHAR(50) NOT NULL, -- LOBBY, PARKING, OFFICE, RETAIL, RESIDENTIAL
    population INTEGER NOT NULL DEFAULT 0 CHECK (population >= 0),
    UNIQUE (building_id, floor_number)
);

COMMENT ON TABLE floor IS '楼层详细信息表,存储每层的用途和人口信息';
COMMENT ON COLUMN floor.usage IS '楼层用途:LOBBY(大厅), PARKING(车库), OFFICE(办公), RETAIL(零售), RESIDENTIAL(住宅)';

floor_time_period - 存储楼层在不同时段的交通特性

CREATE TABLE floor_time_period (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    floor_id UUID NOT NULL REFERENCES floor(id) ON DELETE CASCADE,
    time_period VARCHAR(50) NOT NULL, -- MORNING_PEAK, LUNCH_PEAK, EVENING_PEAK, NORMAL
    incoming_traffic_ratio NUMERIC(4,3) NOT NULL CHECK (incoming_traffic_ratio BETWEEN 0 AND 1),
    outgoing_traffic_ratio NUMERIC(4,3) NOT NULL CHECK (outgoing_traffic_ratio BETWEEN 0 AND 1),
    interfloor_traffic_ratio NUMERIC(4,3) NOT NULL CHECK (interfloor_traffic_ratio BETWEEN 0 AND 1),
    CHECK (incoming_traffic_ratio + outgoing_traffic_ratio + interfloor_traffic_ratio = 1),
    batch_probability NUMERIC(4,3) NOT NULL CHECK (batch_probability BETWEEN 0 AND 1),
    inter_arrival_mean NUMERIC(5,2) NOT NULL CHECK (inter_arrival_mean > 0),
    inter_arrival_stddev NUMERIC(5,2) NOT NULL CHECK (inter_arrival_stddev >= 0)
);

COMMENT ON TABLE floor_time_period IS '存储楼层在不同时段的交通特性,实现PDF中描述的三种交通成分比例';
COMMENT ON COLUMN floor_time_period.time_period IS '时间段:MORNING_PEAK(早高峰), LUNCH_PEAK(午餐高峰), EVENING_PEAK(晚高峰), NORMAL(正常)';
COMMENT ON COLUMN floor_time_period.incoming_traffic_ratio IS '进楼交通比例,对应PDF中"incoming traffic"';
COMMENT ON COLUMN floor_time_period.outgoing_traffic_ratio IS '出楼交通比例,对应PDF中"outgoing traffic"';
COMMENT ON COLUMN floor_time_period.interfloor_traffic_ratio IS '楼层间交通比例,对应PDF中"inter-floor traffic"';

batch_size_distribution - 存储批次大小分布

CREATE TABLE batch_size_distribution (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    floor_time_period_id UUID NOT NULL REFERENCES floor_time_period(id) ON DELETE CASCADE,
    batch_size INTEGER NOT NULL CHECK (batch_size >= 1),
    probability NUMERIC(4,3) NOT NULL CHECK (probability BETWEEN 0 AND 1),
    UNIQUE (floor_time_period_id, batch_size)
);

COMMENT ON TABLE batch_size_distribution IS '存储PDF中描述的批次大小分布,实现"批次到达可以建模为时间非齐次泊松过程"';

7.2.2 电梯配置表

elevator_group - 电梯组配置

CREATE TABLE elevator_group (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    building_id UUID NOT NULL REFERENCES building(id) ON DELETE CASCADE,
    name VARCHAR(100),
    control_type VARCHAR(50) NOT NULL, -- SINGLE_CONTROL, PARALLEL_CONTROL, GROUP_CONTROL, DESTINATION_CONTROL
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

COMMENT ON TABLE elevator_group IS '电梯组配置表,支持PDF中描述的四种控制方式';
COMMENT ON COLUMN elevator_group.control_type IS '控制类型:SINGLE_CONTROL(单控), PARALLEL_CONTROL(并联), GROUP_CONTROL(群控), DESTINATION_CONTROL(目的选层)';

elevator - 单台电梯配置

CREATE TABLE elevator (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    group_id UUID NOT NULL REFERENCES elevator_group(id) ON DELETE CASCADE,
    name VARCHAR(100),
    capacity INTEGER NOT NULL CHECK (capacity > 0),
    rated_speed NUMERIC(4,2) NOT NULL CHECK (rated_speed > 0),
    acceleration NUMERIC(4,2) NOT NULL CHECK (acceleration > 0),
    deceleration NUMERIC(4,2) NOT NULL CHECK (deceleration > 0),
    door_width NUMERIC(3,2) NOT NULL CHECK (door_width > 0),
    standby_floor INTEGER NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

COMMENT ON TABLE elevator IS '单台电梯配置表,存储电梯的物理和运行参数';
COMMENT ON COLUMN elevator.door_width IS '门宽(米),影响PDF中描述的"乘客流动时间"';

elevator_service_floor - 电梯服务楼层

CREATE TABLE elevator_service_floor (
    elevator_id UUID NOT NULL REFERENCES elevator(id) ON DELETE CASCADE,
    floor_number INTEGER NOT NULL,
    PRIMARY KEY (elevator_id, floor_number),
    CHECK (floor_number >= 1)
);

COMMENT ON TABLE elevator_service_floor IS '电梯服务楼层表,实现PDF中描述的电梯服务区域划分';

7.2.3 仿真配置与结果表

simulation - 仿真任务基本信息

CREATE TABLE simulation (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255),
    building_id UUID NOT NULL REFERENCES building(id),
    created_by UUID, -- 用户ID,外键到用户表
    status VARCHAR(20) NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED')),
    start_time TIMESTAMPTZ NOT NULL,
    end_time TIMESTAMPTZ,
    duration INTEGER NOT NULL CHECK (duration > 0), -- 仿真时长(秒)
    time_slice INTEGER NOT NULL DEFAULT 5 CHECK (time_slice > 0), -- 时间切片(秒)
    od_matrix_method VARCHAR(10) NOT NULL DEFAULT 'BILS' CHECK (od_matrix_method IN ('LP', 'BILS', 'CP')),
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

COMMENT ON TABLE simulation IS '仿真任务基本信息表,存储仿真任务的状态和配置';
COMMENT ON COLUMN simulation.od_matrix_method IS 'OD矩阵估计方法:LP(线性规划), BILS(带界约束的整数最小二乘), CP(约束规划)';

simulation_result - 仿真结果指标

CREATE TABLE simulation_result (
    simulation_id UUID PRIMARY KEY REFERENCES simulation(id) ON DELETE CASCADE,
    average_waiting_time NUMERIC(6,2),
    max_waiting_time NUMERIC(6,2),
    min_waiting_time NUMERIC(6,2),
    waiting_time_stddev NUMERIC(6,2),
    waiting_time_percentiles JSONB NOT NULL DEFAULT '[0,0,0,0,0,0,0,0,0,0]'::jsonb,
    average_travel_time NUMERIC(6,2),
    max_travel_time NUMERIC(6,2),
    min_travel_time NUMERIC(6,2),
    travel_time_stddev NUMERIC(6,2),
    travel_time_percentiles JSONB NOT NULL DEFAULT '[0,0,0,0,0,0,0,0,0,0]'::jsonb,
    handling_capacity NUMERIC(5,2), -- 5分钟输送能力(%)
    energy_consumption NUMERIC(8,2),
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

COMMENT ON TABLE simulation_result IS '仿真结果指标表,存储PDF中要求的各项性能指标';
COMMENT ON COLUMN simulation_result.waiting_time_percentiles IS '等待时间百分位数[10%,20%,...,100%]';
COMMENT ON COLUMN simulation_result.travel_time_percentiles IS '运输时间百分位数[10%,20%,...,100%]';
COMMENT ON COLUMN simulation_result.handling_capacity IS '5分钟输送能力(%),对应PDF中"handling capacity"';

simulation_result_by_floor - 按楼层划分的仿真结果

CREATE TABLE simulation_result_by_floor (
    simulation_id UUID NOT NULL REFERENCES simulation(id) ON DELETE CASCADE,
    floor_number INTEGER NOT NULL CHECK (floor_number >= 1),
    average_waiting_time NUMERIC(6,2),
    max_waiting_time NUMERIC(6,2),
    min_waiting_time NUMERIC(6,2),
    waiting_time_stddev NUMERIC(6,2),
    waiting_time_percentiles JSONB NOT NULL DEFAULT '[0,0,0,0,0,0,0,0,0,0]'::jsonb,
    average_travel_time NUMERIC(6,2),
    PRIMARY KEY (simulation_id, floor_number)
);

COMMENT ON TABLE simulation_result_by_floor IS '按楼层划分的仿真结果表,用于分析不同楼层的服务水平';

7.2.4 乘客流量与OD矩阵表

od_matrix - OD矩阵主表

CREATE TABLE od_matrix (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    simulation_id UUID REFERENCES simulation(id) ON DELETE CASCADE,
    building_id UUID REFERENCES building(id) ON DELETE CASCADE,
    method VARCHAR(10) NOT NULL CHECK (method IN ('LP', 'BILS', 'CP')),
    is_rounded BOOLEAN NOT NULL DEFAULT false,
    computation_time_ms INTEGER NOT NULL CHECK (computation_time_ms >= 0),
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

COMMENT ON TABLE od_matrix IS 'OD矩阵主表,存储PDF中描述的电梯行程OD矩阵估计结果';
COMMENT ON COLUMN od_matrix.is_rounded IS '是否为舍入解,对应PDF中"by rounding a continuous solution to the nearest integer"';

od_matrix_entry - OD矩阵条目

CREATE TABLE od_matrix_entry (
    od_matrix_id UUID NOT NULL REFERENCES od_matrix(id) ON DELETE CASCADE,
    origin_floor INTEGER NOT NULL CHECK (origin_floor >= 1),
    destination_floor INTEGER NOT NULL CHECK (destination_floor >= 1),
    passenger_count INTEGER NOT NULL CHECK (passenger_count >= 0),
    probability NUMERIC(5,4) NOT NULL CHECK (probability BETWEEN 0 AND 1),
    PRIMARY KEY (od_matrix_id, origin_floor, destination_floor),
    CHECK (origin_floor <> destination_floor)
);

CREATE INDEX idx_od_matrix_entry_origin ON od_matrix_entry(origin_floor);
CREATE INDEX idx_od_matrix_entry_destination ON od_matrix_entry(destination_floor);

COMMENT ON TABLE od_matrix_entry IS 'OD矩阵条目表,存储起点到终点的乘客流量分布';
COMMENT ON COLUMN od_matrix_entry.probability IS '概率值,用于PDF中描述的"statistics"和"forecast"';

elevator_trip - 电梯行程记录

CREATE TABLE elevator_trip (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    simulation_id UUID NOT NULL REFERENCES simulation(id) ON DELETE CASCADE,
    elevator_id UUID NOT NULL,
    start_time TIMESTAMPTZ NOT NULL,
    end_time TIMESTAMPTZ NOT NULL,
    CHECK (end_time > start_time)
);

COMMENT ON TABLE elevator_trip IS '电梯行程记录表,用于OD矩阵估计的输入数据';

elevator_trip_stop - 电梯行程停靠点

CREATE TABLE elevator_trip_stop (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    trip_id UUID NOT NULL REFERENCES elevator_trip(id) ON DELETE CASCADE,
    floor_number INTEGER NOT NULL CHECK (floor_number >= 1),
    arrival_time TIMESTAMPTZ NOT NULL,
    departure_time TIMESTAMPTZ NOT NULL,
    boarding_count INTEGER NOT NULL DEFAULT 0 CHECK (boarding_count >= 0),
    alighting_count INTEGER NOT NULL DEFAULT 0 CHECK (alighting_count >= 0),
    CHECK (departure_time > arrival_time),
    UNIQUE (trip_id, floor_number)
);

CREATE INDEX idx_elevator_trip_stop_trip ON elevator_trip_stop(trip_id);
CREATE INDEX idx_elevator_trip_stop_floor ON elevator_trip_stop(floor_number);

COMMENT ON TABLE elevator_trip_stop IS '电梯行程停靠点表,存储PDF中描述的"elevator trip"数据';

7.2.5 时序数据表

simulation_timeline - 仿真时间切片数据

CREATE TABLE simulation_timeline (
    id BIGSERIAL PRIMARY KEY,
    simulation_id UUID NOT NULL REFERENCES simulation(id) ON DELETE CASCADE,
    time_point INTEGER NOT NULL CHECK (time_point >= 0), -- 相对于仿真开始的时间(秒)
    elevators JSONB NOT NULL, -- 电梯状态数组
    passenger_events JSONB NOT NULL, -- 乘客事件数组
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_simulation_timeline_simulation ON simulation_timeline(simulation_id);
CREATE INDEX idx_simulation_timeline_time ON simulation_timeline(time_point);

COMMENT ON TABLE simulation_timeline IS '仿真时间切片数据表,用于前端动画展示,按指定时间切片提供电梯和乘客数据';
COMMENT ON COLUMN simulation_timeline.elevators IS 'JSONB格式存储电梯状态,包含位置、方向、门状态等';
COMMENT ON COLUMN simulation_timeline.passenger_events IS 'JSONB格式存储乘客事件,包含上车、下车等';

simulation_timeline_partitioned - 分区版时间切片数据(针对大规模仿真)

CREATE TABLE simulation_timeline_partitioned (
    id BIGSERIAL PRIMARY KEY,
    simulation_id UUID NOT NULL,
    time_point INTEGER NOT NULL,
    elevators JSONB NOT NULL,
    passenger_events JSONB NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
) PARTITION BY RANGE (time_point);

-- 每1000秒创建一个分区
CREATE TABLE simulation_timeline_p0 PARTITION OF simulation_timeline_partitioned
    FOR VALUES FROM (0) TO (1000);
CREATE TABLE simulation_timeline_p1 PARTITION OF simulation_timeline_partitioned
    FOR VALUES FROM (1000) TO (2000);
-- 更多分区...

CREATE INDEX idx_simulation_timeline_partitioned_simulation ON simulation_timeline_partitioned(simulation_id);
CREATE INDEX idx_simulation_timeline_partitioned_time ON simulation_timeline_partitioned(time_point);

COMMENT ON TABLE simulation_timeline_partitioned IS '分区版仿真时间切片数据表,针对大规模仿真优化查询性能';

7.3 索引设计与性能优化

7.3.1 关键索引设计

-- 建筑与楼层相关索引
CREATE INDEX idx_floor_building ON floor(building_id);
CREATE INDEX idx_floor_time_period_floor ON floor_time_period(floor_id);

-- 仿真相关索引
CREATE INDEX idx_simulation_building ON simulation(building_id);
CREATE INDEX idx_simulation_status ON simulation(status);
CREATE INDEX idx_simulation_created ON simulation(created_at DESC);
CREATE INDEX idx_simulation_result_simulation ON simulation_result(simulation_id);

-- OD矩阵相关索引
CREATE INDEX idx_od_matrix_simulation ON od_matrix(simulation_id) WHERE simulation_id IS NOT NULL;
CREATE INDEX idx_od_matrix_building ON od_matrix(building_id) WHERE building_id IS NOT NULL;
CREATE INDEX idx_od_matrix_entry_matrix ON od_matrix_entry(od_matrix_id);
CREATE INDEX od_matrix_entry_origin_dest_idx ON od_matrix_entry(origin_floor, destination_floor);
CREATE INDEX od_matrix_entry_prob_idx ON od_matrix_entry(probability DESC);

-- 电梯行程相关索引
CREATE INDEX idx_elevator_trip_simulation ON elevator_trip(simulation_id);
CREATE INDEX idx_elevator_trip_stop_trip_time ON elevator_trip_stop(trip_id, arrival_time);

-- 时序数据索引
CREATE INDEX idx_simulation_timeline_time_range ON simulation_timeline(simulation_id, time_point)
    WHERE time_point BETWEEN 0 AND 3600; -- 常用时间范围

7.3.2 性能优化策略

1. 时序数据优化

-- 为时间切片数据表添加BRIN索引(适合时间序列)
CREATE INDEX idx_simulation_timeline_brin ON simulation_timeline USING BRIN (time_point)
    WITH (pages_per_range = 16);

-- 分区表的分区剪枝优化
-- 针对查询特定时间范围的场景
EXPLAIN ANALYZE
SELECT * FROM simulation_timeline
WHERE simulation_id = 'some-uuid' AND time_point BETWEEN 1800 AND 2400;

2. OD矩阵查询优化

-- 创建函数索引,用于快速查询特定origin-destination对
CREATE INDEX idx_od_matrix_entry_origin_dest_func 
ON od_matrix_entry ((origin_floor * 1000 + destination_floor));

-- 创建物化视图,用于快速获取建筑级OD矩阵
CREATE MATERIALIZED VIEW building_od_matrix AS
SELECT 
    origin_floor,
    destination_floor,
    SUM(passenger_count) AS total_passengers,
    AVG(probability) AS avg_probability
FROM od_matrix_entry ome
JOIN od_matrix om ON ome.od_matrix_id = om.id
GROUP BY origin_floor, destination_floor;

CREATE INDEX idx_building_od_matrix ON building_od_matrix(origin_floor, destination_floor);

3. 仿真结果查询优化

-- 创建部分索引,针对已完成的仿真
CREATE INDEX idx_simulation_completed ON simulation(id) 
WHERE status = 'COMPLETED';

-- 创建函数索引,用于快速获取特定性能指标
CREATE INDEX idx_simulation_result_waiting_time 
ON simulation_result(average_waiting_time)
WHERE average_waiting_time IS NOT NULL;

4. 批量数据插入优化

-- 针对仿真结果的大批量插入,使用COPY命令替代INSERT
-- 示例Java代码:
/*
try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement(
         "COPY simulation_timeline(simulation_id, time_point, elevators, passenger_events) " +
         "FROM STDIN WITH (FORMAT csv)")) {
    
    CopyManager copyManager = new CopyManager((BaseConnection) conn);
    String csvData = simulationId + "," + timePoint + "," + 
                    escapeJson(elevators) + "," + escapeJson(passengerEvents);
    copyManager.copyIn(ps, new StringReader(csvData));
}
*/

7.4 数据库函数与存储过程

7.4.1 OD矩阵验证函数

CREATE OR REPLACE FUNCTION validate_od_matrix(od_matrix_id UUID)
RETURNS TABLE (is_valid BOOLEAN, issues JSONB) AS $$
DECLARE
    total_floors INTEGER;
    issues_array JSONB := '[]'::jsonb;
    floor INTEGER;
    in_flow NUMERIC;
    out_flow NUMERIC;
BEGIN
    -- 获取建筑总楼层数
    SELECT MAX(floor_number) INTO total_floors
    FROM od_matrix_entry
    WHERE od_matrix_id = validate_od_matrix.od_matrix_id;
    
    -- 检查流量守恒
    FOR floor IN 1..total_floors LOOP
        -- 计算进入该楼层的流量
        SELECT COALESCE(SUM(passenger_count), 0) INTO in_flow
        FROM od_matrix_entry
        WHERE od_matrix_id = validate_od_matrix.od_matrix_id
          AND destination_floor = floor;
        
        -- 计算离开该楼层的流量
        SELECT COALESCE(SUM(passenger_count), 0) INTO out_flow
        FROM od_matrix_entry
        WHERE od_matrix_id = validate_od_matrix.od_matrix_id
          AND origin_floor = floor;
        
        -- 检查是否守恒
        IF ABS(in_flow - out_flow) > 0.001 * GREATEST(in_flow, out_flow) THEN
            issues_array := jsonb_set(issues_array, '{#}', jsonb_build_object(
                'type', 'FLOW_CONSERVATION_VIOLATION',
                'message', format('流量守恒违反: 楼层 %s, 出流量=%.1f, 入流量=%.1f', floor, out_flow, in_flow),
                'floor', floor,
                'in_flow', in_flow,
                'out_flow', out_flow
            ));
        END IF;
    END LOOP;
    
    -- 检查非负性
    IF EXISTS (
        SELECT 1 FROM od_matrix_entry
        WHERE od_matrix_id = validate_od_matrix.od_matrix_id
          AND passenger_count < 0
    ) THEN
        issues_array := jsonb_set(issues_array, '{#}', jsonb_build_object(
            'type', 'NEGATIVE_PASSENGER_COUNT',
            'message', '发现负乘客数'
        ));
    END IF;
    
    RETURN QUERY
    SELECT 
        jsonb_array_length(issues_array) = 0 AS is_valid,
        issues_array AS issues;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION validate_od_matrix(UUID) IS '验证OD矩阵是否满足流量守恒原则,对应PDF中描述的"OD matrix must satisfy the flow conservation principle"';

7.4.2 仿真结果聚合函数

CREATE OR REPLACE FUNCTION get_simulation_comparison(
    simulation_ids UUID[]
)
RETURNS TABLE (
    metric_name TEXT,
    simulation_id UUID,
    value NUMERIC
) AS $$
BEGIN
    RETURN QUERY
    -- 等待时间指标
    SELECT 'average_waiting_time', simulation_id, average_waiting_time
    FROM simulation_result
    WHERE simulation_id = ANY(simulation_ids)
    
    UNION ALL
    
    SELECT 'max_waiting_time', simulation_id, max_waiting_time
    FROM simulation_result
    WHERE simulation_id = ANY(simulation_ids)
    
    UNION ALL
    
    -- 运输时间指标
    SELECT 'average_travel_time', simulation_id, average_travel_time
    FROM simulation_result
    WHERE simulation_id = ANY(simulation_ids)
    
    UNION ALL
    
    SELECT 'max_travel_time', simulation_id, max_travel_time
    FROM simulation_result
    WHERE simulation_id = ANY(simulation_ids)
    
    UNION ALL
    
    -- 系统性能指标
    SELECT 'handling_capacity', simulation_id, handling_capacity
    FROM simulation_result
    WHERE simulation_id = ANY(simulation_ids);
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION get_simulation_comparison(UUID[]) IS '获取多个仿真的关键指标比较,用于PDF中描述的"不同控制策略的对比分析"';

7.4.3 电梯运行时间计算函数

CREATE OR REPLACE FUNCTION calculate_travel_time(
    from_floor INTEGER,
    to_floor INTEGER,
    floor_height NUMERIC,
    rated_speed NUMERIC,
    acceleration NUMERIC,
    deceleration NUMERIC
)
RETURNS NUMERIC AS $$
DECLARE
    distance NUMERIC;
    accel_distance NUMERIC;
    decel_distance NUMERIC;
    t_accel NUMERIC;
    t_decel NUMERIC;
    t_cruise NUMERIC;
BEGIN
    -- 计算距离
    distance := ABS(to_floor - from_floor) * floor_height;
    
    -- 计算加速段距离
    accel_distance := (rated_speed * rated_speed) / (2 * acceleration);
    -- 计算减速段距离
    decel_distance := (rated_speed * rated_speed) / (2 * deceleration);
    
    -- 如果距离不足以达到额定速度
    IF distance <= accel_distance + decel_distance THEN
        -- 三段式运行: 加速-减速
        RETURN 2 * SQRT(distance / acceleration);
    ELSE
        -- 三段式运行: 加速-匀速-减速
        t_accel := rated_speed / acceleration;
        t_decel := rated_speed / deceleration;
        t_cruise := (distance - accel_distance - decel_distance) / rated_speed;
        RETURN t_accel + t_cruise + t_decel;
    END IF;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION calculate_travel_time(INTEGER, INTEGER, NUMERIC, NUMERIC, NUMERIC, NUMERIC) IS '计算电梯理论运行时间,实现PDF中描述的"运行时间 = √(2×距离/加速度) + (距离-加速段-减速段)/额定速度 + √(2×距离/减速度)"';

7.5 数据库维护策略

7.5.1 数据归档策略

-- 创建归档表
CREATE TABLE simulation_archive (LIKE simulation INCLUDING ALL);
CREATE TABLE simulation_result_archive (LIKE simulation_result INCLUDING ALL);
CREATE TABLE simulation_timeline_archive (LIKE simulation_timeline INCLUDING ALL);

-- 归档函数
CREATE OR REPLACE FUNCTION archive_old_simulations(days_threshold INTEGER DEFAULT 90)
RETURNS INTEGER AS $$
DECLARE
    archived_count INTEGER := 0;
BEGIN
    -- 归档仿真主记录
    WITH archived_simulations AS (
        INSERT INTO simulation_archive
        SELECT * FROM simulation
        WHERE status = 'COMPLETED'
          AND end_time < NOW() - (days_threshold || ' days')::interval
        RETURNING id
    )
    -- 归档仿真结果
    INSERT INTO simulation_result_archive
    SELECT sr.* FROM simulation_result sr
    JOIN archived_simulations a ON sr.simulation_id = a.id;
    
    -- 归档时间切片数据(使用批量删除)
    WITH archived_simulations AS (
        SELECT id FROM simulation
        WHERE status = 'COMPLETED'
          AND end_time < NOW() - (days_threshold || ' days')::interval
    )
    DELETE FROM simulation_timeline
    WHERE simulation_id IN (SELECT id FROM archived_simulations)
    RETURNING 1 INTO archived_count;
    
    -- 最后删除主表记录
    DELETE FROM simulation
    WHERE status = 'COMPLETED'
      AND end_time < NOW() - (days_threshold || ' days')::interval;
      
    RETURN archived_count;
END;
$$ LANGUAGE plpgsql;

-- 创建定期归档任务
SELECT cron.schedule(
    'archive-simulations',
    '0 2 * * *',  -- 每天凌晨2点
    $$SELECT archive_old_simulations()$$
);

7.5.2 性能监控与优化

-- 创建性能监控表
CREATE TABLE db_performance_metrics (
    id BIGSERIAL PRIMARY KEY,
    metric_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
    metric_name VARCHAR(100) NOT NULL,
    metric_value NUMERIC NOT NULL,
    additional_info JSONB
);

-- 创建收集性能指标的函数
CREATE OR REPLACE FUNCTION collect_db_performance_metrics()
RETURNS VOID AS $$
BEGIN
    -- 插入缓存命中率
    INSERT INTO db_performance_metrics (metric_name, metric_value, additional_info)
    SELECT 
        'cache_hit_ratio',
        SUM(blks_hit) * 100.0 / (SUM(blks_hit) + SUM(blks_read)) AS cache_hit_ratio,
        jsonb_build_object('total_blocks', SUM(blks_hit) + SUM(blks_read))
    FROM pg_statio_user_tables;
    
    -- 插入慢查询统计
    INSERT INTO db_performance_metrics (metric_name, metric_value, additional_info)
    SELECT 
        'slow_queries',
        COUNT(*),
        jsonb_build_object(
            'avg_duration', AVG(total_time),
            'max_duration', MAX(total_time)
        )
    FROM pg_stat_statements
    WHERE total_time > 100; -- 超过100ms的查询
    
    -- 插入索引使用率
    INSERT INTO db_performance_metrics (metric_name, metric_value, additional_info)
    SELECT 
        'index_usage_ratio',
        SUM(idx_scan) * 100.0 / GREATEST(SUM(idx_scan) + SUM(seq_scan), 1) AS index_usage_ratio,
        jsonb_build_object('total_scans', SUM(idx_scan) + SUM(seq_scan))
    FROM pg_stat_user_tables;
END;
$$ LANGUAGE plpgsql;

-- 创建定期收集任务
SELECT cron.schedule(
    'collect-db-metrics',
    '*/15 * * * *',  -- 每15分钟
    $$SELECT collect_db_performance_metrics()$$
);

7.5.3 数据验证与修复

-- 验证OD矩阵并修复
CREATE OR REPLACE FUNCTION repair_inconsistent_od_matrix()
RETURNS TABLE (fixed_od_matrix_id UUID, issues_fixed JSONB) AS $$
DECLARE
    od_matrix_record RECORD;
    issues JSONB;
    is_valid BOOLEAN;
BEGIN
    FOR od_matrix_record IN 
        SELECT id FROM od_matrix
    LOOP
        -- 验证OD矩阵
        SELECT is_valid, issues INTO is_valid, issues
        FROM validate_od_matrix(od_matrix_record.id);
        
        IF NOT is_valid THEN
            -- 尝试修复流量守恒问题
            IF issues->'issues'->0->>'type' = 'FLOW_CONSERVATION_VIOLATION' THEN
                -- 简单修复:按比例调整
                PERFORM adjust_od_matrix_for_flow_conservation(od_matrix_record.id);
                issues_fixed := jsonb_build_array(jsonb_build_object(
                    'type', 'FLOW_CONSERVATION_REPAIRED',
                    'message', '已修复流量守恒问题'
                ));
                
                RETURN QUERY
                SELECT od_matrix_record.id, issues_fixed;
            END IF;
        END IF;
    END LOOP;
END;
$$ LANGUAGE plpgsql;

-- 辅助函数:调整OD矩阵以满足流量守恒
CREATE OR REPLACE FUNCTION adjust_od_matrix_for_flow_conservation(od_matrix_id UUID)
RETURNS VOID AS $$
DECLARE
    total_floors INTEGER;
    floor INTEGER;
    in_flow NUMERIC;
    out_flow NUMERIC;
    adjustment_factor NUMERIC;
BEGIN
    -- 获取建筑总楼层数
    SELECT MAX(floor_number) INTO total_floors
    FROM od_matrix_entry
    WHERE od_matrix_id = adjust_od_matrix_for_flow_conservation.od_matrix_id;
    
    -- 调整每个楼层的流出量以匹配流入量
    FOR floor IN 1..total_floors LOOP
        -- 计算进入该楼层的流量
        SELECT COALESCE(SUM(passenger_count), 0) INTO in_flow
        FROM od_matrix_entry
        WHERE od_matrix_id = adjust_od_matrix_for_flow_conservation.od_matrix_id
          AND destination_floor = floor;
        
        -- 计算离开该楼层的流量
        SELECT COALESCE(SUM(passenger_count), 0) INTO out_flow
        FROM od_matrix_entry
        WHERE od_matrix_id = adjust_od_matrix_for_flow_conservation.od_matrix_id
          AND origin_floor = floor;
        
        -- 如果流出不等于流入,进行调整
        IF out_flow > 0 AND in_flow <> out_flow THEN
            adjustment_factor := in_flow / out_flow;
            
            -- 调整该楼层作为起点的所有条目
            UPDATE od_matrix_entry
            SET passenger_count = passenger_count * adjustment_factor,
                probability = probability * adjustment_factor
            WHERE od_matrix_id = adjust_od_matrix_for_flow_conservation.od_matrix_id
              AND origin_floor = floor;
        END IF;
    END LOOP;
    
    -- 重新计算概率
    WITH total_passengers AS (
        SELECT SUM(passenger_count) AS total
        FROM od_matrix_entry
        WHERE od_matrix_id = adjust_od_matrix_for_flow_conservation.od_matrix_id
    )
    UPDATE od_matrix_entry ome
    SET probability = ome.passenger_count::NUMERIC / tp.total
    FROM total_passengers tp
    WHERE ome.od_matrix_id = adjust_od_matrix_for_flow_conservation.od_matrix_id;
END;
$$ LANGUAGE plpgsql;

开发文档下一章节标题:8. 附录:关键公式、参数设置建议