Агрегатная функция, которая допускает только один уникальный ввод - PullRequest
4 голосов
/ 11 декабря 2010

Я часто добавляю выражения в предложение group by, которые, я уверен, уникальны.Иногда оказывается, что я ошибаюсь - из-за ошибки в моем SQL или ошибочного предположения, и что это выражение на самом деле не уникально.

Есть много случаев, когда я бы предпочел, чтобы это вызвало скорее ошибку SQLчем молча, иногда очень тонко, расширять свой набор результатов.

Я бы хотел сделать что-то вроде:

select product_id, unique description from product group by product_id

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

Может ли специальный агрегат, который допускает только одно уникальное входное значение, быть полезным во всех версиях SQL?Если да, может ли такая вещь быть реализована сейчас в большинстве баз данных?Значения null следует рассматривать так же, как и любое другое значение, в отличие от того, как обычно работает встроенный агрегат avg.(Я добавил ответы о способах реализации этого для postgres и Oracle.)

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

таблиц:

 product_id | description
------------+-------------
          1 | anvil
          2 | brick
          3 | clay
          4 | door

 sale_id | product_id |  cost
---------+------------+---------
       1 |          1 | £100.00
       2 |          1 | £101.00
       3 |          1 | £102.00
       4 |          2 |   £3.00
       5 |          2 |   £3.00
       6 |          2 |   £3.00
       7 |          3 |  £24.00
       8 |          3 |  £25.00

запросов:

> select * from product join sale using (product_id);

 product_id | description | sale_id |  cost
------------+-------------+---------+---------
          1 | anvil       |       1 | £100.00
          1 | anvil       |       2 | £101.00
          1 | anvil       |       3 | £102.00
          2 | brick       |       4 |   £3.00
          2 | brick       |       5 |   £3.00
          2 | brick       |       6 |   £3.00
          3 | clay        |       7 |  £24.00
          3 | clay        |       8 |  £25.00

> select product_id, description, sum(cost) 
  from product join sale using (product_id) 
  group by product_id, description;

 product_id | description |   sum
------------+-------------+---------
          2 | brick       |   £9.00
          1 | anvil       | £303.00
          3 | clay        |  £49.00

> select product_id, solo(description), sum(cost) 
  from product join sale using (product_id) 
  group by product_id;

 product_id | solo  |   sum
------------+-------+---------
          1 | anvil | £303.00
          3 | clay  |  £49.00
          2 | brick |   £9.00

случай ошибки:

> select solo(description) from product;
ERROR:  This aggregate only allows one unique input

Ответы [ 4 ]

7 голосов
/ 13 декабря 2010

Решение ORACLE -

select product_id, 
       case when min(description) != max(description) then to_char(1/0) 
            else min(description) end description, 
       sum(cost) 
  from product join sale using (product_id) 
  group by product_id;

Вместо to_char (1/0) [который вызывает ошибку DIVIDE_BY_ZERO), вы можете использовать простую функцию, которая делает

CREATE OR REPLACE FUNCTION solo (i_min IN VARCHAR2, i_max IN VARCHAR2) 
RETURN VARCHAR2 IS
BEGIN
  IF i_min != i_max THEN
    RAISE_APPLICATION_ERROR(-20001, 'Non-unique value specified');
  ELSE
    RETURN i_min;
  END;
END;
/
select product_id, 
       solo(min(description),max(description)) end description, 
       sum(cost) 
from product join sale using (product_id) 
group by product_id;

Вы можете использовать определяемый пользователем агрегат, но я буду обеспокоен влиянием на производительность переключения между SQL и PL / SQL.

3 голосов
/ 11 декабря 2010

Вот моя реализация для postgres (отредактированная для обработки null как уникального значения):

create function solo_sfunc(inout anyarray, anyelement) 
       language plpgsql immutable as $$
begin
  if $1 is null then
    $1[1] := $2;
  else
    if ($1[1] is not null and $2 is null) 
         or ($1[1] is null and $2 is not null) 
         or ($1[1]!=$2) then 
      raise exception 'This aggregate only allows one unique input'; 
    end if;
  end if;
  return;
end;$$;

create function solo_ffunc(anyarray) returns anyelement 
       language plpgsql immutable as $$
begin
  return $1[1];
end;$$;

create aggregate solo(anyelement)
                     (sfunc=solo_sfunc, stype=anyarray, ffunc=solo_ffunc);

примеры таблиц для тестирования:

create table product(product_id integer primary key, description text);

insert into product(product_id, description)
values (1, 'anvil'), (2, 'brick'), (3, 'clay'), (4, 'door');

create table sale( sale_id serial primary key, 
                   product_id integer not null references product, 
                   cost money not null );

insert into sale(product_id, cost)
values (1, '100'::money), (1, '101'::money), (1, '102'::money),
       (2, '3'::money), (2, '3'::money), (2, '3'::money),
       (3, '24'::money), (3, '25'::money);
1 голос
/ 13 декабря 2010

А вот моя реализация для Oracle - к сожалению, я думаю, что вам нужна одна реализация для каждого базового типа:

create type SoloNumberImpl as object
(
  val number, 
  flag char(1), 
  static function ODCIAggregateInitialize(sctx in out SoloNumberImpl) 
         return number,
  member function ODCIAggregateIterate( self in out SoloNumberImpl, 
                                        value in number )
         return number,
  member function ODCIAggregateTerminate( self in SoloNumberImpl, 
                                          returnValue out number, 
                                          flags in number ) 
         return number,
  member function ODCIAggregateMerge( self in out SoloNumberImpl, 
                                      ctx2 in SoloNumberImpl ) 
         return number
);
/

create or replace type body SoloNumberImpl is 
static function ODCIAggregateInitialize(sctx in out SoloNumberImpl)
       return number is 
begin
  sctx := SoloNumberImpl(null, 'N');
  return ODCIConst.Success;
end;

member function ODCIAggregateIterate( self in out SoloNumberImpl, 
                                      value in number ) 
       return number is
begin
  if self.flag='N' then
    self.val:=value;
    self.flag:='Y';
  else
    if (self.val is null and value is not null) 
         or (self.val is not null and value is null) 
         or (self.val!=value) then
      raise_application_error( -20001, 
                               'This aggregate only allows one unique input' );
    end if;
  end if;
  return ODCIConst.Success;
end;

member function ODCIAggregateTerminate( self in SoloNumberImpl, 
                                        returnValue out number, 
                                        flags in number )  
       return number is
begin
  returnValue := self.val;
  return ODCIConst.Success;
end;

member function ODCIAggregateMerge( self in out SoloNumberImpl, 
                                    ctx2 in SoloNumberImpl ) 
       return number is
begin
  if self.flag='N' then
    self.val:=ctx2.val;
    self.flag=ctx2.flag;
  elsif ctx2.flag='Y' then
    if (self.val is null and ctx2.val is not null) 
          or (self.val is not null and ctx2.val is null) 
          or (self.val!=ctx2.val) then
      raise_application_error( -20001, 
                               'This aggregate only allows one unique input' );
    end if;
  end if;
  return ODCIConst.Success;
end;
end;
/

create function SoloNumber (input number) 
return number aggregate using SoloNumberImpl;
/
1 голос
/ 11 декабря 2010

Вы должны определить уникальное ограничение для (product_id, description), тогда вам не нужно беспокоиться о наличии двух описаний для одного продукта.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...