Ответы пользователя по тегу JSON
  • Как правильно стерилизовать такой запрос Golang + Postgres?

    @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
    }
    Ответ написан
    Комментировать