Is there a way to check that none of the rows in a grouping contain a value? - sql

I have a query that looks like this:
SELECT ordDetails.OrderId
FROM orders.OrderDetail ordDetails
WHERE ordDetails.Class <> 'O'
GROUP BY ordDetails.OrderId
But this does not work quite right. OrderId is not unique in this table (hence the group by clause). My where clause only removes OrderIds that have ALL rows with a Class of 'O'. (If even one row has a value other than 'O', then that OrderId is included in the result.)
I want all the OrderIds that have NO rows with a value of 'O'.
Normally I would just use an aggregate function like MAX or MIN. But alas, MAX returns 'R' and MIN returns '' (empty string).
What I really need is an aggregate function that checks that none of the values in the aggregate match a parameter. Unfortunately there is no such aggregate function.
Is there a way to say (in a group by query) "only give me results that have no rows matching an 'O'"?

I think you are looking for something like this:
select distinct
o.OrderId
from
OrderDetails as o
where not exists
(
select 1
from
OrderDetails as o2
where
o2.OrderId = o.OrderId and
o2.Class = '0'
)

You could also use:
SELECT ordDetails.OrderId
FROM OrderDetails ordDetails
GROUP BY ordDetails.OrderId
HAVING sum(case when ordDetails.Class='O' then 1 else 0 end)=0

And more options. First using the except set operator
select distinct OrderId from #OrderDetails
except
select OrderId from #OrderDetails where Class = 'O'
;
And conditional counting with HAVING clause
select Orderid
from #OrderDetails
group by OrderId
having count(case Class when 'O' then 1 else null end) = 0
;

Related

Why does this not return 0

I have a query like:
select nvl(nvl(sum(a.quantity),0)-nvl(cc.quantityCor,0),0)
from RCV_TRANSACTIONS a
LEFT JOIN (select c.shipment_line_id,c.oe_order_line_id,nvl(sum(c.quantity),0) quantityCor
from RCV_TRANSACTIONS c
where c.TRANSACTION_TYPE='CORRECT'
group by c.shipment_line_id,c.oe_order_line_id) cc on (a.shipment_line_id=cc.shipment_line_id and a.shipment_line_id=7085740)
where a.transaction_type='DELIVER'
and a.shipment_line_id=7085740
group by nvl(cc.quantityCor,0);
The query runs OK, but returns no value. I want it to return 0 if there is no quantity found. Where have I gone wrong?
An aggregation query with a GROUP BY returns no rows if all rows are filtered out.
An aggregation query with no GROUP BY always returns one row, even if all rows are filtered out.
So, just remove the GROUP BY. And change the SELECT to:
select coalesce(sum(a.quantity), 0) - coalesce(max(cc.quantityCor), 0)
I may be wrong, but it seems you merely want to subtract CORRECT quantity from DELIVER quantity for shipment 7085740. You don't need a complicated query for that. Especially your GROUP BY clauses make no sense if that is what you are after.
One way to write this query would be:
select
sum(case when transaction_type = 'DELIVER' then quantity else 0 end) -
sum(case when transaction_type = 'CORRECT' then quantity else 0 end) as diff
from rcv_transactions
where shipment_line_id = 7085740;
I had a query like this and was trying to return 'X' when the item is not valid.
SELECT case when segment1 is not null then segment1 else 'X' end
--INTO v_orgValidItem
FROM mtl_system_items_b
WHERE segment1='1676001000'--'Jul-00'--l_item
and organization_id=168;
..but it was returning NULL.
Changed to use aggregation with no group by and now it returns 'X' when the item is not valid.
SELECT case when max(segment1) is not null then max(segment1) else 'X' end valid
--INTO v_orgValidItem
FROM mtl_system_items_b
WHERE segment1='1676001000'--'Jul-00'--l_item
and organization_id=168;--l_ship_to_organization_id_pb;
Here is another example, proving the order of operations really matters.
When there is no match for this quote number, this query returns NULL:
SELECT MAX(NVL(QUOTE_VENDOR_QUOTE_NUMBER,0))
FROM PO_HEADERS_ALL
WHERE QUOTE_VENDOR_QUOTE_NUMBER='foo.bar';
..reversing the order of MAX and NVL makes all the difference. This query returns the NULL value condition:
SELECT NVL(MAX(QUOTE_VENDOR_QUOTE_NUMBER),0)
FROM PO_HEADERS_ALL
WHERE QUOTE_VENDOR_QUOTE_NUMBER='foo.bar';

Using SQL SUM with Case statement containing inner SELECT

