M
M
Mat1lda2020-10-14 22:23:45
PostgreSQL
Mat1lda, 2020-10-14 22:23:45

What is the correct way to sterilize such a Golang + Postgres query?

Actually there is such a request

select u.username, p.post_id, p.post_title, p.post_text
from posts p join users u on u.user_id = p.post_author
group by u.username, p.post_id;


its output is the fields u.username, p.post_id, p.post_title, p.post_text
and for each post the username of the author is displayed, how to format it correctly in a structure like

type userposts struct {
username string
posts [массив постов]
}


I googled, but I didn’t find any examples more complicated than select 2 fields of one table ...

Answer the question

In order to leave comments, you need to log in

2 answer(s)
V
Vladislav, 2020-10-15
@Mat1lda

In fact, there are several solutions to the problem, and all of them are implemented at the time of the table scan.
First you will need to pull out all the data, either into some variables or into an intermediate structure. If there are few fields, then you can use variables, if there are many, then automapping to a structure is more convenient ( sqlx with line scan or sqlstruct )
I will make examples based on manual scanning to make it clearer.
Our foundation:

spoiler
type Post struct {
  ID int64
  Title string
  Text string
}

type UserPosts struct {
  Username string
  Posts    []Post
}

func qna864719(db *sql.DB) ([]UserPosts, error) {
  query := `select u.username
     , p.post_id
     , p.post_title
     , p.post_text
from posts p
         join users u on u.user_id = p.post_author
group by u.username, p.post_id;`

  rows, err := db.Query(query)
  if err != nil {
    return nil, fmt.Errorf("query: %w", err)
  }
  defer rows.Close()

  var result []UserPosts
  for rows.Next() {
    var username string
    var post Post

    err = rows.Scan(&username, &post.ID, &post.Title, &post.Text)
    if err != nil {
      return nil, fmt.Errorf("scan: %w", err)
    }

    // Здесь логика преобразования результата в массив UserPosts
  }

  err = rows.Err()
  if err != nil {
    return nil, fmt.Errorf("after scan: %w", err)
  }

  return result, nil
}


Option 1. More efficient. Need to sort by Username (order by u.username)
spoiler
func qna864719_1(db *sql.DB) ([]UserPosts, error) {
  query := `select u.username
     , p.post_id
     , p.post_title
     , p.post_text
from posts p
         join users u on u.user_id = p.post_author
group by u.username, p.post_id
order by u.username` // <-------- ОБЯЗАТЕЛЬНАЯ СОРТИРОВКА

  rows, err := db.Query(query)
  if err != nil {
    return nil, fmt.Errorf("query: %w", err)
  }
  defer rows.Close()

  var result []UserPosts

  // Храним промежуточный результат по юзеру в переменной,
  // перед добавлением в основной массив
  var lastResult *UserPosts // <--------
  for rows.Next() {
    var username string
    var post Post

    err = rows.Scan(&username, &post.ID, &post.Title, &post.Text)
    if err != nil {
      return nil, fmt.Errorf("scan: %w", err)
    }

    // Здесь логика преобразования результата в массив UserPosts

    // Если промежуточный результат существует, но username отличается
    // от текущего, то добавляем промежуточный результат в основной массив
    // обнуляя промежуточный результат
    if lastResult != nil && lastResult.Username != username {
      result = append(result, *lastResult)
      lastResult = nil
    }

    // Если промежуточного результата нет, иницилизируем его
    if lastResult == nil {
      lastResult = &UserPosts{Username: username}
    }

    // Добавляем посты
    lastResult.Posts = append(lastResult.Posts, post)
  }

  err = rows.Err()
  if err != nil {
    return nil, fmt.Errorf("after scan: %w", err)
  }

  // После выхода из сканирования, у нас может остаться промежуточный результат
  // который необходимо добавить в основной массив
  if lastResult != nil {
    result = append(result, *lastResult)
  }

  return result, nil
}

Option 2. More expensive in terms of memory and processor cycles, but easier to write.
spoiler
func qna864719_2(db *sql.DB) ([]UserPosts, error) {
  query := `select u.username
     , p.post_id
     , p.post_title
     , p.post_text
from posts p
         join users u on u.user_id = p.post_author
group by u.username, p.post_id;`

  rows, err := db.Query(query)
  if err != nil {
    return nil, fmt.Errorf("query: %w", err)
  }
  defer rows.Close()

  var result []UserPosts

  // Иницилизируем хеш-мап который будет содержать посты по username
  postsByUsername := make(map[string][]Post)
  for rows.Next() {
    var username string
    var post Post

    err = rows.Scan(&username, &post.ID, &post.Title, &post.Text)
    if err != nil {
      return nil, fmt.Errorf("scan: %w", err)
    }

    // Здесь логика преобразования результата в массив UserPosts

    // Добавляем посты в мап
    postsByUsername[username] = append(postsByUsername[username], post)
  }

  err = rows.Err()
  if err != nil {
    return nil, fmt.Errorf("after scan: %w", err)
  }

  // Преобразовываем мап в массив
  for username, posts := range postsByUsername {
    result = append(result, UserPosts{
      Username: username,
      Posts:    posts,
    })
  }

  // Сортируем, так как в мапе записи хранятся в "случайном" порядке
  sort.Slice(result, func(i, j int) bool {
    return result[i].Username < result[j].Username
  })

  return result, nil
}

A
alfss, 2020-10-15
@alfss

type posts struct {
username string
postid
etc
}

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question