views:

148

answers:

2

I have a postgresql table containing movements of different items (models) between warehouses.

For example, the following record means that 5 units of model 1 have been sent form warehouse 1 to 2:

source target model units
------ ------ ----- -----
     1      2     1     5

I am trying to build a SQL query to obtain the difference between units sent and received, grouped by models. Again with an example:

source target model units
------ ------ ----- -----
     1      2     1     5  -- 5 sent from 1 to 2
     1      2     2     1
     2      1     1     2  -- 2 sent from 2 to 1
     2      1     1     1  -- 1 more sent from 2 to 1

The result should be:

source target model diff
------ ------ ----- ----
     1      2     1    2   -- 5 sent minus 3 received
     1      2     2    1

I wonder if this is possible with a single SQL query.

Here is the table creation script and some data, just in case anyone wants to try it:

CREATE TEMP TABLE movements
(
    source  INTEGER,
    target  INTEGER,
    model   INTEGER,
    units   INTEGER
);

insert into movements values (1,2,1,5);
insert into movements values (1,2,2,1);
insert into movements values (2,1,1,2);
insert into movements values (2,1,1,1);
+3  A: 

You can do this with two subselects which sum the movements in each direction, and then union and sum the results of those two subqueries:

SELECT source, target, model, SUM(units)
FROM (
    SELECT source, target, model, SUM(units) AS units
    FROM movements
    WHERE source < target
    GROUP BY source, target, model
    UNION ALL
    SELECT target, source, model, SUM(-units) AS units
    FROM movements
    WHERE source > target
    GROUP BY source, target, model
) T1
GROUP BY source, target, model
Mark Byers
Great answer! very clear!
noinflection
Thank you, let me try it. How does your query compare to the query provided by @Martin Smith, that avoids the use of "UNION ALL" (uses some CASE clauses instead) ?
Guido
@Guido: His answer scans the table only once, mine scans it twice. How many rows do you have? Is performance an issue? I would claim that my suggestion is more clear / readable, but thats my own opinion and is subjective.
Mark Byers
I've scrubbed my code formatting up a bit to make it marginally more aesthetic but I have to agree the 5 repeating CASE statements detract from readability. I would claim that mine is perhaps more in the spirit of the "single SQL query" part of the question though by virtue of only containing 1 "SELECT" :-)
Martin Smith
I mark Martin's answer as valid, as it is easier to add additional filters in his query. The table is quite big as well. Thank you both.
Guido
+1  A: 

Does this do what you need? I don't have an Oracle DB to test against so I hope the rules on Grouping expressions are the same as for MS SQL Server

SELECT     
 CASE WHEN source < target THEN source ELSE target END AS source,
 CASE WHEN source < target THEN target ELSE source END AS target,
 SUM(CASE WHEN source < target THEN units ELSE -units END) AS Diff, 
    model
FROM  movements
GROUP BY
 CASE WHEN source < target THEN source ELSE target END,
 CASE WHEN source < target THEN target ELSE source END,
    model
Martin Smith