Удалить дубликаты с предостережениями

У меня есть таблица с идентификатором строки, долготой, широтой, именем компании, URL-адресом, заголовком. Это может выглядеть так:

rowID | long  | lat |  businessName | url | caption

  1      20     -20     Pizza Hut   yum.com  null

Как удалить все дубликаты, но оставить только тот, у которого есть URL-адрес (первый приоритет), или сохранить тот, у которого есть заголовок, если у другого нет URL-адреса (второй приоритет), и удалить остальные?


person Community    schedule 29.09.2008    source источник
comment
Дубликаты основаны на названии компании?   -  person Sam Saffron    schedule 30.09.2008
comment
Угадывание дубликатов: long + lat + businessName?   -  person Forgotten Semicolon    schedule 30.09.2008
comment
Дубликаты основаны на long + lat + businessName, в идеале, в конце только один long + lat + businessName, который лучше всего соответствует сценарию.   -  person RyanKeeter    schedule 30.09.2008


Ответы (5)


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

DECLARE @LoopVar int

DECLARE
  @long int,
  @lat int,
  @businessname varchar(30),
  @winner int

SET @LoopVar = (SELECT MIN(rowID) FROM Locations)

WHILE @LoopVar is not null
BEGIN
  --initialize the variables.
  SELECT 
    @long = null,
    @lat = null,
    @businessname = null,
    @winner = null

  -- load data from the known good row.  
  SELECT
    @long = long,
    @lat = lat,
    @businessname = businessname
  FROM Locations
  WHERE rowID = @LoopVar

  --find the winning row with that data
  SELECT top 1 @Winner = rowID
  FROM Locations
  WHERE @long = long
    AND @lat = lat
    AND @businessname = businessname
  ORDER BY
    CASE WHEN URL is not null THEN 1 ELSE 2 END,
    CASE WHEN Caption is not null THEN 1 ELSE 2 END,
    RowId

  --delete any losers.
  DELETE FROM Locations
  WHERE @long = long
    AND @lat = lat
    AND @businessname = businessname
    AND @winner != rowID

  -- prep the next loop value.
  SET @LoopVar = (SELECT MIN(rowID) FROM Locations WHERE @LoopVar < rowID)
END
person Amy B    schedule 29.09.2008
comment
Я использую очень похожий подход. Этот тип цикла также быстрее, чем CURSOR. У этого также есть то преимущество, что он не будет привязывать ЦП сервера. Я поместил аналогичный код в другой пост, на который вы ссылались в своем вопросе. - person Hector Sosa Jr; 30.09.2008
comment
Что, если rowID является переменной типа char(11)? Это первичный ключ, но можете ли вы выбрать min(foo) для чего-то, что является строкой? - person RyanKeeter; 30.09.2008
comment
Чтобы некоторый тип был истинным первичным ключом, он должен установить порядок в таблице. Нет проблем с упорядочением по char(11). - person Amy B; 30.09.2008

Это решение предложено вам «материалом, который я узнал о переполнении стека» на прошлой неделе:

DELETE restaurant
WHERE rowID in 
(SELECT rowID
    FROM restaurant
    EXCEPT
    SELECT rowID 
    FROM (
        SELECT rowID, Rank() over (Partition BY BusinessName, lat, long ORDER BY url DESC, caption DESC ) AS Rank
        FROM restaurant
        ) rs WHERE Rank = 1)

Предупреждение: я не проверял это на реальной базе данных.

person Darrel Miller    schedule 30.09.2008

Решение на основе набора:

delete from T as t1
where /* delete if there is a "better" row
         with same long, lat and businessName */
  exists(
    select * from T as t2 where
      t1.rowID <> t2.rowID
      and t1.long = t2.long
      and t1.lat = t2.lat
      and t1.businessName = t2.businessName 
      and
        case when t1.url is null then 0 else 4 end
          /* 4 points for non-null url */
        + case when t1.businessName is null then 0 else 2 end
          /* 2 points for non-null businessName */
        + case when t1.rowID > t2.rowId then 0 else 1 end
          /* 1 point for having smaller rowId */
        <
        case when t2.url is null then 0 else 4 end
        + case when t2.businessName is null then 0 else 2 end
        )
person Constantin    schedule 29.09.2008

Аналогично другому ответу, но вы хотите удалить на основе номера строки, а не ранга. Смешайте также с общими табличными выражениями:


;WITH GroupedRows AS
(   SELECT rowID, Row_Number() OVER (Partition BY BusinessName, lat, long ORDER BY url DESC, caption DESC) rowNum 
    FROM restaurant
)
DELETE r
FROM restaurant r
JOIN GroupedRows gr ON r.rowID = gr.rowID
WHERE gr.rowNum > 1
person mancaus    schedule 30.09.2008

Если возможно, можете ли вы гомогенизировать, а затем удалить дубликаты?

Шаг 1:

UPDATE BusinessLocations
SET BusinessLocations.url = LocationsWithUrl.url
FROM BusinessLocations
INNER JOIN (
  SELECT long, lat, businessName, url, caption
  FROM BusinessLocations 
  WHERE url IS NOT NULL) LocationsWithUrl 
    ON BusinessLocations.long = LocationsWithUrl.long
    AND BusinessLocations.lat = LocationsWithUrl.lat
    AND BusinessLocations.businessName = LocationsWithUrl.businessName

UPDATE BusinessLocations
SET BusinessLocations.caption = LocationsWithCaption.caption
FROM BusinessLocations
INNER JOIN (
  SELECT long, lat, businessName, url, caption
  FROM BusinessLocations 
  WHERE caption IS NOT NULL) LocationsWithCaption 
    ON BusinessLocations.long = LocationsWithCaption.long
    AND BusinessLocations.lat = LocationsWithCaption.lat
    AND BusinessLocations.businessName = LocationsWithCaption.businessName

Шаг 2: Удалите дубликаты.

person Forgotten Semicolon    schedule 29.09.2008