I have two tables, an Orders table which contains a list of a users orders and a OrderShippingCosts table which contains a price for shipping each item based on the OrderTypeID in the Orders table.
I am running a query like below to calculate the total shipping costs:
SELECT
SUM(CASE
WHEN OR.OrderTypeID = 1
THEN (SELECT CostOfShippingSmallParcel
FROM OrderShippingCosts)
ELSE (SELECT CostOfShippingBigParcel
FROM OrderShippingCosts)
END) AS TotalShippingCost
FROM
Orders AS OR
But I'm getting the following error:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery
Does anyone know what is wrong with my query?
Function SUM takes an expression on input, which evaluates into single data value, not a dataset. Expression definition from MSDN:
Is a combination of symbols and operators that the SQL Server Database Engine evaluates to obtain a single data value.
You trying to pass to SUM function a dataset (which is result of subquery), not a single data value. This is simplification of what you trying to query:
SELECT SUM(SELECT Number FROM SomeTable)
It is not valid. The valid query would be:
SELECT SUM(Value) FROM SomeTable
In your particular case looks like you missing JOIN. Your original logic will result in summary of entire OrderShippingCosts table for each row of Orders table. I think, it should be something like this:
SELECT
SUM
(
CASE
WHEN ord.OrderTypeID = 1 THEN ship.CostOfShippingSmallParcel
ELSE ship.CostOfShippingBigParcel
END
) TotalShippingCost
FROM Orders AS ord
JOIN OrderShippingCosts ship ON /* your search condition, e.g.: ord.OrderID = ship.OrderID */
By the way, it is not a good idea to use reserved symbols as aliases, names and so on. In your query you use OR as alias for Orders table. Symbol OR is reserved for logical or operation. If you really need to use reserved symbol, wrap it into [ and ] square braces. Look here and here for more details.
The error message is clear, you can avoid it with a join:
SELECT
SUM(CASE WHEN [OR].OrderTypeID = 1
THEN CostOfShippingSmallParcel
ELSE CostOfShippingBigParcel END) AS TotalShippingCost
FROM Orders [OR]
CROSS JOIN OrderShippingCosts
You can try like this...
SELECT
CASE WHEN OR.OrderTypeID = 1
THEN (SELECT SUM(CostOfShippingSmallParcel) FROM OrderShippingCosts)
ELSE (SELECT SUM(CostOfShippingBigParcel) FROM OrderShippingCosts) END AS TotalShippingCost
FROM Orders AS OR
Let me know
select sum (or.TotalShippingCost)
FROM
SELECT
(CASE WHEN OR.OrderTypeID = 1
THEN (SELECT CostOfShippingSmallParcel FROM OrderShippingCosts)
ELSE (SELECT CostOfShippingBigParcel FROM OrderShippingCosts) END) AS TotalShippingCost
FROM Orders AS OR
Try this
SELECT
ISNULL
(
SUM
(
CASE
WHEN O.OrderTypeID = 1 THEN C.CostOfShippingSmallParcel
ELSE C.CostOfShippingBigParcel END
), 0
) AS TotalShippingCost
FROM
Orders AS O LEFT JOIN
OrderShippingCosts C ON O.Id = C.OrderId -- Your releation id

Joining a Temp Table to Actual Table

