Как обновить несколько дубликатов с разными значениями в одной таблице?

В таблице, в которой я имею дело, есть несколько строк, которые имеют одинаковые значения для lat и lon. Пример показывает, что 1, 3, 5 имеют одинаковое расположение, но атрибут name отличается. hash построен из name, lat и lon и поэтому отличается.

BEFORE:

 id | name | lat | lon | flag | hash
----+------+-----+-----+------+------
 1  | aaa  | 16  | 48  |  0   | 2cd       <-- duplicate
 2  | bbb  | 10  | 22  |  0   | 3fc
 3  | ccc  | 16  | 48  |  0   | 8ba       <-- duplicate
 4  | ddd  | 10  | 23  |  0   | c33
 5  | eee  | 16  | 48  |  0   | 751       <-- duplicate

Мне нужно идентифицировать "дубликаты" в этой таблице и вы хотите назначить флаг 1 (primary) одному из них и флаг 2 (вторичный) для других. Не важно, какой "дубликат" помечен как первичный.

 AFTER:

 id | name | lat | lon | flag | hash
----+------+-----+-----+------+------
 1  | aaa  | 16  | 48  |  1   | 2cd       <-- updated
 2  | bbb  | 10  | 22  |  0   | 3fc
 3  | ccc  | 16  | 48  |  2   | 8ba       <-- updated
 4  | ddd  | 10  | 23  |  0   | c33
 5  | eee  | 16  | 48  |  2   | 751       <-- updated

Я начал экспериментировать с INNER JOIN вдохновленным этим сообщением, и это визуальное описание. С этим я могу назначить один и тот же флаг всем дубликатам.

UPDATE table t1
INNER JOIN table_name t2
ON
  t1.lat = t2.lat
  AND t1.lon = t2.lon
  AND t1.hash != t2.hash
SET
  t1.flag = 2;

Я также тестировал LEFT OUTER JOIN с WHERE t2.id IS NULL, который мог работать, когда есть только две строки. Однако я не могу думать, как JOIN должен работать с более двух дубликатов. Марк Харрисон также предполагает, что вы присоединяетесь к колонкам без дубликатов в начале своего сообщения, которые звучат так, как будто это не очень хорошая идея.

Я использую MySQL, если это интересно.

Ответы

Ответ 1

Не уверен, что это очень эффективно, но работает только в одном запросе:

UPDATE t
JOIN (
  SELECT MAX(t.id) AS maxid, lat, lon
  FROM t
  JOIN t AS duplicates
  USING (lat, lon)
  GROUP BY lat, lon
  HAVING COUNT(*) > 1
) AS maxima USING (lat, lon)
SET flag = IF(id = maxid, 1, 2);

Ответ 2

Предполагая, что вы хотите, чтобы все канонические записи имели flag=1 (а не только те записи, которые имеют дубликаты), вы можете сделать:

UPDATE table t1
SET t1.flag = 1
WHERE t1.id in (
    SELECT min(id)
    FROM table t2
    WHERE t1.lat = t2.lat
    AND t1.lon = t2.lon
);

UPDATE table
SET flag = 2
WHERE flag is null;

COMMIT;

Ответ 3

Вы можете сделать это в два этапа:

update table t1 set flag = 2 where id in 
   (select a.id
    from 
        t1 a
        join t1 b on (b.lon = a.lon and b.lat = a.lat));

update table t1 set flag = 1 where id in 
   (select a.id 
    from t1 a
    where a.flag = 2 and a.id = 
        (select min(b.id) from t1 b where b.lon = a.lon and b.lat = a.lat)
   );

Это должно использоваться как вдохновение, а не как Евангелие (это, вероятно, неправильно):)