处理数据库查询中可能的空值的正确/惯用方式是什么?

问题描述

我正在处理Go中的多对多关系。为此,我使用的是pgx Postgresql驱动程序。

为了使这个问题尽可能简单,我们假设一个简单的博客文章可以包含一些标签

CREATE TABLE IF NOT EXISTS tag (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,tagName varchar(255) UNIQUE NOT NULL,);

CREATE TABLE IF NOT EXISTS post (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,title varchar(255) NOT NULL,description varchar(255),);

CREATE TABLE IF NOT EXISTS post_tag (
    post_id bigint REFERENCES post (id) ON UPDATE CASCADE ON DELETE CASCADE,tag_id bigint REFERENCES tag (id) ON UPDATE CASCADE ON DELETE CASCADE,CONSTRAINT post_tag_pkey PRIMARY KEY (post_id,tag_id)
);

要检索帖子及其标签,我使用类似以下查询内容(在视图中便于在Go中查询):

SELECT p.id as post_id,p.title,p.description,t.id as tag_id,t.tagName as tag_name
    FROM post p 
    LEFT JOIN post_tag pt
    ON p.id = pt.post_id
    LEFT JOIN tag t 
    ON t.id = pt.tag_id;

查询可能会返回一些行,其中tag_idtag_name为空。我当前的处理方式如下(为简单起见,删除错误处理):

func ReadPosts() ([]*model.Post,error) {
    var posts []*model.Post
    var postWithTags = make(map[uint64]*model.Post)

    statement := `SELECT *
                    FROM post_with_tag` // pgsql view with joins to get tags

    rows,_ := db.Query(
        context.Background(),statement,)

    for rows.Next() {
        var (
            post     model.Post
            tag      model.Tag
            tagID    pgtype.Numeric
            tagName  pgtype.Varchar
            tagValid bool
        )

        _ = rows.Scan(
            &post.ID,&post.Title,&post.Description,&tagID,&tagName,)

        if tagID.Status == pgtype.Present {
            tag.ID = tagID.Int.Uint64()
            tag.Name = tagName.String
            tagValid = true
        } else {
            tagValid = false
        }

        if _,ok := postWithTags[post.ID]; ok {
            if tagValid {
                postWithTags[post.ID].Tags = append(postWithTags[post.ID].Tags,&tag)
            }
        } else {
            post.Tags = []*model.Tag{}

            if tagValid {
                post.Tags = []*model.Tag{
                    &tag,}
            }

            postWithTags[post.ID] = &post
        }
    }

    for _,v := range postWithTags {
        posts = append(posts,v)
    }

    return posts,nil
}

如您所见,我正在使用pgtype处理潜在的空值。我应该提到解决方案有效。但是,我有两个问题:

  1. 解决方案似乎很笨拙且混乱;读起来很复杂(至少对我来说)。 是否有更好的,更惯用的Go方式?
  2. 调用tagID.Int.Uint64()时,总是返回0作为标签ID,这是不正确的。 在这里做错什么了吗?(我使用pgtype.Numeric是因为数据库中的标记ID是pgsql bigint)。

解决方法

如果您使用的是表格视图,而无需按<html> <head> <title>Question 2</title> <script> var images = ["frog.jpg","lion.jpg"] //I Created an array with both images inside var imgInterval; var imgi = 'empty'; var score = document.getElementById('score'); function start(){ imgInterval = setInterval(displayImage,500); //Sets an interval for the func displayImage and for it to loop ever 500ms } function displayImage() { if (imgi == 'empty') { //If the index is empty set the interval to 0 or frog.jpg imgi = 0; } else if (imgi == 0) { // If the index is set to 0 set it to 1 or lion.jpg imgi = 1; } else if (imgi == 1) { // If the idex is set to 1 set it back to 0,this creates a loop that will be run every 500ms imgi = 0; } document.getElementById('image').src = images[imgi]; } function stop(){ clearInterval(imgInterval); } function scoreNumb() { //This is where i think i am having issues and am unsure what to do. if (imgi.onclick == 0) { score + 1; } } </script> </head> <body> <button onclick=start()>Start Animation</button> <button onclick=stop()>Stop Animation</button> <br/> <br/> <span> <img id="image" onclick=scoreNumb() width="250px" /> </span> <br/> <span id="score">0</span> </body> </html> (例如NULL)进行过滤,则可能只想在视图中使用WHERE col IS [NOT] NULL,您可以在Go结束时省掉一些头痛的方法。如果您直接处理表,仍然可以在Go中构造的SQL COALESCE字符串中使用COALESCE,但是如果选择这样做,将无法使用它与statement一起使用,而是必须显式列出这些列。

如果您不喜欢SELECT *方法,则可以实现自己的COALESCE,该方法不需要一个值字段,而是一个指针字段,该指针字段允许您通过以下方式设置模型字段:间接扫描与您扫描列所在的同一行。

sql.Scanner

_ = rows.Scan( &post.ID,&post.Title,&post.Description,MyNullNumeric{&tag.ID},MyNullString{&tag.Name},) 可能看起来像这样:

MyNullXxx