Go: удаление повторяющихся строк после результата соединения SQL

Я выполняю объединенный SQL-запрос для местоположений и событий (происходящих в этих местах). В результатах, естественно, данные о местоположении реплицируются для каждой строки, поскольку существует отношение «один ко многим»: одно местоположение содержит несколько событий.

Каков оптимальный подход к очистке размноженных данных о местоположении?

Оставаясь с одной операцией SQL, наиболее целесообразно выполнять проверку при циклическом просмотре результатов запроса (строк).

Однако я не могу получить доступ к объекту местоположения, чтобы проверить уже существующий идентификатор местоположения.

Изменить: это вывод SQL. Как видите, данные о местоположении естественным образом появляются несколько раз, потому что они являются общими для разных событий. В конечном итоге это будет отправлено в формате JSON с вложенными структурами, одна для местоположений, другая для событий.

id  title           latlng                  id  title           locationid  
1   Fox Thea...     43.6640673,-79.4213863  1   Bob's Event     1
1   Fox Thea...     43.6640673,-79.4213863  2   Jill's Event    1
2   Wrigley ...     43.6640673,-79.4213863  3   Mary's Event    2
3   Blues Bar       43.6640673,-79.4213863  4   John's Event    3
1   Fox Thea...     43.6640673,-79.4213863  5   Monthly G...    1
1   Fox Thea...     43.6640673,-79.4213863  6   A Special...    1
1   Fox Thea...     43.6640673,-79.4213863  7   The Final...    1

Вывод JSON. Как вы видите, данные о местоположении умножаются, создавая файл JSON большего размера.

   {
        "Locations": [
            {
                "ID": 1,
                "Title": "Fox Theatre",
                "Latlng": "43.6640673,-79.4213863",
            },
            {
                "ID": 1,
                "Title": "Fox Theatre",
                "Latlng": "43.6640673,-79.4213863",
            },
            {
                "ID": 2,
                "Title": "Wrigley Field",
                "Latlng": "43.6640673,-79.4213863",
            },
            {
                "ID": 3,
                "Title": "Blues Bar",
                "Latlng": "43.6640673,-79.4213863",
            },
            {
                "ID": 1,
                "Title": "Fox Theatre",
                "Latlng": "43.6640673,-79.4213863",
            },
            {
                "ID": 1,
                "Title": "Fox Theatre",
                "Latlng": "43.6640673,-79.4213863",
            },
            {
                "ID": 1,
                "Title": "Fox Theatre",
                "Latlng": "43.6640673,-79.4213863",
            }
        ],
        "Events": [
            {
                "ID": 1,
                "Title": "Bob's Event",
                "Location": 1
            },
            {
                "ID": 2,
                "Title": "Jill's Event",
                "Location": 1
            },
            {
                "ID": 3,
                "Title": "Mary's Event",
                "Location": 2
            },
            {
                "ID": 4,
                "Title": "John's Event",
                "Location": 3
            },
            {
                "ID": 5,
                "Title": "Monthly Gathering",
                "Location": 1
            },
            {
                "ID": 6,
                "Title": "A Special Event",
                "Location": 1
            },
            {
                "ID": 7,
                "Title": "The Final Contest",
                "Location": 1
            }
        ]

    }

Структуры:

// Event type
type Event struct {
    ID int `schema:"id"`
    Title string `schema:"title"`
    LocationID int `schema:"locationid"`
}

// Location type
type Location struct {
    ID int `schema:"id"`
    Title string `schema:"title"`
    Latlng string `schema:"latlng"`
}

// LocationsEvents type
type LocationsEvents struct {
    Locations []Location `schema:"locations"`
    Events []Event `schema:"events"`
}

Функция, выполняющая запрос и перебирающая строки:

func getLocationsEvents(db *sql.DB, start, count int) ([]Location, []Event, error) {

    var locations = []Location{}
    var events = []Event{}

    rows, err := db.Query("SELECT locations.id, locations.title, locations.latlng, events.id, events.title, events.locationid FROM locations LEFT JOIN events ON locations.id = events.locationid LIMIT ? OFFSET ?", count, start)
    if err != nil {
        return locations, events, err
    }
    defer rows.Close()

    for rows.Next() {
        var location Location
        var event Event

        err := rows.Scan(&location.ID, &location.Title, &location.Latlng, &event.ID, &event.Title, &event.LocationID);
        if err != nil {
                return locations, events, err
        }

    // Here I can print locations and see it getting longer with each loop iteration
    fmt.Println(locations)

    // How can I check if an ID exists in locations?
    // Ideally, if location.ID already exists in locations, then only append event, otherwise, append both the location and event

        locations = append(locations, location)
        events = append(events, event)
    }

    return locations, events, nil
}

Функция, вызываемая маршрутизатором:

func (a *App) getLocationsEventsJSON(w http.ResponseWriter, r *http.Request) {

count := 99
start := 0

    if count > 10 || count < 1 {
        count = 10
    }
    if start < 0 {
        start = 0
    }

    locations, events, err := getLocationsEvents(a.DB, start, count)
    if err != nil {
        respondWithError(w, http.StatusInternalServerError, err.Error())
        return
    }

    var locationsEvents LocationsEvents

    locationsEvents.Locations = locations
    locationsEvents.Events = events

    respondWithJSON(w, http.StatusOK, locationsEvents)
}

Функция отправки данных в формате JSON (часть REST API):

func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
    response, _ := json.Marshal(payload)

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(code)
    w.Write(response)
}

ОБНОВЛЕНИЕ:

Возвращаясь к выполнению этого с запросом SQL, каковы возможности? Используя ГРУППУ? Вот пример SQL:

ВЫБЕРИТЕ location.id,locations.title,locations.latlng,events.id,events.title,events.locationid ИЗ местоположений LEFT JOIN events ONlocations.id = events.locationid СГРУППИРОВАТЬ ПО Locations.id, events.id

Набор результатов по-прежнему содержит повторяющиеся данные о местоположении, однако они хорошо сгруппированы и отсортированы.

Тогда есть возможность подзапросов: http://www.w3resource.com/sql/subqueries/understanding-sql-subqueries.php, но теперь я запускаю несколько запросов SQL, чего я хотел избежать.

На самом деле я не думаю, что смогу избежать дублирования данных о местоположении при использовании одного запроса на соединение, как я. Как еще я мог бы получить результирующий набор объединенных данных без репликации данных о местоположении? Если SQL-сервер отправляет мне готовые данные JSON по мере необходимости (местоположения и события разделены)? Насколько я понимаю, лучше делать эту работу после получения результатов.


go
person MarsAndBack    schedule 02.07.2017    source источник
comment
Приведите пример данных, которые вы получаете на данный момент, вместе с ожидаемым результатом. Из запроса в коде, который вы предоставили, не очевидно, какие дубликаты вы наблюдаете: единственный способ получить их - только в том случае, если ваши исходные данные содержат дубликаты.   -  person zerkms    schedule 02.07.2017
comment
Отредактированный OP с выводом строк SQL.   -  person MarsAndBack    schedule 02.07.2017
comment
Я не вижу там повторяющихся строк: каждая строка представляет собой уникальную комбинацию значений в том, что вы только что опубликовали.   -  person zerkms    schedule 03.07.2017
comment
Вы правы насчет рядов. Однако видите ли вы мой окончательный вывод JSON и тот факт, что встречается несколько одинаковых записей о местоположении? Теперь умножьте на 100 местоположений и 10 000 событий, и вы увидите проблему.   -  person MarsAndBack    schedule 03.07.2017
comment
Если вам нужно 2 разных набора результатов, просто запустите 2 разных запроса: один извлекает местоположения, другой извлекает события. Извлекать все сразу не только сложнее, но и не имеет особого смысла. UPD: именно так, как объяснил Евгений Лисицкий в их ответе.   -  person zerkms    schedule 03.07.2017


Ответы (2)


Я думаю, вы можете разделить свой запрос на две части: местоположения (SELECT * FROM locations) и события (SELECT * FROM events), а затем передать их маршаллеру JSON. Эти 2 запроса будут очень простыми и быстрыми для выполнения базой данных. Далее им будет проще кэшировать промежуточные результаты.

но теперь я запускаю несколько запросов SQL, чего я хотел избежать.

Не могли бы вы уточнить этот момент - почему вы хотите избежать множественных запросов? Какую задачу вы хотите решить и какие ограничения есть? Иногда набор небольших простых запросов лучше, чем один слишком сложный.

person Eugene Lisitsky    schedule 02.07.2017

Если вы сами запрашиваете базу данных, вы должны быть в состоянии избежать любых дубликатов. В конце вашего запроса добавьте «ГРУППИРОВАТЬ ПО {уникальное поле}».

Пример, который должен дать уникальный список местоположений, которые находятся в вашем списке событий.

SELECT location.* 
FROM location.ID, location.Title, location.Latlng
  INNER JOIN event ON event.ID=location.ID
GROUP BY location.ID
person Skov    schedule 02.07.2017
comment
Привет, я пробовал этот подход, но безуспешно. Использование GROUP BY с определенным полем приводит к удалению строк. Использование нескольких полей с GROUP BY помогает сортировать строки по группам. См. редактирование ОП. - person MarsAndBack; 02.07.2017
comment
Ааа, на этот раз я более подробно изучил структуру вашей таблицы. Вы установили связь «многие ко многим» между местоположением и событием. Можно ли действительно провести мероприятие в нескольких местах? Или это только одно и то же место, которое можно использовать для многих мероприятий? - person Skov; 02.07.2017
comment
Тогда я бы просто сделал два запроса. 1) Получите список местоположений, как в примере кода выше, и 2) Новый запрос, содержащий все события, и включите location.Id - person Skov; 03.07.2017
comment
Кроме того, мне кажется, что вам нужно только отношение один ко многим между местоположением и событием (не многие ко многим). Это сделало бы запрос еще проще. - person Skov; 03.07.2017