Удаление дубликатов и почти дубликатов (пропущенных значений) в SQL - PullRequest
0 голосов
/ 23 марта 2020

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

ID   FIRST LAST  YEAR CITY    COUNTRY
1    John  SMITH 1985 NewYork USA
1    John  NULL  1985 NULL    USA
1    NULL  SMITH NULL Miami USA
1    John  SMITH 1985 NewYork USA

Мне требуется:

1    John  SMITH 1985 NewYork USA
1    NULL  SMITH NULL Miami   USA

Похож на этот вопрос:

SQL Удалить почти повторяющиеся строки

Однако в моих данных любая из переменных (кроме ID) может быть NULL. Если это имеет значение, я делаю это в SAS, так что pro c SQL или SAS Dataste будет в порядке.

Так что пара вещей, которые, я думаю, будут работать, но просто требуют вечной обработки ( мой файл порядка 20 миллионов строк):

proc sql create table want as select

t1.index,
t2.index as index2

from data t1, data t2 where 

(t1.first = t2.first or missing(t1.first) or missing(t2.first)) AND

(t1.last = t2.last or missing(t1.last) or missing(t2.last)) AND

(t1.year = t2.year or missing(t1.year) or missing(t2.year)) AND

(t1.city = t2.city or missing(t1.city) or missing(t2.city)) AND

(t1.country = t2.country or missing(t1.country) or missing(t2.country)) AND

(t1.ID = t2.ID);

quit;

Оттуда я могу узнать, какие строки эквивалентны, и могу удалить их. Но pro c sql ужасно медленно работает с предложениями OR в операторах WHERE.

Другое решение, которое у меня было, - это использование решения Михаэля Голдштейна из связанного вопроса и просто последовательное его применение ко всем переменным, но я остановился после 2 дней обработки без обработки.

Есть ли способ ускорить любой из этих способов?

1 Ответ

1 голос
/ 25 марта 2020

Рассмотрим структуру таблицы, в которой есть k ключевые переменные и n переменные данных.

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

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

C1, C2, N3, C4, C5

  • C для символа,
  • N для номера c.

Примечание. На все переменные данных нельзя ссылаться через один шаг DATA array из-за различных типов.

Перемещение:

  • Построить битовую маску, соответствующую не пропущенным значениям. В него будут включены m битов.
  • Идентификация субмасок
    • Те битовые маски, чье покрытие хуже битовой маски
    • Там будет 2m-1 подмаски

Рассмотрим пример строки:

  • John SMITH . . USA

Примеры значений данных имеют маску с m=3

  • 1 1 0 0 1

Есть 23-1 = 7 субмаски

  • 1 1 0 0 *
  • 1 * 0 0 1
  • 1 * 0 0 *
  • * 1 0 0 1
  • * 1 0 0 *
  • * * 0 0 1
  • * * 0 0 *

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

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

A l oop за 2 n значения от 0 до 2n-1 - это простой способ перебора всех масок-кандидатов. BAND Операции с основной маской будут вычислять подмаску из кандидата.

Пример кода

data have;input
ID FIRST:$ LAST:$ YEAR CITY:$ COUNTRY:$; datalines;
1  John  SMITH 1985 NewYork USA
1  John  .     1985 .       USA
1  John  .     1985 .       UK
1  .     SMITH .    Miami   USA
1  John  SMITH 1985 NewYork USA
1  Mark  SMITH 1990 London  UK
1  Mark  SMITH 1990 London  UK
1  Mark  SMITH 1990 London  UK
1  Mark  SMITH 1990 London  UK
1  .     SMITH 1990 London  UK
1  Mark  .     1990 London  UK
1  Mark  SMITH .    London  UK
1  Mark  SMITH 1990 .       UK
1  Mark  SMITH 1990 London  .
1  Mark  SMITH .    London  UK
1  Mark  .     .    London  UK
;


data have;
  set have have(in=_2);
  if _2 then id=2;
run;

%macro loadkeysFor(var);

  %local i j n itop bits;

  %let n = %sysfunc(countw(&var));
  %let itop = %eval(&n-1);

  %do i = 0 %to &itop;
    %local var&i;
    %let var&i = %scan(&var,&i+1);  %* data variable names;
  %end;

  %do i = 0 %to &itop;
    _&i = &&var&i;                  %* generate code to save data values;
  %end;
  %do i = 0 %to &itop;
    _bit&i = not missing(_&i);      %* generate code to compute bits of mask;
  %end;

  %* mask indicates the non-missing permutations of 1 bits in mask submasks will be ;
  _mask = input(cats(of _bit&itop-_bit0), binary&n..);

/*  put (_&itop-_0) (=);*/
/*  put (_bit&itop-_bit0) (1.) +1 _mask binary&n..;*/

  if h.find() = 0 then continue;
    %* continue will skip this row because when 'found' the data values of the row are either
    %*  - identical to a prior row, or
    %*  - inferior to a prior row
    %*;

  _submask = _mask;
  _seq = index;                     %* non-missing seq is the mark of a principal row;

  h.add();  %* new principal row. save it, and replace all keys of corresponding key values as inferior;

  array _mark(0:%eval(2**&n)) _temporary_;

  call missing (of _mark(*));
  _seq = .;                          %* seq is the mark of an inferior submask;

  do _maskbits_ = 0 to %eval(2**&n-1);
    _submask = band (_mask, _maskbits_);  %* good ole BAND - binary and, compute the submask;
    if _submask = _mask then continue;    %* skip principal row;

    if missing(_mark(_submask)) then do;  %* reduce extra work, each submask done only once;
      _mark(_submask) = 1;

      %* generate code to assign data values (from saved values) according to submask;
      %* set host variables according to submask;
      %do i = 0 %to &itop;
        if band(_submask,blshift(1,&i)) then &&var&i = _&i; else call missing(&&var&i);
      %end;

      rc = h.replace();  %* add/replace hash entry of inferior sub-mask;
    end;
  end;

  format _submask _mask binary&n.. _seq _n_ 4.;
%mend;

options mprint;

data want;
  if 0 then set have;

  if _n_ = 1 then do;
    declare hash h (ordered:'a');
    h.defineKey ('first', 'last', 'year', 'city', 'country');
    h.defineData('first', 'last', 'year', 'city', 'country', '_seq'); * , 'row', '_submask', '_mask';
    h.defineDone();

    declare hiter hi('h');

    declare hash select();
    select.defineKey('_seq');
    select.defineDone();
  end;

  h.clear();

  do index = 1 by 1 until (last.id);
    set have;
    by id;

    row = index;

    %loadkeysFor(first last year city country)
  end;

  put index=;

  indexTop = index;

  %* retrieve maximally covering principal rows of group;
  select.clear();
  do _n_ = 1 by 1 while (hi.next() = 0);
    if _seq then OUTPUT;
  end;

  keep id first last year city country;
run;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...