使用 SQLite 在 Go 中设置数据库模型

问题描述 投票:0回答:1

我正在用 Go 构建一个简单的 CRUD 应用程序,它具有以下数据模型:

enter image description here

我对设置 FilmGroup 模型的正确方法有点困惑。它与 Film 是一对多的关系,与 FilmOrder 是一对一的关系。

我的其他实体模型设置如下:

type Film struct {
    ID                 int     `db:"id"`
    FilmName           string  `db:"film_name"`
    FilmFormat         *string `db:"film_format"`
    FilmProcessingType *string `db:"film_processing_type"`
    ScansFolder        *string `db:"scans_folder"`
    RollLabel          *string `db:"roll_label"`
    Comments           *string `db:"comments"`
    DateShot           *string `db:"date_shot"`
    ScanCompleted      *bool   `db:"scan_completed"`
    CutAndSleeved      *bool   `db:"cut_and_sleeved"`
}
type FilmOrder struct {
    ID                   int        `db:"id"`
    FilmLabName          string     `db:"film_lab_name"`
    ToLabTracking        *string    `db:"to_lab_tracking"`
    FromLabTracking      *string    `db:"from_lab_tracking"`
    DateFilmReceivedByLab *time.Time `db:"date_film_received_by_lab"`
    DateFilmReceivedByMe *time.Time `db:"date_film_received_by_me"`
    DateOrdered          *time.Time `db:"date_ordered"`
    DateMailedToLab      *time.Time `db:"date_mailed_to_lab"`
    ScansRequested       *bool      `db:"scans_requested"`
    ScansReceived        *bool      `db:"scans_received"`
    DateScansReceivedByMe *time.Time `db:"date_scans_received_by_me"`
}

对于 FilmGroup,我将其设置为:

type FilmGroup struct {
    ID        int        `db:"id"`
    Films     []Film     `db:"films"`
    FilmOrderID *int      `db:"film_order_id"`
    FilmOrder *FilmOrder
    Status    string     `db:"status"`
    Comments  *string    `db:"comments"`
}

我似乎同时需要 FilmOrderID 和 FilmOrder,因为我需要将 FilmOrderID 与此模型关联,但我也希望轻松访问与其关联的 FilmOrder,因为我将所有数据呈现给用户。

我必须在 AddFilmGroup 方法中执行以下操作并仅传入 FilmOrderID:

func AddFilmGroup(db *sql.DB, filmGroup models.FilmGroup) error {
    insertGroupQuery := `
        INSERT INTO FilmGroup (status, comments, film_order_id)
        VALUES (?, ?, ?);`
    result, err := db.Exec(insertGroupQuery, filmGroup.Status, filmGroup.Comments, filmGroup.FilmOrderID)
    if err != nil {
        return fmt.Errorf("failed to insert FilmGroup: %w", err)
    }

    groupID, err := result.LastInsertId()
    if err != nil {
        return fmt.Errorf("failed to retrieve FilmGroup ID: %w", err)
    }

    for _, film := range filmGroup.Films {
        insertFilmGroupFilmQuery := `
            INSERT INTO FilmGroup_Film (film_group_id, film_id)
            VALUES (?, ?);`
        _, err := db.Exec(insertFilmGroupFilmQuery, groupID, film.ID)
        if err != nil {
            return fmt.Errorf("failed to associate Film %d with FilmGroup %d: %w", film.ID, groupID, err)
        }
    }

    log.Printf("FilmGroup '%d' added successfully.\n", groupID)
    return nil
}

这也使得获取这个有点复杂

