SQL "дедуплицирует", используя NULL в качестве подстановочного знака - PullRequest
0 голосов
/ 27 мая 2018

Рассмотрим следующую таблицу, где ни один из столбцов не имеет ограничения NULL:

  a   |  b   |  c   |  d
------+------+------+------
    3 |    5 |   12 | NULL
 NULL |    5 |   12 | NULL
   13 | NULL |   26 | NULL
 NULL | NULL |   26 |    4
    6 |    7 |    5 | NULL
    6 | NULL | NULL | NULL
    6 | NULL |    5 | NULL
    6 |    7 | NULL | NULL
 NULL | NULL | NULL | NULL
(9 rows)

Все девять строк различны, но если мы примем NULL как «подстановочный знак», то естьчто он может принимать любое значение, тогда только первая, третья, четвертая и пятая строки несомненно различимы.Поскольку все ненулевые значения других строк появляются в несомненно отличных строках, я хотел бы отбросить эти строки, чтобы получить следующую таблицу:

  a   |  b   | c  |  d
------+------+----+------
    3 |    5 | 12 | NULL
   13 | NULL | 26 | NULL
 NULL | NULL | 26 |    4
    6 |    7 |  5 | NULL
(4 rows)

В данной таблице как удалить строкичьи значения представляют подмножество значений другой строки в той же таблице?Другой способ задать этот вопрос: как можно дедуплицировать таблицу, используя NULL в качестве подстановочного знака?

Не беспокойтесь об удалении фактически повторяющихся строк (именно поэтому я поставил «дедупликация» в кавычках взаглавие).В частности, я хотел бы иметь возможность делать это как в PostgreSQL, так и в Redshift.

Для справки, эти операторы создают исходную таблицу, описанную выше:

CREATE TABLE t (a int, b int, c int, d int);
INSERT INTO t
VALUES (   3,    5,   12, NULL),
       (NULL,    5,   12, NULL),
       (  13, NULL,   26, NULL),
       (NULL, NULL,   26,    4),
       (   6,    7,    5, NULL),
       (   6, NULL, NULL, NULL),
       (   6, NULL,    5, NULL),
       (   6,    7, NULL, NULL),
       (NULL, NULL, NULL, NULL);

Ответы [ 4 ]

0 голосов
/ 27 мая 2018

К вашему сведению

Кто-то опубликовал довольно аккуратный ответ вскоре после того, как я задал свой вопрос, но похоже, что он / она позже удалил его.Код в ответе не совсем сработал, но подход мне понравился.Я изменил его, и, похоже, он выполняет свою работу:

DELETE FROM t
 WHERE EXISTS (
   SELECT u.*
     FROM t AS u
    WHERE (t.a IS NULL OR t.a = u.a)
      AND (t.b IS NULL OR t.b = u.b)
      AND (t.c IS NULL OR t.c = u.c)
      AND (t.d IS NULL OR t.d = u.d)
      AND NOT (
        (
          t.a IS NULL AND u.a IS NULL
          OR (
            t.a IS NOT NULL AND u.a IS NOT NULL
            AND t.a = u.a
          )
        )
        AND (
          t.b IS NULL AND u.b IS NULL
          OR (
            t.b IS NOT NULL AND u.b IS NOT NULL
            AND t.b = u.b
          )
        )
        AND (
          t.c IS NULL AND u.c IS NULL
          OR (
            t.c IS NOT NULL AND u.c IS NOT NULL
            AND t.c = u.c
          )
        )
        AND (
          t.d IS NULL AND u.d IS NULL
          OR (
            t.d IS NOT NULL AND u.d IS NOT NULL
            AND t.d = u.d
          )
        )
      )
 );

Более краткая версия, которая (пока) не поддерживается в Redshift:

DELETE FROM t
 WHERE EXISTS (
   SELECT u.*
     FROM t AS u
    WHERE (t.a IS NULL OR t.a = u.a)
      AND (t.b IS NULL OR t.b = u.b)
      AND (t.c IS NULL OR t.c = u.c)
      AND (t.d IS NULL OR t.d = u.d)
          EXCEPT
   SELECT t.*
 );
0 голосов
/ 27 мая 2018

Для выбора только тех, у кого нет совпадений на основе символов подстановки NULL.
Использование NOT EXISTS:

SELECT *
FROM T AS t
WHERE NOT EXISTS (
    SELECT 1
    FROM T AS dup
    WHERE (dup.a = t.a OR t.a IS NULL)
      AND (dup.b = t.b OR t.b IS NULL)
      AND (dup.c = t.c OR t.c IS NULL)
      AND (dup.d = t.d OR t.d IS NULL)
      AND CONCAT(dup.a,'-',dup.b,'-',dup.c,'-',dup.d) <> CONCAT(t.a,'-',t.b,'-',t.c,'-',t.d)
)

