Are duplicate math operations reran in case expressions? - sql

In the example below, does the sql execution do anything to "cache" the results of fuel + cost? Or does the math get ran potentially three times? This is not a real use case, I just drummed up this example to mimic a more realistic need.
select case when shipFuelSurcharge + shipCost > 0
then 'over'
when shipFuelSurcharge + shipCost < 0
then 'under'
when shipFuelSurcharge + shipCost = 0
then 'equals'
end as ExampleMathCase
from shippment

The question can't be answered without knowing what the actual query is. The database doesn't execute SQL queries in the way they're written. The query optimizer will simplify and optimize them, then create an execution plan based on the table schemas, indexes and data statistics. The expensive part is reading the data from disk, not calculating simple additions and comparison.
For example, these two queries have the same execution plan and cost the same as far as the query optimizer is concerned:
declare #shippment table(id int primary key,shipFuelSurcharge numeric(18,4), shipCost numeric(18,4))
select case when shipFuelSurcharge + shipCost > 0
then 'over'
when shipFuelSurcharge + shipCost < 0
then 'under'
when shipFuelSurcharge + shipCost = 0
then 'equals'
end as ExampleMathCase
from #shippment;
select case when valuesum > 0
then 'over'
when valuesum < 0
then 'under'
when valuesum = 0
then 'equals'
end as ExampleMathCase
from (select shipFuelSurcharge + shipCost as valuesum from #shippment) x
The scalar calculation is the same in both cases.
[Expr1003] = Scalar Operator(CASE WHEN ([shipFuelSurcharge]+[shipCost])>(0.0000) THEN 'over'
ELSE CASE WHEN ([shipFuelSurcharge]+[shipCost])<(0.0000) THEN 'under'
ELSE CASE WHEN ([shipFuelSurcharge]+[shipCost])=(0.0000) THEN 'equals'
ELSE NULL END END END)
Execution plans work on streams of rows and precalculating the sum would require another node in the flow. That doesn't mean the actual expression doesn't get simplified either by the engine or the CPU though.
The relative cost for this expression is 0, because the IO cost is far greater than a few float additions and comparison.
Things are different if the actual query uses the operation result for filtering, ordering or grouping. The server can't use any indexes to accelerate the query and has to calculate the expression results first before proceeding.
This query results in a more complex and expensive execution plan :
declare #shippment table(id int primary key,shipFuelSurcharge numeric(18,4), shipCost numeric(18,4))
select ExampleMathCase,count(*)
from (
select case when shipFuelSurcharge + shipCost > 0
then 'over'
when shipFuelSurcharge + shipCost < 0
then 'under'
when shipFuelSurcharge + shipCost = 0
then 'equals'
end as ExampleMathCase
from #shippment) xx
group by ExampleMathCase;
select case when shipFuelSurcharge + shipCost > 0
then 'over'
when shipFuelSurcharge + shipCost < 0
then 'under'
when shipFuelSurcharge + shipCost = 0
then 'equals'
end as ExampleMathCase
from #shippment;
The more complex query is considered almost 5 times more expensive:

Related

SQL using SUM in CASE in SUM

I had this query select
sum(CASE WHEN kpi.average >= temp.average THEN 1 ELSE 0 END) AS recordOrder,
which worked fine, but I had to change it to this
sum(CASE WHEN sum(kpi.averageUse) / sum(kpi.averageTotal) >= temp.average THEN 1 ELSE 0 END) AS recordOrder,
These queries have to get number of rows, where some value (average) is greater than average from TEMP table. But in the second query I have more accurate data (weighted average).
but I am getting error
1111 invalid use of group function
Any ideas how to write SUM in CASE in SUM?
Thanks!
This code is just non-sensical because you have nested sum functions:
sum(CASE WHEN sum(kpi.averageUse) / sum(kpi.averageTotal) >= temp.average THEN 1 ELSE 0 END) AS recordOrder,
Without seeing your larger query, it is not possible to know what you really intend. But I would speculate that you don't want the internal sum()s:
sum(CASE WHEN (skpi.averageUse / kpi.averageTotal) >= temp.average THEN 1 ELSE 0 END) AS recordOrder,

SQL Server CASE Statement Evaluate Expression Once

I may be missing something obvious! Thanks in advance for any help.
I am trying to use a CASE statement in an inline SQL Statement. I only want to evaluate the expression once, so I am looking to put the expression in the CASE section, and then evaluate the result in each WHEN. Here is the example:
SELECT
MyTable.ColumnA,
CASE DateDiff(d, MyTable.MyDate, getDate())
WHEN <= 0 THEN 'bad'
WHEN BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END as MyCalculatedColumn,
MyTable.SomeOtherColumn
I know I can do this:
CASE
WHEN DateDiff(d, MyTable.MyDate, getDate()) <= 0 THEN 'bad'
WHEN DateDiff(d, MyTable.MyDate, getDate()) BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END
But in my first example, SQL does not seem to like this statement:
WHEN <= 0 THEN 'bad'
Note that the statement is inline with other SQL, so I can't do something like:
DECLARE #DaysDiff bigint
SET #DaysDiff = DateDiff(d, MyTable.MyDate, getDate())
CASE #DaysDiff
WHEN <= 0 THEN 'bad'
WHEN BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END
My actual DateDiff expression is much more complex and I only want to maintain its logic, and have it evaluated, only once.
Thanks again...
You can use apply for this purpose:
SELECT MyTable.ColumnA,
(CASE WHEN day_diff <= 0 THEN 'bad'
WHEN BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END) as MyCalculatedColumn,
MyTable.SomeOtherColumn
FROM MyTable CROSS APPLY
(VALUES (DateDiff(day, MyTable.MyDate, getDate()))) v(day_diff)
APPLY is a very handy way to add calculated values into a statement. Because they are defined in the FROM clause, they can be used in SELECT, WHERE, and GROUP BY clauses where column aliases would not be recognized.
I see your problem. CASE expression WHEN value can only do an equality check
You could try using a CTE (Common Table Expression), do everything except the case statement in the CTE and then put the CASE in the final SELECT at the end. I'm not sure whether it will prevent the expression being evaluated twice - thats kindof the optimisers problem, not yours (thats how I like to think about it)
WITH cteMyComplexThing AS(
SELECT MyTable.ColumnA,
DateDiff(d, MyTable.MyDate, getDate()) as ComplexThing,
MyTable.SomeOtherColumn
FROM MyTable
)
SELECT
ColumnA,
CASE
WHEN ComplexThing <= 0 THEN 'bad'
WHEN ComplexThing BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END as MyCalculatedColumn,
SomeOtherColumn
FROM cteMyComplexThing
The WHEN clause in a CASE statement needs both sides of the condition. <=0 cannot stand by itself.
CASE #DaysDiff
WHEN ? <= 0 THEN 'bad'
WHEN BETWEEN 1 AND 15 THEN 'reasonable'
ELSE 'good'
END

SQL Server - divide by zero error in WHERE clause (even with condition divisor > 0)

I have encountered a seemingly strange error when testing an SQL query with WHERE clause involving division. Task is to find errors in invoice where any of the following is true:
Either Amount or Tax is zero (not both);
Both Amount and Tax are not zero, but they don't meet certain arithmetic condition.
So the query I came up with is as follows:
SELECT InvoiceNumber, InvoiceDate, Amount, Tax
FROM Invoices
WHERE (Amount <> 0 AND Tax = 0)
OR (Amount = 0 AND Tax <> 0)
OR (Amount <> 0 AND Tax <> 0 AND ABS(Tax / Amount - 0.18) > 0.005)
To make things more strange, this query works okay:
SELECT InvoiceNumber, InvoiceDate, Amount, Tax
FROM Invoices
WHERE Amount <> 0 AND Tax <> 0 AND ABS(Tax / Amount - 0.18) > 0.005
I guess that Boolean evaluation in SQL Server conditional clauses does not work as in C# (where I could expect lazy evaluation preventing division by zero error).
How do I make this query correct?
NOTE: There are tens of millions of records, so nested SELECT statements may affect performance.
Do not depend on order of evaluation of clauses or conditions in a query. Period. The optimizer reserves the right to rearrange everything for performance. Okay, not everything. CASE expressions have a guaranteed order of evaluation.
The solution is quite simple. Use NULLIF():
WHERE (Amount <> 0 AND Tax = 0) OR
(Amount = 0 AND Tax <> 0) OR
(Amount <> 0 AND Tax <> 0 AND ABS(Tax / NULLIF(Amount, 0) - 0.18) > 0.005)
WHERE Amount <> 0 AND Tax <> 0 AND ABS(Tax / NULLIF(Amount, 0) - 0.18) > 0.005
did you try using parenthesis
SELECT InvoiceNumber, InvoiceDate, Amount, Tax
FROM Invoices
WHERE (Amount <> 0 AND Tax = 0)
OR (Amount = 0 AND Tax <> 0)
OR (Amount <> 0 AND Tax <> 0 AND ABS(Tax / Amount - 0.18) > 0.005)
When dealing with multiple conditions (Or/And) try to separate then by using "()".
The logical order of operators is
Arithmetic
Concatenation
Comparison conditions
IS [NOT] NULL, LIKE, [NOT] IN
[NOT] BETWEEN
Not equal to
NOT
AND
OR
You can use parentheses handle this rules.

Work out percentage count using SQL CASE statement

I have the following code which tells me which line items are in and out of SLA.
How can I turn that into a %, so for example when I add them together it will show 98% SLA Met.
,CASE
WHEN m.COMPLETED_DT is NULL THEN ''
WHEN m.COMPLETED_DT <= m.SLA_ADJUSTED_DT THEN 'SLA Met'
WHEN m.SLA_ADJUSTED_DT IS NULL THEN 'SLA Met'
ELSE 'SLA Missed' END AS "SLA Desc"
If I had the result already, I think it would look something like...
SELECT (count(*) * 100 / (select count(*) FROM testtable)) AS YesSLA
FROM testtable where SLA='Yes';
I am not sure how to integrate that with my current statement, I don't believe I can reference the AS SLA Desc in a new statement.
Does this do what you want?
select 100 * avg(case when m.completed_dt <= m.SLA_ADJUSTED_DT or m.SLA_ADJUSTED_DT is null
then 1.0 else 0
end)
from testtable
where SLA = 'Yes';
The code below calculates the % met SLA out of 100 by counting only values that met SLA and then dividing by the total opportunities.
DECLARE #Data TABLE (COMPLETED_DT DATETIME, SLA_ADJUSTED_DT DATETIME)
INSERT #Data VALUES ('5/5/2014', '5/6/2014'), ('5/6/2014', '5/6/2014'), ('5/7/2014', '5/6/2014')
SELECT
CONVERT(FLOAT, SUM(CASE WHEN COMPLETED_DT <= SLA_ADJUSTED_DT THEN 1 ELSE 0 END)) * 100 / COUNT(1) AS [% Met SLA]
FROM #Data
Output
% Met SLA
----------------------
66.6666666666667

SQL Division returning odd value

So Basically I have two sum values, that I return. I then divide one of them by the other, my goal was to get spit out a basic value. However the Value that is returned is all over the place.
I assume I screwed up the "Cast" but I am not sure here.
It would be impossible for me to share the entirety of the SQL that generates this. I will focus on the end results here which start with an Outer Apply:
This is me calling the Outer apply values for display, and then my attempt at dividing them:
,VerifyBlock.Numa as [Numerator]
,VerifyBlock.Denominator
,(isNull(((VerifyBlock.Numa) / NullIF(VerifyBlock.Denominator,0)),0)) as [Division]
This is the outer apply that generates the above:
OUTER APPLY (SELECT CASE WHEN CAST(tmpdc2.[14]AS decimal(10,2)) <= 0 AND (CASE WHEN T4MathBlock.[Value] >0 THEN T4MathBlock.[Value]
ELSE '0.00' END)<= 0
THEN '0.00'
ELSE (CASE WHEN CAST(tmpdc2.[14]AS decimal(10,2)) < (CASE WHEN T4MathBlock.[Value] >0 THEN T4MathBlock.[Value]
ELSE '0.00' END) THEN CAST(tmpdc2.[14]AS decimal(10,2))
ELSE (CASE WHEN T4MathBlock.[Value] >0 THEN T4MathBlock.[Value]
ELSE '0.00' END) End)
END AS [Amount]
From ##TempDisclosure tmpdc2
WHERE tmpdc1.[Student Number] = Tmpdc2.[Student Number]
)[AF]
WHERE tmpdc1.[Student Number] = Tmpdc.[Student Number]
)[VerifyBlock]
Sorry if the formatting is wonky.
I know the Values generated by:
VerifyBlock.Numa and VerifyBlock.Denominator are solid. The values they return are
32747682.64 and 78740189.20 (Respectively)
The Division though returns: 947860.602435
When it should actually return: 0.4158954019886963
So where the heck did I go wrong here?