简体   繁体   中英

How to select columns that aren't part of an aggregate query using HAVING SUM() in the WHERE and selecting only certain rows on db2

Using AS400 db2 for this.

I have a table of orders. From that table I have to:

  • Get all orders from a specified list of order IDs and type
  • Group by the user_id on those orders
  • Check to make sure the total order amount on the group is greater than $100
  • Return all orders that matched the group but the results won't be grouped, which includes order_id which is not part of the group

I got a bit stuck because the AS400 did not like that I was asking to select a field that wasn't part of the group, which I need.

I came up with this query, but it's slow.

-- Create a common temp table we can use in both places
WITH wantedOrders AS (
SELECT order_id FROM orders
WHERE
-- Only orders from the web
order_type = 'web'
-- And only orders that we want to get at this time
AND order_id IN
    (
        50,
        20,
        30
    )
)
-- Our main select that gets all order information, even the non-grouped stuff
SELECT
    t1.order_id,
    t1.user_id,
    t1.amount,
    t2.total_amount,
    t2.count
    
FROM orders AS t1
-- Join in the group data where we can do our query
JOIN (
    SELECT 
    user_id,
    SUM(amount) as total_amount,
    COUNT(*) AS count
    FROM
    orders
-- Re use the temp table to get the order numbers
    WHERE order_id IN (SELECT order_id FROM wantedOrders)
    GROUP BY
    user_id
    HAVING SUM(amount)>100
) AS t2 ON t2.user_id=t1.user_id
-- Make sure we only use the order numbers
WHERE order_id IN (SELECT order_id FROM wantedOrders)
ORDER BY t1.user_id ASC;

What's the better way to write this query?

Try this:

WITH 
  wantedOrders (order_id) AS
  (
    VALUES 1, 2
  )
, orders (order_id, user_id, amount) AS
  (
    VALUES
      (1, 1, 50)
    , (2, 1, 50)
    , (1, 2, 60)
    , (2, 2, 60)

    , (3, 3, 200)
    , (4, 3, 200)
   )
-- Our main select that gets all order information, even the non-grouped stuff
SELECT *
FROM
(
  SELECT
    order_id,
    user_id,
    amount,
    SUM   (amount) OVER (PARTITION BY user_id) AS total_amount,
    COUNT (*)      OVER (PARTITION BY user_id) AS count
  FROM orders t
  WHERE EXISTS 
  (
    SELECT 1 
    FROM wantedOrders w 
    WHERE w.order_id = t.order_id
  )
) A
WHERE total_amount > 100
ORDER BY user_id ASC
ORDER_ID USER_ID AMOUNT TOTAL_AMOUNT COUNT
1 2 60 120 2
2 2 60 120 2

If order_id is the PK of the table. Then just add the columns you need to the wantedOrders query and use it as your "base" (instead of using orders and refiltering it. You should end up joining wantedOrders with itself.

You can do:

select t.*
from orders t
join (
  select user_id
  from orders t
  where order_id in (50, 20, 30)
  group by user_id
  having sum(total_amount) > 100
) s on s.user_id = t.user_id

The first table orders as t will produce the data you want. It will be filtered by the second "table expression" s that preselects the groups according to your logic.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM