Django - Создание модели, хранящей агрегированные значения внешних ключей

У меня есть Django Model, определенный как ServiceEvent, и другой, определенный как Part.

class ServiceEvent(Model):
    date = DateTimeField(null=False, blank=False, db_column='date', 
                         default=datetime.now())
    vehicle = ForeignKey(Vehicle, on_delete=models.CASCADE)
    description = TextField(null=True, blank=False, db_column='description')
    
    # The contents of the notifications to create for this event.
    notification_contents = TextField(null=True, blank=False, 
                                      db_column='notification_contents')
    
    # The mileage the event should occur.
    target_mileage = DecimalField(max_digits=10, decimal_places=2, 
                                  null=True, blank=False, 
                                  db_column='target_mileage')
    
    # The mileage at the time of the event.
    event_mileage = DecimalField(max_digits=10, decimal_places=2, 
                                 null=True, blank=False, 
                                 db_column='event_mileage')

class Part(Model):
    part_number = CharField(max_length=100, null=False, blank=False, 
                            db_column='part_number')
    description = CharField(max_length=100, null=False, blank=False, 
                            db_column='description')
    price = DecimalField(max_digits=10, decimal_places=2, null=False, 
                         db_column='price')
    service_event = ForeignKey(ServiceEvent, on_delete=models.CASCADE)

На одного ServiceEvent может приходиться несколько Parts.

Каким образом можно сохранить в таблице ServiceEvent столбец, содержащий цену всех сложенных деталей, относящихся к данному событию?

Я хотел бы сделать это для эффективности.

Какой подход лучше (например, использование таблицы отображения, что может привести к тому же вопросу о столбце)?

Я использую SQLite.

Каков способ хранения столбца в таблице ServiceEvent, содержащего цену всех сложенных деталей, относящихся к данному событию?

Вы могли бы сделать это с помощью TRIGGER НО скорее всего в этом нет необходимости, так как sum цены могут быть сгенерированы/вычислены/выведены из существующих данных при извлечении данных.

Демонстрация

Вот демонстрация, основанная на схеме в соответствии с вашим кодом:-

/* Cleanup test environment (just in case)*/
DROP TABLE IF EXISTS part;
DROP TABLE IF EXISTS serviceevent;
DROP TABLE IF EXISTS vehicle;
CREATE TABLE IF NOT EXISTS vehicle (
    description TEXT PRIMARY KEY, 
    otherData TEXT DEFAULT 'blah'
);
CREATE TABLE IF NOT EXISTS serviceevent (
    date TEXT PRIMARY KEY DEFAULT CURRENT_TIMESTAMP,
    description TEXT REFERENCES vehicle(description) ON DELETE CASCADE ON UPDATE CASCADE,
    notification_contents TEXT,
    target_mileage REAL,
    event_mileage REAL

);
CREATE TABLE IF NOT EXISTS part (
    part_number TEXT PRIMARY KEY,
    description TEXT,
    price REAL,
    service_event TEXT REFERENCES serviceevent ON DELETE CASCADE ON  UPDATE CASCADE
);
/* Add some vehicles */
INSERT INTO vehicle (description) VALUES ('CAR001'),('CAR002'),('CAR003');
/* Add some serviceevents for the vehicles */
INSERT INTO serviceevent VALUES
    ('2023-03-14 10:20:00','CAR001','10k Service',10000.00,9100.01),
    ('2024-03-13 11:00:00','CAR001','20k Service',20000.00,19100.01),
    ('2023-03-15 14:20:00','CAR002','10k Service',10000.00,9100.02),
    ('2024-03-16 15:00:00','CAR002','20k Service',20000.00,19100.02),
    ('2023-03-17 18:20:00','CAR003','10k Service',10000.00,9100.01),
    ('2024-03-18 19:00:00','CAR003','20k Service',20000.00,19100.01)
;
/* add some parts to service events (not all for brevity)*/
INSERT INTO part VALUES
    /* 10k service for Car001 (i.e. serviceevent '2023-03-14 10:20:00')*/
    ('p001','Air Filter',25.68,'2023-03-14 10:20:00'),
    ('p002','Spark Plug Set',15.20,'2023-03-14 10:20:00'),
    ('p003','OIL (5L)',32.32,'2023-03-14 10:20:00'),
    /* 10k service for Car002 (i.e. serviceevent '2023-03-15 14:20:00)*/
    /* Note just the 2 parts */
    ('p004','Air Filter',25.68,'2023-03-15 14:20:00'),
    ('p005','Spark Plug Set',15.20,'2023-03-15 14:20:00')
;
SELECT 
    vehicle.*, /* include ALL of the vehicle columns in the output */
    serviceevent.*, /* include ALL of the serviceevent columns in the output */
    
    /* Use Aggregate functions to show all the parts and the sum of the prices */
    group_concat(part.description),sum(part.price) AS total_parts_price
FROM vehicle 
    JOIN serviceevent ON vehicle.description = serviceevent.description
    JOIN part ON part.service_event = serviceevent.date
GROUP BY service_event /* group the output insto sets per service event (to which the aggregate functions apply) */
;
/* Cleanup test environment (After run)*/
DROP TABLE IF EXISTS part;
DROP TABLE IF EXISTS serviceevent;
DROP TABLE IF EXISTS vehicle;

В результате получается:-

enter image description here

  • При обслуживании 10k для CAR001, выполненном 14/03/2023 @ 10:20, были использованы детали (воздушный фильтр, масляный фильтр и масло) на общую сумму $73.20
  • .
  • в 10-тысячном сервисе для Car002, выполненном 15/03/2023 @ 14:20, были/использованы детали (воздушный фильтр и масляный фильтр) на общую сумму $40.88
  • т.е. ожидаемые результаты БЕЗ необходимости дополнительной колонки

Экстра

Вы даже можете сделать что-то вроде следующего:-

WITH cte AS (
    SELECT 
        'Service for '||vehicle.description||
        ' done on '||serviceevent.date||
        ' re '||serviceevent.notification_contents||' ('||serviceevent.target_mileage||' miles)'||
        ' @ '||serviceevent.event_mileage||' miles.' AS printline,
        1 AS printorder,
        serviceevent.date
    FROM vehicle
        JOIN serviceevent ON vehicle.description = serviceevent.description
    UNION ALL SELECT
        '\t'||'Part '||part.description||' Cost $'||part.price,
        2,
        serviceevent.date
    FROM serviceevent JOIN part ON part.service_event = serviceevent.date
    UNION ALL SELECT
        '\t\tTotal Parts Cost $'||sum(part.price),3,part.service_event
    FROM serviceevent JOIN PART ON part.service_event = serviceevent.date
    GROUP BY service_event
)
SELECT printline FROM cte ORDER BY date,printorder
;

Который создаст грубый счет-фактуру с подробным указанием деталей и общей суммы:-

enter image description here

  • снова показывает, что столбцу не нужно хранить значение, которое может быть получено из базовых данных.
Вернуться на верх