Why does this not return 0 - sql

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';

Related

Using A Count And A Case Statement In One Query

I'm pretty much out of ideas on how to get this to work.I haven't really used SQL in several years so there's a lot I don't remember.
So here is what I would like to happen:
I return the rows where the Code field from table has the value 1208 AND estnumber = 1187216
Run a count on the selection, if 0 run a subquery
If >0 run a different subquery
I didn't get to the subquery part yet because I can't get this to work correctly at all. Right now I just want it to return text.
Here is the latest attempt, I'm actually using db2 but maybe we can ignore that for now and i'll work that part out later because it says the syntax isnt correct, but other validators disagree (if you dont know anything about db2 just use standard sql when giving advice)
SELECT
count(*) AS t
FROM
table
WHERE
(
ESTNUMBER = 1187216
AND CODE = 1208
)
AND CASE WHEN t = 0 THEN 'it is zero' ELSE 'it is not zero' END;
Are you trying to do something like this?
WITH c AS (
SELECT count(*) AS cnt
FROM table
WHERE ESTNUMBER = 1187216 AND CODE = 1208
)
SELECT s1.*
FROM subquery1 s1
WHERE (SELECT cnt FROM c) = 0
UNION ALL
SELECT s2.*
FROM subquery2 s2
WHERE (SELECT cnt FROM c) > 0;
This assumes that the columns returned by the subqueries are compatible (same number, same types).
There are better ways to write this query (notably using EXISTS and NOT EXISTS), but this conforms directly to how you asked the question.
The string value should come up in the select clause and not in the where filter.
SELECT
count(*) AS t,
(CASE WHEN count(*) = 0 THEN 'it is zero' ELSE 'it is not zero' END) display_str
FROM
table
WHERE
(
ESTNUMBER = 1187216
AND CODE = 1208
)
You're thinking like an imperative programmer, not a declarative one. That is, SQL doesn't have sequential execution: it's all or nothing.
So, here's the start, the bit that works:
SELECT count(*) AS t
FROM table
WHERE ESTNUMBER = 1187216 AND CODE = 1208
Now, to check for the value of count(*), you by now know that WHERE isn't going to work. That's because COUNT is an aggregate function. To look at the result of such of function, you use HAVING.
For your CASE to work, you can move it up into the area that can get count(*) results:
SELECT count(*) AS t
(CASE WHEN count(*) = 0 THEN 'it is zero' ELSE 'it is not zero' END) as msg
FROM table
WHERE ESTNUMBER = 1187216 AND CODE = 1208
Note that "t" is an alias you've given the result of count(*). In most SQL implementations, that alias can't be leveraged in the rest of the statement.
Now, for the either or kind of thing, it would be time to reconsider your approach and what you're really after. You'll probably ultimately have both result sets in your statement and choose how the results are served up.
Something like:
select a.id, a.ct, (case when a.ct=0 then b.amt else c.amt end) as amt
from (select id, count(*) as ct from table1) a
left join (select id, sum(amount) as amt from table2) b on a.id=b.id
left join (select id, sum(amount) as amt from table3) c on a.id=c.id
Hope this helps.

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

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.

How do I return my records grouped by NULL and NOT NULL?

