2010-12-11 2 views
4

Я часто нахожу, что добавляю выражения в предложение 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 
+0

, какую базу данных вы используете MySQL, Oracle, Есть ли какие-либо персональная функция в MySQL – XMen

+0

@Rahul это общий вопрос SQL - я надеюсь получить ответы на базы данных, которые мне не знакомы (postgres и Oracle) – 2010-12-18 14:44:20

ответ

3

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

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); 
+0

Хорошая функция, но кажется, что она возвращает массив типа ввода (до 9.5), есть ли способ для он должен возвращать точно тип ввода? – MarHoff

1

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

решения
+1

«Реальное использование, скорее всего, будет в больших запросах, где легче ошибиться в допущениях о уникальности». Ваши «таблицы» могут быть любыми источниками данных, такими как подзапрос с агрегатом, где не так просто добавлять ограничения базы данных для обеспечения уникальности – 2010-12-11 11:53:42

+0

В любом случае, (product_id, description) уже уникально, потому что product_id – 2010-12-11 11:57:46

7

оракула является

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.

+0

спасибо, я hadn ' Я думал об этом изобретательном подходе. Я действительно ищу что-то, что позволит SQL как можно более кратким - в идеале я бы хотел получить больший синтаксис, например 'select id, уникальное описание из группы продуктов product_id', но ни один поставщик не добавит этого, если он не находится в стандарт, я думаю. Я также не могу представить, как ваш метод мог быть изменен, чтобы заставить «null» считаться уникальным значением, как в ответе Oracle, который я собираюсь опубликовать. – 2010-12-13 08:53:19

1

А вот моя реализация для 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; 
/
Смежные вопросы