0

I have a very long query, across multiple tables, where I have concatenated four values as owners (first, middle and last name + org). All other columns are the same, but there are multiple owners, thus, multiple rows which I would like to aggregate.

What I am seeing is (paired down)

#     Owner
1     Sam Smith, AAA
2     Stan Bird, BBB
2     Nancy Bird, BBB
3     Mike Owen, CCC

What I would like to see is

#     Owner
1     Sam Smith, AAA
2     Stan Bird, Nancy Bird, BBB
3     Mike Owen, CCC

Caveats:

  • I can not create functions (lack of privileges)
  • Using Oracle 10g
  • I have attempted CASE(COLLECT... but this kills my connection:

    Error- "No more data to read from socket"

    SysAdmin, isn't sure why

  • WM_CONCAT simply repeats one owner (sometimes 20 times) and is not giving the desired result.

I have tried a few other things, with no luck. My current query is producing the desired number of rows, but is just lopping off the second owner.

I am unsure if it would be wise to post the entire query here. Please let me know if this would be helpful.


Update 2012-01-29

I was using wm_concat incorrectly before, but when I use it as you have shown, I am getting this error:

 ORA-06502: PL/SQL: numeric or value error: character string buffer too
 small ORA-06512: at "WMSYS.WM_CONCAT_IMPL", line 30
 06502. 00000 -  "PL/SQL: numeric or value error%s"
 *Cause:    
 *Action:

I believe there may be some higher level issues at play. My sysadmin had no answer for the "No more data to read from socket" issue. Could this be another.

My SQL knowledge is limited, and with the length and complexity of the query, I can't seem to implement the sys_connect_by_path; completely my own fault.

Sorry about the delayed response. I was pulled away to complete another task. Thank you for all of your help. Thank you ShadowWizard for the bounty.

EDIT
Here is how I have used wm_concat in the current instance:

 replace(cast(wm_concat(PERSON.MASTER_PERSON_FIRST_NAME   || ' '   ||
 PERSON.MASTER_PERSON_MIDDLE_INITIAL   || ' '   ||
 PERSON.MASTER_PERSON_LAST_NAME   || ','   || ' '   ||
 ORGANIZATION.MASTER_ORG_NAME) AS VARCHAR2(1000 BYTE)), ',', ', ') AS
 "Owner(s)",

Sorry, forgot to include that.

Alex Poole
  • 183,384
  • 11
  • 179
  • 318
user1983682
  • 208
  • 6
  • 20
  • Is no one answering because I asked incorrectly? I was a little confused with Don's edit, thinking maybe he did that because I am not researched enough? – user1983682 Jan 17 '13 at 14:09
  • Nope, you asked well as far as I can see. Some people think that every keyword should be marked as code, it's long time issue we got here but really not related to you. People probably just don't know the answer, that's all. Well, done my best to improve readability and added relevant tag, as we're talking about doing it with SQL, right? – Shadow The GPT Wizard Jan 17 '13 at 14:13
  • OK, trying to set a bounty here as last resort to draw attention. :) – Shadow The GPT Wizard Jan 23 '13 at 11:52
  • 1
    Does the row number, sys connect by path method mentioned [here](http://stackoverflow.com/questions/1076011/how-can-multiple-rows-be-concatenated-into-one-in-oracle-without-creating-a-stor) work for you ? – Appleman1234 Jan 23 '13 at 17:35
  • 1
    When you get the "No more data to read from socket", that usually means there was a serious error, which is usually an Oracle bug. Tell your DBA to check the alert log for ORA-600 errors at the time the error occurred. – Jon Heller Jan 23 '13 at 18:56
  • @user1983682 can you please check the given answer and see if it solve your problem? – Shadow The GPT Wizard Jan 28 '13 at 09:19
  • @user1983682 - the `sys_connect_by_path` approach wraps two extra levels of `select` around your original query, which you may not need to change; its complexity shouldn't make any difference really. You may still hit the 4k `varchar2` limit with this approach too though. – Alex Poole Jan 29 '13 at 14:45

1 Answers1

5

Not sure why wm_concat didn't work for you, but I suspect you had it at the wrong level or were grouping oddly.

If I set up some dummy data:

create table issues (id number);

create table owners (id number, first varchar2(10), middle varchar2(10),
    last varchar2(10), org varchar2(3));

create table issue_owners (issue_id number, owner_id number);

insert into issues (id) values (1);
insert into issues (id) values (2);
insert into issues (id) values (3);

insert into owners (id, first, middle, last, org)
    values (11, 'Sam', null, 'Smith', 'AAA');
insert into owners (id, first, middle, last, org)
    values (12, 'Stan', null, 'Bird', 'BBB');
insert into owners (id, first, middle, last, org)
    values (13, 'Nancy', null, 'Bird', 'BBB');
insert into owners (id, first, middle, last, org)
    values (14, 'Mike', null, 'Owen', 'CCC');

insert into issue_owners (issue_id, owner_id) values (1, 11);
insert into issue_owners (issue_id, owner_id) values (2, 12);
insert into issue_owners (issue_id, owner_id) values (2, 13);
insert into issue_owners (issue_id, owner_id) values (3, 14);

... which gives the same initial output as your paired-down sample:

column issue_id format 9 heading "#"
column owner format a50 heading "Owner"

select i.id as issue_id,
    o.first
        || case when o.middle is null then null else ' ' || o.middle end
        || ' ' || last || ', ' ||o.org as owner
from issues i
left join issue_owners io on io.issue_id = i.id
left join owners o on o.id = io.owner_id
order by issue_id, owner;

 # Owner
-- --------------------------------------------------
 1 Sam Smith, AAA
 2 Nancy Bird, BBB
 2 Stan Bird, BBB
 3 Mike Owen, CCC

4 rows selected.

I can use wm_concat to aggregate the names:

select issue_id,
    replace(cast(wm_concat(owner_name) as varchar2(4000)), ',', ', ')
        || ', ' || owner_org as owner
from (
    select i.id as issue_id,
        o.first
            || case when o.middle is null then null else ' ' || o.middle end
            || ' ' || last as owner_name,
        o.org as owner_org
    from issues i
    left join issue_owners io on io.issue_id = i.id
    left join owners o on o.id = io.owner_id
)
group by issue_id, owner_org
order by issue_id, owner;

 # Owner
-- --------------------------------------------------
 1 Sam Smith, AAA
 2 Stan Bird, Nancy Bird, BBB
 3 Mike Owen, CCC

3 rows selected.

The replace is just putting the space between the names, which isn't entirely relevant, and I'm casting to varchar2 because wm_concat returns a clob which causes a problem concatenating the org. At least, it's a clob in 11gR2 - I don't have a 10g instance with wm_concat available, but I think it returns varchar2 in earlier versions; if so the cast isn't needed and it would be more like:

select issue_id,
    replace(wm_concat(owner_name), ',', ', ') || ', ' || owner_org as owner
from (
...

I'm not sure where your org value is coming from so this is probably simplified, and I don't know what you want to happen if the org is linked to a person (rather than an issue, or your equivalent) and an issue has two owners with different org values.


If this isn't getting you any closer then maybe you can post a cut-down version of your query, replacing the long multiple-table part with some fixed data and showing how you're trying to use wm_concat against it; or your own version of sample data-build that shows the same behaviour.


Alternative sys_connect_by_path method as suggested Appleman1234, for the same data:

select issue_id,
    ltrim(max(sys_connect_by_path(owner_name, ', '))
        keep (dense_rank last order by curr), ', ')
        || ', ' || owner_org as owner
from (
    select issue_id,
        owner_name,
        owner_org,
        row_number() over (partition by issue_id order by owner_name) as curr,
        row_number() over (partition by issue_id order by owner_name) - 1 as prev
    from (
        select i.id as issue_id,
            o.first
                || case when o.middle is null then null else ' ' || o.middle end
                || ' ' || last as owner_name,
            o.org as owner_org
        from issues i
        left join issue_owners io on io.issue_id = i.id
        left join owners o on o.id = io.owner_id
    )
)
group by issue_id, owner_org
connect by prev = prior curr and issue_id = PRIOR issue_id
start with curr = 1;

 # Owner
-- --------------------------------------------------
 1 Sam Smith, AAA
 2 Nancy Bird, Stan Bird, BBB
 3 Mike Owen, CCC

3 rows selected.

If you end up using that, Appleman1234 should add an answer and I'll remove this part, as he should get credit for suggesting it! I wanted to try it anyway, I've seen it before but hadn't remembered it...

Alex Poole
  • 183,384
  • 11
  • 179
  • 318
  • @user1983682 - regarding your `character string buffer too small` error; how many values are you trying to concatenate with `wm_concat`? The result is a `varchar2` in 10g, which will be restricted to 4k (or 32k, but I think 4k!); though you're then casting it to 1k anyway. If you have more values than that and you don't mind losing the end of the list, you could limit the number of rows being passed to it, but you'd have to set a number low enough to make sure you never hit the limit. – Alex Poole Jan 29 '13 at 14:39
  • It is only first, middle and last name, of max three people, then maybe org if I can work it out. The problem is that I am seeing this: `Mark M. Majors, Mark M. Majors, Mark M. Majors....` I am not sure why it is repeating the same value over and over, probably causing the error. – user1983682 Jan 29 '13 at 15:19
  • @user1983682 - the 4k limit is against the total result length, not the length of each name; if you allowed 16 chars per name then you could only fit 256 names in, for example. Three people shouldn't be an issue. But the repetition looks like it's coming from the underlying data; do you have duplicates if you run the query without the `wm_concat`? – Alex Poole Jan 29 '13 at 15:29
  • [It's a 4k limit for the total length of the string](http://stackoverflow.com/questions/8823509/how-to-concatenate-strings/8825034#8825034) Alex (I think I ran this in 11g), cc @user1983682 – Ben Jan 29 '13 at 19:29
  • @Ben - yep, that's what I was trying to clarify in the previous comment, clearly not very successfully! Seems like the expected data shouldn't be close to that though, so I think there's something wrong with the data being passed in... – Alex Poole Jan 29 '13 at 19:42
  • @AlexPoole No, I do not have dups without the wm_concat. All is good in the query until I add that function. – user1983682 Jan 30 '13 at 15:32
  • @user1983682 - well that's weird, I'm not sure how to mimic that. Without a reproducible test case I don't know how to help more. Are you able to cut it down to the minimum that causes this effect, with dummy data instead of your real tables? – Alex Poole Jan 30 '13 at 15:50
  • Unfortunately no. No one at my organization has write rights besides the dba, so I can not create dummy tables. I believe I have given up on this. After further research I feel that the `CASE(COLLECT` statement would give me what I want, but due to the misconfiguration leading to the data socket error, I can not test the theory (even at a very basic level, `CASE(COLLECT` produces the same error). I thank you all for the help. Great suggestions. I am leaving it in the hands of the dba with instructions to try `CASE(COLLECT`. Should it work, I will update the thread. Again, thank you. – user1983682 Jan 31 '13 at 13:22
  • @user1983682 - I was thinking of CTEs built with fixed data selected from dual, which is all in memory and doesn't need physical tables; but that's a bit moot if you're going in another direction. Good luck *8-) – Alex Poole Jan 31 '13 at 13:57