I need to verify that each order has been acknowledged. The problem is that each order can have multiple codes. The query I had (utilizing a CASE statement) would check for blank fields or fields with the string "None" to verify the order has not been acknowledged. It would return the appropriate result, but multiple rows (once for each possible response) and I only need (1).
I'm attempting to create a temp table that will return the appropriate result and join (via an order unique ID) the two tables together hoping to correct the multiple row issue. Here is the code:
DROP TABLE staging_TABLE;
CREATE TEMP TABLE staging_TABLE(
ORDERID varchar(256) ,
CODE varchar(256) );
/*Keeping data types consistent with the real table*/
INSERT INTO staging_TABLE
SELECT ORDERID,
CASE CODE
WHEN 'None' THEN 'No'
WHEN '' THEN 'No'
ELSE 'Yes'
END
FROM ORDERS
WHERE UTCDATE > SYSDATE - 10
AND CODE IS NOT NULL;
SELECT R.QUESTION,
R.ORDERNAME,
T.CODE
FROM ORDERS R
INNER JOIN staging_TABLE T
ON R.ORDERID= T.ORDERID
WHERE R.UTCDATE > SYSDATE - 10
AND R.CODE IS NOT NULL
AND R.CATEGORY IS NOT NULL
AND R.UTCDATE IS NOT NULL
GROUP BY
R.ORDER,
T.CODE,
R.ORDERNAME,
R.CODE
ORDER BY
R.ORDERNAME,
R.ORDER;
Am I doing this correctly? Or is this even the right approach?
Am I doing this correctly? Or is this even the right approach?
No. You don't need a temp table for this. Your query might look like this:
SELECT question, ordername
, CASE WHEN code IN ('None', '') THEN 'No' ELSE 'Yes' END AS code
FROM orders
WHERE utcdate > sysdate - 10
AND code IS NOT NULL
AND category IS NOT NULL
GROUP BY question, ordername, 3, "order"
ORDER BY ordername, "order";
ORDER is a reserved word. It's not possible to use it as column name unless double quoted. There is something wrong there.
AND R.UTCDATE IS NOT NULL is redundant. It can't be NULL anyway with WHERE R.UTCDATE > SYSDATE - 10
3 in my GROUP BY clause is a positional reference to the CASE expression. Alternatively you can spell it out again:
....
GROUP BY question, ordername
, CASE WHEN code IN ('None', '') THEN 'No' ELSE 'Yes' END
, "order"
You can use the DISTINCT keyword as follows so you will not need a temp table:
SELECT DISTINCT QUESTION,
ORDERNAME,
CASE CODE
WHEN 'None' THEN 'No'
WHEN '' THEN 'No'
ELSE 'Yes'
FROM ORDERS
WHERE UTCDATE > SYSDATE - 10
AND CODE IS NOT NULL
AND CATEGORY IS NOT NULL
AND UTCDATE IS NOT NULL
ORDER BY 2,3;

T-SQL Group By; Contains or IfAny

SQL2005 and/or SQL2008
Is there any kind of built-in aggregate, within T-SQL, for Contains or IfAny or whatever? Something where any in the group equals a value?
Similar to Max(xyz)=value except not limited to max.
Select custID, case when Min(ProductGroup)= "A" then 'Have Ordered Group A' else 'Haven't Ordered Group A' end hasOrdered
from orders
inner join products on ordPoductId = productID
group by custID
This works for a single value comparison, if it is min/max, but instead I want something like:
Select custID, case when contains(ProductGroup, "G") then 'Have Ordered Group G' else 'Haven't Ordered Group G' end hasOrdered
from orders
inner join products on ordPoductId = productID
group by custID
I could use Min(ProductGroup)="A" if the value I'm concerned about is a min/max or change the from-clause to (case when 'G' then 0 else 1 end) to create a fake maximum. Currently I am only concerned with a single value, but I would like something more intuitive and flexible if possible.
Any ideas?
Your examples at the end are close to what I'd normally do. Something like:
CASE MAX(CASE WHEN ProductGroup = 'G' THEN 1 ELSE 0 END)
WHEN 1 THEN 'Have Ordered'
ELSE 'Haven''t ordered'
END
Where the inner CASE expression will obviously be evaluated against each row, whereas the outer CASE expression determines whether the inner expression ever succeeded.

sql query: subtract from results the corresponding USD, YEN value which has Type='r'

I need help with a query. Consider the following table:
I need to select first the sum of each Code from table. I am doing it with simple sum and group by statement. Then I have to subtract the results from each code sum where type='r'
1) Say for first part of query, we will get 2 rows from SUM (one with total USD and one with total YEN)
2) Now I need to subtract from these results the corresponding USD, YEN value which has Type='r'
I have to do it inside SQL and not a stored procedure.
Why not use a WHERE statement to say WHERE Type != 'r' so that those values never even get added to sum in the first place...
SELECT `Code`, SUM(`Amount`) AS `Total`
FROM `Table`
WHERE `Type` != 'r'
GROUP
BY `Code`;
Something like that.
select code, l.amount - r.amount
from
(select code, sum(amount) as amount from my_table group by code) l
left join (select code, sum(amount) as amount from my_table where type = 'r' group by code) r
on l.code = r.code
You can do this in a single, simple query:
select
code,
sum(case when type = 'r' then (-1 * amount) else amount end) as sum
from
yourtable
group by
code
Basically, you're changing the sign of the rows that have type = 'r', so when you sum all rows for a particular code you'll get the correct answer.
Does it have to be a single query?
I'd say SUM the total, then SUM the subcategory where Type='r', then subtract one from the other.
You could do this in one line of SQL, but I'm pretty sure it would be either joining the table with itself or using a subquery. Either way, it's doing the same amount of work as the above.
Try:
select code,
sum(amount) gross_total,
sum(case when type = 'r' then amount else 0 end) type_r_total,
sum(case when type != 'r' then amount else 0 end) net_total
from yourtable
group by code;
to see the overall totals, type R only totals and non-type R totals for each currency on one row per currency, in a single pass.