Выберите только первую строку повторяющегося значения в столбце SQL

У меня есть таблица с столбцом, который может иметь одинаковые значения в пакете. Вот так:

+----+---------+
| id |   Col1  | 
+----+---------+
| 1  | 6050000 |
+----+---------+
| 2  | 6050000 |
+----+---------+
| 3  | 6050000 |
+----+---------+
| 4  | 6060000 |
+----+---------+
| 5  | 6060000 |
+----+---------+
| 6  | 6060000 |
+----+---------+
| 7  | 6060000 |
+----+---------+
| 8  | 6060000 |
+----+---------+
| 9  | 6050000 |
+----+---------+
| 10 | 6000000 |
+----+---------+
| 11 | 6000000 |
+----+---------+

Теперь я хочу обрезать строки, где значение Col1 повторяется и выбирается только первое вхождение.
Для приведенной выше таблицы результат должен быть:

+----+---------+
| id |   Col1  | 
+----+---------+
| 1  | 6050000 |
+----+---------+
| 4  | 6060000 |
+----+---------+
| 9  | 6050000 |
+----+---------+
| 10 | 6000000 |
+----+---------+

Как это сделать в SQL?
Обратите внимание, что только строки пакета должны быть удалены, а значения могут повторяться в непарных строках! id=1 и id=9 повторяются в примере.

EDIT:
Я достиг этого, используя это:

select id,col1 from data as d1
where not exists (
    Select id from data as d2
    where d2.id=d1.id-1 and d1.col1=d2.col1 order by id limit 1)

Но это работает только тогда, когда идентификаторы являются последовательными. В случае пробелов между идентификаторами (удаленными) запрос прерывается. Как я могу это исправить?

Ответ 1

Вы можете использовать полу-объединение EXISTS для идентификации кандидатов:

Выберите нужные строки:

SELECT * FROM tbl
WHERE NOT EXISTS (
    SELECT *
    FROM tbl t
    WHERE t.col1 = tbl.col1
    AND t.id = tbl.id - 1
    )
ORDER BY id

Избавиться от нежелательных строк:

DELETE FROM tbl
-- SELECT * FROM tbl
WHERE EXISTS (
    SELECT *
    FROM   tbl t
    WHERE  t.col1 = tbl.col1
    AND    t.id   = tbl.id - 1
    )

Это эффективно удаляет каждую строку, где предыдущая строка имеет одинаковое значение в col1, тем самым достигая поставленной цели: выживает только первая строка каждого пакета.

Я оставил комментарий SELECT, потому что вы должны всегда проверять, что будет удалено, прежде чем сделать это.


Решение для не последовательных идентификаторов:

Если ваша РСУБД поддерживает CTE и функции окна (например, PostgreSQL, Oracle, SQL Server,... но не SQLite, MS Access или MySQL), есть элегантный способ:

WITH x AS (
    SELECT *, row_number() OVER (ORDER BY id) AS rn
    FROM tbl
    )
SELECT id, col1
FROM   x
WHERE NOT EXISTS (
    SELECT *
    FROM   x x1
    WHERE  x1.col1 = x.col1
    AND    x1.rn   = x.rn - 1
    )
ORDER BY id;

Существует также не очень элегантный способ, который выполняет работу без этих тонкостей.
Должно работать для вас:

SELECT id, col1
FROM   tbl
WHERE (
    SELECT t.col1 = tbl.col1
    FROM   tbl AS t
    WHERE  t.id < tbl.id
    ORDER  BY id DESC
    LIMIT  1) IS NOT TRUE
ORDER BY id

Инструмент для несекретных идентификаторов тестовой оболочки

(проверено в PostgreSQL)

CREATE TEMP TABLE tbl (id int, col1 int);
INSERT INTO tbl VALUES
 (1,6050000),(2,6050000),(6,6050000)
,(14,6060000),(15,6060000),(16,6060000)
,(17,6060000),(18,6060000),(19,6050000)
,(20,6000000),(111,6000000);

Ответ 2

select min(id), Col1 from tableName group by Col1 

Ответ 3

Если ваша RDBMS поддерживает функции Window Aggregate и/или функции LEAD() и LAG(), вы можете использовать их для достижения того, что вы пытаетесь сообщить. Следующий SQL поможет вам начать работу по правильному пути:

SELECT id
     , Col AS CurCol
     , MAX(Col)
       OVER(ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS PrevCol
     , MIN(COL)
       OVER(ORDER BY id ROWS BETWEEN 1 FOLLOWING AND 1 FOLLOWING) AS NextCol
FROM MyTable

Оттуда вы можете поместить этот SQL в производную таблицу с некоторой логикой CASE, если NextCol или PrevCol совпадает с CurCol, затем установите CurCol = NULL. Затем вы можете свернуть все идентификационные записи CurCol IS NULL.

Если у вас нет возможности использовать агрегаты окон или функции LEAD/LAG, ваша задача немного сложнее.

Надеюсь, что это поможет.

Ответ 4

Так как id всегда последователен, без пробелов или повторений, в соответствии с вашим комментарием, вы можете использовать следующий метод:

SELECT t1.*
FROM atable t1
  LEFT JOIN atable t2 ON t1.id = t2.id + 1 AND t1.Col1 = t2.Col1
WHERE t2.id IS NULL

Таблица (внешняя) соединена с собой при условии, что левая сторона id больше, чем правая, а их значения Col1 идентичны. Другими словами, условие "предыдущая строка содержит то же значение Col1, что и текущая строка. Если нет совпадения справа, тогда следует выбрать текущую запись.


UPDATE

Для учета непоследовательных id (которые, однако, считаются уникальными и определяют порядок изменений Col1), вы также можете попробовать следующий запрос:

SELECT t1.*
FROM atable t1
  LEFT JOIN atable t2 ON t1.id > t2.id
  LEFT JOIN atable t3 ON t1.id > t3.id AND t3.id > t2.id
WHERE t3.id IS NULL
  AND (t2.id IS NULL OR t2.Col1 <> t1.Col1)

Третье самосоединение состоит в том, чтобы убедиться, что второй дает строку, непосредственно предшествующую строке t1. То есть, если нет соответствия для t3, то либо t2 содержит предыдущую строку, либо совпадение не имеет, последнее означает, что t1 текущая строка является верхней.

Ответ 5

как насчет этого простого подхода?

select distinct col1 from tbl