I have a table that has a processed_timestamp column -- if a record has been processed then that field contains the datetime it was processed, otherwise it is null.
I want to write a query that returns two rows:
NULL xx -- count of records with null timestamps
NOT NULL yy -- count of records with non-null timestamps
Is that possible?
Update: The table is quite large, so efficiency is important. I could just run two queries to calculate each total separately, but I want to avoid hitting the table twice if I can avoid it.
In MySQL you could do something like
SELECT
IF(ISNULL(processed_timestamp), 'NULL', 'NOT NULL') as myfield,
COUNT(*)
FROM mytable
GROUP BY myfield
In T-SQL (MS SQL Server), this works:
SELECT
CASE WHEN Field IS NULL THEN 'NULL' ELSE 'NOT NULL' END FieldContent,
COUNT(*) FieldCount
FROM
TheTable
GROUP BY
CASE WHEN Field IS NULL THEN 'NULL' ELSE 'NOT NULL' END
Oracle:
group by nvl2(field, 'NOT NULL', 'NULL')
Try the following, it's vendor-neutral:
select
'null ' as type,
count(*) as quant
from tbl
where tmstmp is null
union all
select
'not null' as type,
count(*) as quant
from tbl
where tmstmp is not null
After having our local DB2 guru look at this, he concurs: none of the solutions presented to date (including this one) can avoid a full table scan (of the table if timestamp is not indexed, or of the indexotherwise). They all scan every record in the table exactly once.
All the CASE/IF/NVL2() solutions do a null-to-string conversion for each row, introducing unnecessary load on the DBMS. This solution does not have that problem.
Stewart,
Maybe consider this solution. It is (also!) vendor non-specific.
SELECT count([processed_timestamp]) AS notnullrows,
count(*) - count([processed_timestamp]) AS nullrows
FROM table
As for efficiency, this avoids 2x index seeks/table scans/whatever by including the results on one row. If you absolutely require 2 rows in the result, two passes over the set may be unavoidable because of unioning aggregates.
Hope this helps
If it's oracle then you can do:
select decode(field,NULL,'NULL','NOT NULL'), count(*)
from table
group by decode(field,NULL,'NULL','NOT NULL');
I'm sure that other DBs allow for similar trick.
Another MySQL method is to use the CASE operator, which can be generalised to more alternatives than IF():
SELECT CASE WHEN processed_timestamp IS NULL THEN 'NULL'
ELSE 'NOT NULL' END AS a,
COUNT(*) AS n
FROM logs
GROUP BY a
SQL Server (starting with 2012):
SELECT IIF(ISDATE(processed_timestamp) = 0, 'NULL', 'NON NULL'), COUNT(*)
FROM MyTable
GROUP BY ISDATE(processed_timestamp);
Another way in T-sql (sql-server)
select count(case when t.timestamps is null
then 1
else null end) NULLROWS,
count(case when t.timestamps is not null
then 1
else null end) NOTNULLROWS
from myTable t
If your database has an efficient COUNT(*) function for a table, you could COUNT whichever is the smaller number, and subtract.
In Oracle
SELECT COUNT(*), COUNT(TIME_STAMP_COLUMN)
FROM TABLE;
count(*) returns the count of all rows
count(column_name) returns the number of rows which are not NULL, so
SELECT COUNT(*) - COUNT(TIME_STAMP_COLUMN) NUL_COUNT,
COUNT(TIME_STAMP_COLUMN) NON_NUL_COUNT
FROM TABLE
ought to do the job.
If the column is indexed, you might end up with some sort of range scan and avoid actually reading the table.
I personally like Pax's solution, but if you absolutely require only one row returned (as I had recently), In MS SQL Server 2005/2008 you can "stack" the two queries using a CTE
with NullRows (countOf)
AS
(
SELECT count(*)
FORM table
WHERE [processed_timestamp] IS NOT NULL
)
SELECT count(*) AS nulls, countOf
FROM table, NullRows
WHERE [processed_timestamp] IS NULL
GROUP BY countOf
Hope this helps
[T-SQL]:
select [case], count(*) tally
from (
select
case when [processed_timestamp] is null then 'null'
else 'not null'
end [case]
from myTable
) a
And you can add into the case statement whatever other values you'd like to form a partition, e.g. today, yesterday, between noon and 2pm, after 6pm on a Thursday.
Select Sum(Case When processed_timestamp IS NULL
Then 1
Else 0
End) not_processed_count,
Sum(Case When processed_timestamp Is Not NULL
Then 1
Else 0
End) processed_count,
Count(1) total
From table
Edit: didn't read carefully, this one returns a single row.