Mat1lda
@Mat1lda

Как правильно стерилизовать такой запрос Golang + Postgres?

Собственно есть такой запрос
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;


его вывод это поля u.username, p.post_id, p.post_title, p.post_text
причём для каждого поста выводится username автора, как правильно это оформить в структуру вида

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


Гуглил но примеров сложнее чем select 2 поля одной таблицы не нашёл...
  • Вопрос задан
  • 100 просмотров
Решения вопроса 1
@ghostiam
На Go писатель, серверов пинатель.
На самом деле, здесь несколько решений задачи и все они реализуются в момент скана таблицы.

Сначала вам нужно будет вытащить все данные, либо в какие нибудь переменные, либо в промежуточную структуру. Если полей мало, то можно в переменные, если много, то удобнее автомапинг на структуру (sqlx со сканом по строке или sqlstruct)

Я сделаю примеры на основе ручного сканирования, чтобы было понятнее.

Наша основа:
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
}


Вариант 1. Более эффективный. Необходима сортировка по 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
}


Вариант 2. Более затратный по памяти и тактам процессора, но более простой в написании.
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
}
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы