Oracle: способ агрегирования конкатенации несгруппированного столбца в сгруппированных результатах - PullRequest
2 голосов
/ 18 ноября 2009

У меня есть запрос с несколькими агрегатными функциями и несколькими сгруппированными столбцами. Я хочу вывести один из сгруппированных столбцов из группы и выполнить какую-то совокупную «конкатенацию» всех значений VARCHAR, которые он имеет. (В идеале в новом списке с разделением каретки).

Вот мой запрос, и я отмечаю, где я хотел бы сделать это:

SELECT rownum, F.*
FROM (SELECT 
  c.logical_type "MerchantType",
  c.merchant_id "MerchantID",
  c.m_name "MerchantName",
  m.m_name "TransferredBy", /* <----- Make this aggregate */
  SUM(DECODE(b.ba_price,null,0,DECODE(b.BILL_SRVC_ID,'CREDITCHANGE',0,b.ba_price))) "TotalValue", 
  sum(DECODE(b.ba_price,null,0,DECODE(b.BILL_SRVC_ID,'CREDITCHANGE',b.ba_price,0))) "LimitChange", 
  SUM(DECODE(b.ba_status,'bdone',1,0)) "TxnCount",
  sum(to_number(decode(substr(b.ba_merchant_freetext,1,10),'Commission',substr(b.ba_merchant_freetext, 12,(instr(b.ba_merchant_freetext,';',1,1)-12))))) "Commission"
FROM bill_auth0 b,
  merchant0 m,
  merchant0 c
WHERE 
  b.srvc_prod_id = 'TRANSFER'
    AND b.ba_channel = 'WPSS'
    AND b.ba_status     IN ('bdone')
    AND b.merchant_id    = m.merchant_id
    AND b.customer_id    = c.merchant_id
    AND b.ba_timestamp BETWEEN to_date( '11/01/2009', 'MM/DD/YYYY' ) 
        AND to_date( '11/17/2009', 'MM/DD/YYYY' )+1
GROUP BY 
  c.logical_type,
  c.merchant_id,
  c.m_name,
  m.m_name /* <-- Remove from Grouped By */
ORDER BY c.logical_type, c.merchant_id, m.m_name) F;

Так что, по сути, я хочу получить результат, в котором «TransferredBy» будет выглядеть примерно так: Merchant1
Merchant2
Торговец3

если было 3 отдельных совпадения m.m_name с этой строкой группы.

Ответы [ 5 ]

5 голосов
/ 18 ноября 2009

Вот хорошая статья о различных методах агрегирования строк .

Я могу добавить еще один метод (на основе XML):

select rtrim(
         extract(
           sys_xmlagg(
             xmlelement("X",ename||', ')
           ),
           '/ROWSET/X/text()'
         ).getstringval(),
         ', '
       )
  from emp;

И в версии 11g 2 у нас наконец-то есть встроенная функция LISTAGG .

3 голосов
/ 18 ноября 2009

Хотя я не знаю ни одной встроенной функции, способной решить вашу проблему, похоже, что вы можете написать собственную агрегатную функцию , которая может! Поскольку мне было любопытно, я попробовал свои силы в реализации пользовательской агрегатной функции, которая объединяет текст с разделителем:

Спецификация типа:

CREATE OR REPLACE TYPE TextConcatenation AS OBJECT
(

  text VARCHAR2(10000),
  delimiter VARCHAR2(10),
  concatenation_count NUMBER,

  STATIC FUNCTION ODCIAggregateInitialize(actx IN OUT TextConcatenation) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateIterate(self IN OUT TextConcatenation, val IN VARCHAR2) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateTerminate(self IN TextConcatenation, returnValue OUT VARCHAR2, flags IN NUMBER) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateMerge(self IN OUT TextConcatenation, ctx2 IN TextConcatenation) RETURN NUMBER

)

Тип корпуса:

CREATE OR REPLACE TYPE BODY TextConcatenation AS

  STATIC FUNCTION ODCIAggregateInitialize(actx IN OUT TextConcatenation) RETURN NUMBER IS
  BEGIN
    IF actx IS NULL THEN
      actx := TextConcatenation('', ', ', 0); #substitute your own delimiter here in the second argument
    ELSE
      actx.text := '';
      actx.delimiter := ', '; # substitute your own delimiter here
      actx.concatenation_count := 0;
    END IF;
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateIterate(self IN OUT TextConcatenation, val IN VARCHAR2) RETURN NUMBER IS
  BEGIN
    IF self.concatenation_count > 0 THEN
      self.text := self.text || delimiter;
    END IF;

    self.text := self.text || val;

    self.concatenation_count := self.concatenation_count + 1;

    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateTerminate(self IN TextConcatenation, returnValue OUT VARCHAR2, flags IN NUMBER) RETURN NUMBER IS
  BEGIN
    returnValue := text;
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateMerge(self IN OUT TextConcatenation, ctx2 IN TextConcatenation) RETURN NUMBER IS
  BEGIN
    IF ctx2.concatenation_count > 0 THEN
      IF self.concatenation_count > 0 THEN
        self.text := self.text || delimiter || ctx2.text;
      ELSE
        self.text := ctx2.text;
      END IF;
      self.concatenation_count := self.concatenation_count + ctx2.concatenation_count;
    END IF;

    RETURN ODCIConst.Success;
  END;

END;

Агрегатная функция:

CREATE OR REPLACE FUNCTION text_concatenate(text VARCHAR2) RETURN VARCHAR2 AGGREGATE USING TextConcatenation;

После всего этого мне удалось выполнить следующий запрос:

SELECT text_concatenate(name) FROM
(
  SELECT 'Paynter' name FROM DUAL
  UNION ALL
  SELECT 'Adam' name FROM DUAL
)

Результатом стала одна строка:

Paynter, Adam
1 голос
/ 20 мая 2011

использовать листаг http://download.oracle.com/docs/cd/E14072_01/server.112/e10592/functions087.htm Это хорошо документировано и поддерживается.

0 голосов
/ 19 ноября 2009

Попробуйте wm_concat(yourColumn) ... это доступно в зависимости от вашей версии БД. Это не официально задокументированная функция, но она выполняет те же функции, что и многие другие функции, перечисленные выше.

Это список, разделенный запятыми. Чтобы получить символ новой строки, вы можете окружить его replace ()

Еще один совет - использовать разные внутри wm_concat (), такие как wm_concat (Different yourColumn) , чтобы вы не получили дубликаты.

0 голосов
/ 18 ноября 2009

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

Я считаю, что версия 10g.

...