Чтобы выбрать только дубликаты на основе символов подстановки NULL.
Использование EXISTS:

SELECT *
FROM T AS t
WHERE EXISTS (
    SELECT 1
    FROM T AS dup
    WHERE (dup.a = t.a OR t.a IS NULL)
      AND (dup.b = t.b OR t.b IS NULL)
      AND (dup.c = t.c OR t.c IS NULL)
      AND (dup.d = t.d OR t.d IS NULL)
      AND CONCAT(dup.a,'-',dup.b,'-',dup.c,'-',dup.d) <> CONCAT(t.a,'-',t.b,'-',t.c,'-',t.d)
)

Чтобы удалить дубликаты из таблицы на основе подстановочных знаков NULL.
Использование EXISTS:

DELETE
FROM T AS t
WHERE EXISTS (
    SELECT 1
    FROM T AS dup
    WHERE (dup.a = t.a OR t.a IS NULL)
      AND (dup.b = t.b OR t.b IS NULL)
      AND (dup.c = t.c OR t.c IS NULL)
      AND (dup.d = t.d OR t.d IS NULL)
      AND CONCAT(dup.a,'-',dup.b,'-',dup.c,'-',dup.d) <> CONCAT(t.a,'-',t.b,'-',t.c,'-',t.d)
)

Обратите внимание, что из-за сравнения на CONCAT эти записикоторые имеют точные дубликаты, не будут рассматриваться как дубли.

Если таблица имеет идентификатор в качестве первичного ключа, то сравнение CONCAT может быть заменено на

AND dup.ID <> t.ID

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

0 голосов
/ 27 мая 2018

Это не сможет обнаружить истинные дубликаты (ловит оба), я думаю, что нам все еще нужен ctid (или некоторый материал курсора)


WITH enums AS (
        SELECT x.a, x.b,x.c,x.d
        -- , (x.a IS NULL)::integer + (x.b IS NULL)::integer 
         -- + (x.c IS NULL)::integer + (x.d IS NULL)::integer AS nnull
        , row_number() OVER www AS rn
        FROM tbl x
        JOIN tbl y
        ON (x.a =y.a OR x.a IS NULL)
        AND (x.b =y.b OR x.b IS NULL)
        AND (x.c =y.c OR x.c IS NULL)
        AND (x.d =y.d OR x.d IS NULL)
        WINDOW WWW AS
        (PARTITION BY COALESCE(x.a ,y.a), COALESCE(x.b ,y.b)
                , COALESCE(x.c ,y.c), COALESCE(x.d ,y.d)
         ORDER BY x.a NULLS LAST
        , x.b NULLS LAST
        , x.c NULLS LAST
        , x.d NULLS LAST )
        )
SELECT* --DELETE
-- FROM  enums ex ; \q
FROM tbl del
WHERE EXISTS ( SELECT *
        FROM  enums ex
        WHERE ex.rn > 1
        AND ex.a IS NOT DISTINCT FROM del.a
        AND ex.b IS NOT DISTINCT FROM del.b
        AND ex.c IS NOT DISTINCT FROM del.c
        AND ex.d IS NOT DISTINCT FROM del.d
        );
0 голосов
/ 27 мая 2018

Здесь будут найдены уникальные строки с подстановочными знаками (нулями), но это может не сработать, если на совпадение более 1 нуля.

Это работает, сначала получая строки без нулей, и присоединяя их обратно.к базовой таблице и заполнению нулями.наконец, выбор отличных значений приводит к желаемому результату.

WITH nn AS (
    SELECT * 
    FROM t 
    WHERE a IS NOT NULL 
      AND b IS NOT NULL 
      AND c IS NOT NULL 
      AND d IS NOT NULL
)
SELECT DISTINCT 
      COALESCE(t1.a, nn.a) a
    , COALESCE(t1.b, nn.b) b
    , COALESCE(t1.c, nn.c) c
    , COALESCE(t1.d, nn.d) d
    FROM t t1 
    LEFT JOIN nn
           ON (t1.a IS NULL OR t1.a = nn.a)
          AND (t1.b IS NULL OR t1.b = nn.b)
          AND (t1.c IS NULL OR t1.c = nn.c)
          AND (t1.d IS NULL OR t1.d = nn.d)

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

insert into t values (2, 10, 12, null), (2, null, 12, 10);

, потому что список не равен нулю (nn) пропускает оба.

...