Below is an example. TABLE 1 is manually created where the first three columns
are loaded here from an external file. Fourth column(SHOWROOM_ID) will be taken from TABLE2
and the rest of the columns in TABLE 1 will be updated based on criteria.
TABLE 1
NAME |OLD_CPR_NO |OLD_COS_NO |SHOWROOM_ID|NM_CPR_COS_MAT|NM_CPR_MAT|COS_CPR_MAT|
------------------------------------------------------------------------------------
FORD | 45 | 487 | | | |
TOYOTA | 78 | 562 | | | |
BENZ | 55 | 789 | | | |
JEEP | 66 | 124 | | | |
HONDA | 34 | 142 | | | |
KIA | 12 | 962 | | | |
GM | 89 | 7787 | | | |
CHRYSLER | 45 | 236 | | | |
AUDI | 67 | 4789 | | | |
TABLE 2
PK|NAME |OLD_CPR_NO |OLD_COS_NO |SHOWROOM_ID
---------------------------------------------
1 |FORD | 45 | 487 | 1
2 |TOYOTA | 78 | 562 | 2
3 |CIAT | 55 | 789 | 3
4 |JEEP | 66 | 124 | 5
5 |HONDA | 34 | 456 | 6
6 |MUSTANG | 12 | 962 | 7
7 |GM | 89 | 56 | 8
8 |CHRYSLER | 45 | 236 | 9
9 |AUDI | 67 | 4789 | 10
STEP 1: Update NM_CPR_COS_MAT column from table 1. This is an indicator field
where NAME,OLD_CPR_NO,OLD_COS_NO matches from TABLE 1 and TABLE 2 then assign indicator 'Y'
I was able to attain the results based on my below query:
UPDATE TABLE_1 TAB1
SET NM_CPR_COS_MAT = (SELECT 'Y'
FROM
TABLE_2 TAB2
WHERE
TRIM(TAB1.NAME) = TRIM(TAB2.NAME)
AND TRIM(TAB1.OLD_CPR_NO) = TRIM(TAB2.OLD_CPR_NO)
AND TRIM(TAB1.OLD_COS_NO) = TRIM(TAB2.OLD_COS_NO)
;
COMMIT;
UPDATE TABLE_1 TAB1
SET SHOWROOM_ID= (SELECT TAB2.SHOWROOM_ID
FROM
TABLE_2 TAB2
WHERE
TRIM(TAB1.NAME) = TRIM(TAB2.NAME)
AND TRIM(TAB1.OLD_CPR_NO) = TRIM(TAB2.OLD_CPR_NO)
AND TRIM(TAB1.OLD_COS_NO) = TRIM(TAB2.OLD_COS_NO)
AND TRIM(TAB1.NM_CPR_COS_MAT) = 'Y'
;
COMMIT;
RESULT:
TABLE 1
NAME |OLD_CPR_NO |OLD_COS_NO |SHOWROOM_ID|NM_CPR_COS_MAT|NM_CPR_MAT|COS_CPR_MAT|
------------------------------------------------------------------------------------
FORD | 45 | 487 | 1 | Y | |
TOYOTA | 78 | 562 | 2 | Y | |
BENZ | 55 | 789 | | | |
JEEP | 66 | 124 | 5 | Y | |
HONDA | 34 | 142 | | | |
KIA | 12 | 962 | | | |
GM | 89 | 7787 | | | |
CHRYSLER | 45 | 236 | 9 | Y | |
AUDI | 67 | 4789 | 10 | Y | |
But I am getting errors if I tried to use the join statements.
UPDATE TABLE_1 TAB1
SET NM_CPR_COS_MAT = 'Y'
FROM
TABLE_2 TAB2 JOIN
TABLE_1 TAB1 ON
TRIM(TAB1.OLD_CPR_NO) = TRIM(TAB2.OLD_CPR_NO)
WHERE
TRIM(TAB1.NAME) = TRIM(TAB2.NAME)
AND TRIM(TAB1.OLD_COS_NO) = TRIM(TAB2.OLD_COS_NO)
;
COMMIT;
ORA-00933: SQL command not properly ended.
From the below resulting table, I have to again UPDATE SHOWROOM_ID column and NM_CPR_MAT
TABLE 1
NAME |OLD_CPR_NO |OLD_COS_NO |SHOWROOM_ID|NM_CPR_COS_MAT|NM_CPR_MAT|COS_CPR_MAT|
------------------------------------------------------------------------------------
FORD | 45 | 487 | 1 | Y | |
TOYOTA | 78 | 562 | 2 | Y | |
BENZ | 55 | 789 | | | |
JEEP | 66 | 124 | 5 | Y | |
HONDA | 34 | 142 | | | |
KIA | 12 | 962 | | | |
GM | 89 | 7787 | | | |
CHRYSLER | 45 | 236 | 9 | Y | |
AUDI | 67 | 4789 | 10 | Y | |
STEP 2:
UPDATE TABLE_1 TAB1
SET NM_CPR_MAT = (SELECT 'Y'
FROM
TABLE_2 TAB2
WHERE
TRIM(TAB1.NAME) = TRIM(TAB2.NAME)
AND TRIM(TAB1.OLD_CPR_NO) = TRIM(TAB2.OLD_CPR_NO)
AND TRIM(NM_CPR_COS_MAT) IS NULL
;
COMMIT;
UPDATE TABLE_1 TAB1
SET SHOWROOM_ID= (SELECT TAB2.SHOWROOM_ID
FROM
TABLE_2 TAB2
WHERE
WHERE
TRIM(TAB1.NAME) = TRIM(TAB2.NAME)
AND TRIM(TAB1.OLD_CPR_NO) = TRIM(TAB2.OLD_CPR_NO)
AND TRIM(NM_CPR_COS_MAT) IS NULL
AND TRIM(NM_CPR_MAT) = 'Y'
;
COMMIT;
I AM GETTING THE BELOW RESULTS.I AM GETTING THE CORRECT 'Y' IN NM_CPR_MAT COLUMNS
AND ALSO THE CORRECT NUMBERS IN SHOWROOM_ID FOR THE NEW UPDATE STATEMENT BUT THE NUMBERS
THAT WAS UPDATED IN THE UPDATED STATEMENT WERE GONE.
TABLE 1
NAME |OLD_CPR_NO |OLD_COS_NO |SHOWROOM_ID|NM_CPR_COS_MAT|NM_CPR_MAT|COS_CPR_MAT|
------------------------------------------------------------------------------------
FORD | 45 | 487 | | Y | |
TOYOTA | 78 | 562 | | Y | |
BENZ | 55 | 789 | | | |
JEEP | 66 | 124 | | Y | |
HONDA | 34 | 142 | 6 | | Y |
KIA | 12 | 962 | | | |
GM | 89 | 7787 | 8 | | Y |
CHRYSLER | 45 | 236 | | Y | |
AUDI | 67 | 4789 | | Y | |
Incorrect syntax, missing (SELECT before 'Y', and don't forget the matching closing bracket at the end.
UPDATE TABLE_1 TAB1
SET NM_CPR_COS_MAT = (SELECT 'Y'
FROM
TABLE_2 TAB2 JOIN
TABLE_1 TAB1 ON
TRIM(TAB1.OLD_CPR_NO) = TRIM(TAB2.OLD_CPR_NO)
WHERE
TRIM(TAB1.NAME) = TRIM(TAB2.NAME)
AND TRIM(TAB1.OLD_COS_NO) = TRIM(TAB2.OLD_COS_NO));
I am practicing on cloudera yarn VMware Player(non commercial use).
My script in pig is,
a1 = load '/user/training/my_hdfs/id' using PigStorage('\t') as(id:int,name:chararray,desig:chararray);
a2 = load '/user/training/my_hdfs/trips' using PigStorage('\t') as(id:int,place:chararray,no_trips:int);
a3 = join a1 by id,a2 by id;
a4 = group a3 by a1::id;
illustrate a4;
After illustrate it is showing message as,
2017-08-21 07:52:11,926 [main] ERROR org.apache.pig.tools.grunt.Grunt - ERROR 2997: Encountered IOException. Exception : Error compiling operator POLocalRearrange
Dataset is,
Table id
101 aaa executive
102 bbb manager
104 hhh manager
106 ccc trainee
109 hhh trainee
Table trips
101 pune 1
101 hyd 2
102 pune 2
102 hyd 3
102 bang 4
When i tried running you program with the provided data,i too get some error as the delimiter in you files is not consistent. Some where its space and some where its tab(may be its because copy pasting). I make the delimiter universal(using tab) and everything works perfectly fine.
Try to use dump a1 or dump a2 and see if you can see data in correct columns.
For me it worked perfect after making delimiter universal and illustrate a4 gives below output:
------------------------------------------------------------------
| a1 | id:int | name:chararray | desig:chararray |
------------------------------------------------------------------
| | 101 | aaa | executive |
| | 101 | aaa | executive |
------------------------------------------------------------------
----------------------------------------------------------------
| a2 | id:int | place:chararray | no_trips:int |
----------------------------------------------------------------
| | 101 | pune | 1 |
| | 101 | hyd | 2 |
----------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------
| a3 | a1::id:int | a1::name:chararray | a1::desig:chararray | a2::id:int | a2::place:chararray | a2::no_trips:int |
------------------------------------------------------------------------------------------------------------------------------------------------
| | 101 | aaa | executive | 101 | pune | 1 |
| | 101 | aaa | executive | 101 | hyd | 2 |
| | 101 | aaa | executive | 101 | pune | 1 |
| | 101 | aaa | executive | 101 | hyd | 2 |
------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| a4 | group:int | a3:bag{:tuple(a1::id:int,a1::name:chararray,a1::desig:chararray,a2::id:int,a2::place:chararray,a2::no_trips:int)} |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| | 101 | {(101, ..., 1), ..., (101, ..., 2)} |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Given two sets, e.g.:
{A B C}, {1 2 3 4 5 6}
I want to generate the Cartesian product in an order that puts as much space as possible between equal elements. For example, [A1, A2, A3, A4, A5, A6, B1…] is no good because all the As are next to each other. An acceptable solution would be going "down the diagonals" and then every time it wraps offsetting by one, e.g.:
[A1, B2, C3, A4, B5, C6, A2, B3, C4, A5, B6, C1, A3…]
Expressed visually:
| | A | B | C | A | B | C | A | B | C | A | B | C | A | B | C | A | B | C |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 1 | | | | | | | | | | | | | | | | | |
| 2 | | 2 | | | | | | | | | | | | | | | | |
| 3 | | | 3 | | | | | | | | | | | | | | | |
| 4 | | | | 4 | | | | | | | | | | | | | | |
| 5 | | | | | 5 | | | | | | | | | | | | | |
| 6 | | | | | | 6 | | | | | | | | | | | | |
| 1 | | | | | | | | | | | | | | | | | | |
| 2 | | | | | | | 7 | | | | | | | | | | | |
| 3 | | | | | | | | 8 | | | | | | | | | | |
| 4 | | | | | | | | | 9 | | | | | | | | | |
| 5 | | | | | | | | | | 10| | | | | | | | |
| 6 | | | | | | | | | | | 11| | | | | | | |
| 1 | | | | | | | | | | | | 12| | | | | | |
| 2 | | | | | | | | | | | | | | | | | | |
| 3 | | | | | | | | | | | | | 13| | | | | |
| 4 | | | | | | | | | | | | | | 14| | | | |
| 5 | | | | | | | | | | | | | | | 15| | | |
| 6 | | | | | | | | | | | | | | | | 16| | |
| 1 | | | | | | | | | | | | | | | | | 17| |
| 2 | | | | | | | | | | | | | | | | | | 18|
or, equivalently but without repeating the rows/columns:
| | A | B | C |
|---|----|----|----|
| 1 | 1 | 17 | 15 |
| 2 | 4 | 2 | 18 |
| 3 | 7 | 5 | 3 |
| 4 | 10 | 8 | 6 |
| 5 | 13 | 11 | 9 |
| 6 | 16 | 14 | 12 |
I imagine there are other solutions too, but that's the one I found easiest to think about. But I've been banging my head against the wall trying to figure out how to express it generically—it's a convenient thing that the cardinality of the two sets are multiples of each other, but I want the algorithm to do The Right Thing for sets of, say, size 5 and 7. Or size 12 and 69 (that's a real example!).
Are there any established algorithms for this? I keep getting distracted thinking of how rational numbers are mapped onto the set of natural numbers (to prove that they're countable), but the path it takes through ℕ×ℕ doesn't work for this case.
It so happens the application is being written in Ruby, but I don't care about the language. Pseudocode, Ruby, Python, Java, Clojure, Javascript, CL, a paragraph in English—choose your favorite.
Proof-of-concept solution in Python (soon to be ported to Ruby and hooked up with Rails):
import sys
letters = sys.argv[1]
MAX_NUM = 6
letter_pos = 0
for i in xrange(MAX_NUM):
for j in xrange(len(letters)):
num = ((i + j) % MAX_NUM) + 1
symbol = letters[letter_pos % len(letters)]
print "[%s %s]"%(symbol, num)
letter_pos += 1
String letters = "ABC";
int MAX_NUM = 6;
int letterPos = 0;
for (int i=0; i < MAX_NUM; ++i) {
for (int j=0; j < MAX_NUM; ++j) {
int num = ((i + j) % MAX_NUM) + 1;
char symbol = letters.charAt(letterPos % letters.length);
String output = symbol + "" + num;
++letterPos;
}
}
What about using something fractal/recursive? This implementation divides a rectangular range into four quadrants then yields points from each quadrant. This means that neighboring points in the sequence differ at least by quadrant.
#python3
import sys
import itertools
def interleave(*iters):
for elements in itertools.zip_longest(*iters):
for element in elements:
if element != None:
yield element
def scramblerange(begin, end):
width = end - begin
if width == 1:
yield begin
else:
first = scramblerange(begin, int(begin + width/2))
second = scramblerange(int(begin + width/2), end)
yield from interleave(first, second)
def scramblerectrange(top=0, left=0, bottom=1, right=1, width=None, height=None):
if width != None and height != None:
yield from scramblerectrange(bottom=height, right=width)
raise StopIteration
if right - left == 1:
if bottom - top == 1:
yield (left, top)
else:
for y in scramblerange(top, bottom):
yield (left, y)
else:
if bottom - top == 1:
for x in scramblerange(left, right):
yield (x, top)
else:
halfx = int(left + (right - left)/2)
halfy = int(top + (bottom - top)/2)
quadrants = [
scramblerectrange(top=top, left=left, bottom=halfy, right=halfx),
reversed(list(scramblerectrange(top=top, left=halfx, bottom=halfy, right=right))),
scramblerectrange(top=halfy, left=left, bottom=bottom, right=halfx),
reversed(list(scramblerectrange(top=halfy, left=halfx, bottom=bottom, right=right)))
]
yield from interleave(*quadrants)
if __name__ == '__main__':
letters = 'abcdefghijklmnopqrstuvwxyz'
output = []
indices = dict()
for i, pt in enumerate(scramblerectrange(width=11, height=5)):
indices[pt] = i
x, y = pt
output.append(letters[x] + str(y))
table = [[indices[x,y] for x in range(11)] for y in range(5)]
print(', '.join(output))
print()
pad = lambda i: ' ' * (2 - len(str(i))) + str(i)
header = ' |' + ' '.join(map(pad, letters[:11]))
print(header)
print('-' * len(header))
for y, row in enumerate(table):
print(pad(y)+'|', ' '.join(map(pad, row)))
Outputs:
a0, i1, a2, i3, e0, h1, e2, g4, a1, i0, a3, k3, e1,
h0, d4, g3, b0, j1, b2, i4, d0, g1, d2, h4, b1, j0,
b3, k4, d1, g0, d3, f4, c0, k1, c2, i2, c1, f1, a4,
h2, k0, e4, j3, f0, b4, h3, c4, j2, e3, g2, c3, j4,
f3, k2, f2
| a b c d e f g h i j k
-----------------------------------
0| 0 16 32 20 4 43 29 13 9 25 40
1| 8 24 36 28 12 37 21 5 1 17 33
2| 2 18 34 22 6 54 49 39 35 47 53
3| 10 26 50 30 48 52 15 45 3 42 11
4| 38 44 46 14 41 31 7 23 19 51 27
If your sets X and Y are sizes m and n, and Xi is the index of the element from X that's in the ith pair in your Cartesian product (and similar for Y), then
Xi = i mod n;
Yi = (i mod n + i div n) mod m;
You could get your diagonals a little more spread out by filling out your matrix like this:
for (int i = 0; i < m*n; i++) {
int xi = i % n;
int yi = i % m;
while (matrix[yi][xi] != 0) {
yi = (yi+1) % m;
}
matrix[yi][xi] = i+1;
}
I want to generate a unique id in Oracle, it contains alphanumerics and length is 9-digits.
I tried,
==> select substr(sys_guid(),5,9) guid from dual;
will it have the unique nature?
please anyone help me out.
Thank u.
Seems to be overcomplicated when you cound just use a numeric sequence but you could do:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE OR REPLACE FUNCTION numberToAlnumString(
n IN NUMBER
) RETURN VARCHAR2
AS
i NUMBER := n;
s VARCHAR2(9);
r NUMBER(2,0);
BEGIN
WHILE i > 0 LOOP
r := MOD( i, 36 );
i := ( i - r ) / 36;
IF ( r < 10 ) THEN
s := TO_CHAR(r) || s;
ELSE
s := CHR( 55 + r ) || s;
END IF;
END LOOP;
RETURN LPAD( s, 9, '0' );
END;
/
CREATE SEQUENCE test__id__seq INCREMENT BY 1 START WITH 1
/
CREATE TABLE test (
id CHAR(9) NOT NULL,
name VARCHAR2(20)
)
/
CREATE OR REPLACE TRIGGER test_ins_trig
BEFORE INSERT ON test
FOR EACH ROW
BEGIN
:new.id := numberToAlnumString( test__id__seq.NEXTVAL );
END;
/
INSERT INTO test ( name )
SELECT TO_CHAR( LEVEL )
FROM DUAL
CONNECT BY LEVEL < 100
/
Query 1:
SELECT * FROM test
Results:
| ID | NAME |
|-----------|------|
| 000000001 | 1 |
| 000000002 | 2 |
| 000000003 | 3 |
| 000000004 | 4 |
| 000000005 | 5 |
| 000000006 | 6 |
| 000000007 | 7 |
| 000000008 | 8 |
| 000000009 | 9 |
| 00000000A | 10 |
| 00000000B | 11 |
| 00000000C | 12 |
| 00000000D | 13 |
| 00000000E | 14 |
| 00000000F | 15 |
| 00000000G | 16 |
| 00000000H | 17 |
| 00000000I | 18 |
| 00000000J | 19 |
| 00000000K | 20 |
| 00000000L | 21 |
| 00000000M | 22 |
| 00000000N | 23 |
| 00000000O | 24 |
| 00000000P | 25 |
| 00000000Q | 26 |
| 00000000R | 27 |
| 00000000S | 28 |
| 00000000T | 29 |
| 00000000U | 30 |
| 00000000V | 31 |
| 00000000W | 32 |
| 00000000X | 33 |
| 00000000Y | 34 |
| 00000000Z | 35 |
| 000000010 | 36 |
| 000000011 | 37 |
| 000000012 | 38 |
| 000000013 | 39 |
| 000000014 | 40 |
| 000000015 | 41 |
| 000000016 | 42 |
| 000000017 | 43 |
| 000000018 | 44 |
| 000000019 | 45 |
| 00000001A | 46 |
| 00000001B | 47 |
| 00000001C | 48 |
| 00000001D | 49 |
| 00000001E | 50 |
| 00000001F | 51 |
| 00000001G | 52 |
| 00000001H | 53 |
| 00000001I | 54 |
| 00000001J | 55 |
| 00000001K | 56 |
| 00000001L | 57 |
| 00000001M | 58 |
| 00000001N | 59 |
| 00000001O | 60 |
| 00000001P | 61 |
| 00000001Q | 62 |
| 00000001R | 63 |
| 00000001S | 64 |
| 00000001T | 65 |
| 00000001U | 66 |
| 00000001V | 67 |
| 00000001W | 68 |
| 00000001X | 69 |
| 00000001Y | 70 |
| 00000001Z | 71 |
| 000000020 | 72 |
| 000000021 | 73 |
| 000000022 | 74 |
| 000000023 | 75 |
| 000000024 | 76 |
| 000000025 | 77 |
| 000000026 | 78 |
| 000000027 | 79 |
| 000000028 | 80 |
| 000000029 | 81 |
| 00000002A | 82 |
| 00000002B | 83 |
| 00000002C | 84 |
| 00000002D | 85 |
| 00000002E | 86 |
| 00000002F | 87 |
| 00000002G | 88 |
| 00000002H | 89 |
| 00000002I | 90 |
| 00000002J | 91 |
| 00000002K | 92 |
| 00000002L | 93 |
| 00000002M | 94 |
| 00000002N | 95 |
| 00000002O | 96 |
| 00000002P | 97 |
| 00000002Q | 98 |
| 00000002R | 99 |
No, this approach will not have the unique nature.
if you want auto increment in your column value you can use Sequence for the this.
CREATE OR REPLACE SEQUENCE dept_seq
INCREMENT BY 1
START WITH 100000000
NOMAXVALUE
NOCYCLE
CACHE 10;
after creating sequence you can use After Insert Trigger to insert identical value.
here is trigger example...
CREATE OR REPLACE TRIGGER dep_ins_trig
BEFORE INSERT ON <table_name>
FOR EACH ROW
BEGIN
SELECT dept_seq.NEXTVAL
INTO :new.emp_id
FROM dual;
END;
/
---------------------------------------------------------------------------------------
Trigger and Sequence can be used when you want serialized (Auto Increment) number that anyone can easily read/remember/understand. But if you don't want to manage ID Column (like emp_id) by this way, and value of this column is not much considerable, you can use SYS_GUID() at Table Creation to get Auto Increment like this.
CREATE TABLE <table_name>
(emp_id RAW(16) DEFAULT SYS_GUID() PRIMARY KEY,
name VARCHAR2(30));
Now your emp_id column will accept "globally unique identifier value".
you can insert value in table by ignoring emp_id column like this.
INSERT INTO <table_name> (name) VALUES ('name value');
So, it will insert unique value to your emp_id Column.
I have the following table which I'd like to turn into a report:
ClientGroup | Product | Client | Quantity
-----------------------------------------
Gr1 | P1 | C1 | 10
Gr1 | P1 | C2 | 20
Gr1 | P1 | C3 | 30
Gr1 | P2 | C1 | 40
Gr1 | P2 | C2 | 50
Gr1 | P2 | C3 | 60
Gr2 | P1 | C4 | 70
Gr2 | P1 | C5 | 80
Gr2 | P1 | C6 | 90
Gr2 | P2 | C4 | 100
Gr2 | P2 | C5 | 110
Gr2 | P2 | C6 | 120
The report would have the following layout:
--------------------
| G1 |
--------------------
Client | P1 | P2 |
--------------------
C1 | 10 | 40 |
C2 | 20 | 50 |
C3 | 30 | 60 |
--------------------
Total | 60 |150 |
--------------------
| G2 |
--------------------
Client | P1 | P2 |
--------------------
C4 | 70 | 100 |
C5 | 80 | 110 |
C5 | 90 | 120 |
--------------------
Total | 240 | 330 |
--------------------
What I'm doing is to create a Matrix, add a row group on ClientGroup, a sub group row on Client, a column group on Product with Quantity as detail. In the designer it looks somewhat like this:
---------------------------------------------
| ClientGroup | Client | [Product] |
---------------------------------------------
| [ClientGroup] | [Client] | Sum([Quantity])|
---------------------------------------------
I then hide the ClientGroup column and it seems I'm almost there. What I can't figure out is how to have a header over the columns Client and [Product] displaying the current ClientGroup.
Is it possible? Any ideas?
You can get pretty close:
Set the Headings row to be hidden.
Right-click the [Client] cell and select Insert Row > Outside Group - Above, twice.
Copy [ClientGroup] into the left-hand cell on the first new row, and set the BorderStyle-Right of the cell to be None.
Select the right-hand cell on the first new row, and set the BorderStyle-Left and -Right of the cell to be None.
Copy the heading Client into the left-hand cell on the second new row.
Copy [Product] into the right-hand cell on the second new row.
Your report should look something like this in the designer:
--------------------------------------------------
| ClientGroup | Client | [Product] |
--------------------------------------------------
| [ClientGroup] | [ClientGroup] | |
| |---------------------------------
| | Client | [Product] |
| |---------------------------------
| | [Client] | Sum([Quantity])|
--------------------------------------------------
If you preview it, the results should be pretty close to the desired layout.