func GetFilmGroupWithDetails(db *sql.DB, groupID int) (models.FilmGroup, error) {
    var filmGroup models.FilmGroup
    
    query := `
        SELECT id, status, comments, film_order_id
        FROM FilmGroup
        WHERE id = ?;`
    err := db.QueryRow(query, groupID).Scan(&filmGroup.ID, &filmGroup.Status, &filmGroup.Comments, &filmGroup.FilmOrderID)
    if err != nil {
        if err == sql.ErrNoRows {
            return filmGroup, fmt.Errorf("FilmGroup with ID %d not found", groupID)
        }
        return filmGroup, fmt.Errorf("failed to fetch FilmGroup: %w", err)
    }

    // Fetch the associated films
    filmQuery := `
        SELECT f.id, f.film_name, f.film_format, f.film_processing_type, f.scans_folder, f.roll_label, f.comments, f.date_shot, f.scan_completed, f.cut_and_sleeved
        FROM Film f
        JOIN FilmGroup_Film fgf ON f.id = fgf.film_id
        WHERE fgf.film_group_id = ?;`
    rows, err := db.Query(filmQuery, groupID)
    if err != nil {
        return filmGroup, fmt.Errorf("failed to fetch films for FilmGroup %d: %w", groupID, err)
    }
    defer rows.Close()

    var films []models.Film
    for rows.Next() {
        var film models.Film
        var filmFormat, filmProcessingType, scansFolder, rollLabel, comments, dateShot sql.NullString
        var scanCompleted, cutAndSleeved sql.NullBool

        if err := rows.Scan(&film.ID, &film.FilmName, &filmFormat, &filmProcessingType, &scansFolder, &rollLabel, &comments, &dateShot, &scanCompleted, &cutAndSleeved); err != nil {
            return filmGroup, fmt.Errorf("failed to scan film row: %w", err)
        }

        // Handle nullable fields
        // Redacted
        
        films = append(films, film)
    }
    if err := rows.Err(); err != nil {
        return filmGroup, fmt.Errorf("error iterating film rows: %w", err)
    }
    filmGroup.Films = films

    var filmOrder models.FilmOrder
    if filmGroup.FilmOrderID != nil {
        filmOrderQuery := `
            SELECT id, film_lab_name, to_lab_tracking, from_lab_tracking, date_film_received_by_lab, date_film_received_by_me, date_ordered, date_mailed_to_lab, scans_requested, scans_received, date_scans_received_by_me
            FROM FilmOrder
            WHERE id = ?;`
        err = db.QueryRow(filmOrderQuery, *filmGroup.FilmOrderID).Scan(&filmOrder.ID, &filmOrder.FilmLabName, &filmOrder.ToLabTracking, &filmOrder.FromLabTracking, &filmOrder.DateFilmReceivedByLab, &filmOrder.DateFilmReceivedByMe, &filmOrder.DateOrdered, &filmOrder.DateMailedToLab, &filmOrder.ScansRequested, &filmOrder.ScansReceived, &filmOrder.DateScansReceivedByMe)
        if err != nil {
            if err == sql.ErrNoRows {
                return filmGroup, fmt.Errorf("FilmOrder not found for FilmGroup %d", groupID)
            }
            return filmGroup, fmt.Errorf("failed to fetch FilmOrder: %w", err)
        }
        filmGroup.FilmOrder = &filmOrder
    }

    filmGroup.FilmOrder = &filmOrder

    return filmGroup, nil
}

也供参考,我的数据库设置如下:

    CREATE TABLE IF NOT EXISTS Film (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        film_name TEXT NOT NULL,
        film_format TEXT,
        film_processing_type TEXT,
        scans_folder TEXT,
        roll_label TEXT,
        comments TEXT,
        date_shot TEXT,
        scan_completed BOOLEAN,
        cut_and_sleeved BOOLEAN
    );


    CREATE TABLE IF NOT EXISTS FilmOrder (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        film_lab_name TEXT NOT NULL,
        to_lab_tracking TEXT,
        from_lab_tracking TEXT,
        date_film_received_by_lab DATE,
        date_film_received_by_me DATE,
        date_ordered DATE,
        date_mailed_to_lab DATE,
        scans_requested BOOLEAN,
        scans_received BOOLEAN,
        date_scans_received_by_me DATE
    );
    
    CREATE TABLE IF NOT EXISTS FilmGroup (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        status TEXT,
        comments TEXT,
        film_order_id INTEGER,
        FOREIGN KEY (film_order_id) REFERENCES FilmOrders (id)
    );

    // Junction table
    CREATE TABLE IF NOT EXISTS FilmGroup_Film (
        film_group_id INTEGER NOT NULL,
        film_id INTEGER NOT NULL,
        PRIMARY KEY (film_group_id, film_id),
        FOREIGN KEY (film_group_id) REFERENCES FilmGroup (id),
        FOREIGN KEY (film_id) REFERENCES Film (id)
    );

有没有一种方法可以简化 FilmGroup 模型,使其不需要同时具有 FilmOrderID 和对 FilmOrder 对象的引用即可正常工作?我不确定如何将

FilmOrder *FilmOrder
映射到数据库字段。我想对数据库模型使用相同的结构,并在可能的情况下也显示对象。

sqlite go
1个回答
0
投票

...电影集团模型。它与 Film 是一对多的关系,与 FilmOrder 是一对一的关系。

一对一的关系值得怀疑并且难以执行。

这里有一个更简单的模型,假设一部电影只能属于一个 FilmGroup,而一个 FilmOrder 只能属于一个 FilmGroup。 Film 和 FilmOrder 均引用 FilmGroup。

Film -> FilmGroup <- FilmOrder

如果一部电影可以属于多个 FilmGroup,您可以将 FilmGroup_Film 连接表放回原处,但 FilmOrder 仍会引用 FilmGroup。

Film <- FilmGroup_Film -> FilmGroup <- FilmOrder

注释

  • 避免
    create table if not exists
    。这看起来很方便,但会发生的情况是您将创建表,更改
    create table if not exists
    语句,再次运行脚本,然后花几个小时试图找出它不起作用的原因。
  • 在 SQLite 中不需要自动增量
  • 考虑使用时间戳而不是布尔值,这样您就可以跟踪它发生的时间。 Null 表示它没有发生。这很方便,可以避免冗余数据来跟踪时间戳。
  • 使用时间戳而不是日期以获得更好的跟踪精度,除非您真的只想存储日期。您稍后无法恢复精度,但可以将时间戳截断为日期。
  • 考虑使用 *_on 和 *_at 约定。实验室收到的电影
  • 考虑制作一个表来存储胶片实验室以实现引用完整性,最终您可能会想要存储有关实验室的额外信息。
© www.soinside.com 2019 - 2024. All rights reserved.