Oracle Case Statement and efficency - sql

I have a piece of my Oracle Query i need to optimize a little
select
case
when SUM(dnl.quantity) = line.quantity then 1
when SUM(dnl.quantity) < line.quantity then 0
when SUM(dnl.quantity) > line.quantity then 2
end
from mytable dnl
line.quantity comes out from other part o query, for this example is not needed i think. I would like to calculate only once SUM(dnl.quantity) instead ad every iteraciotn, somethink like
select
case SUM(dnl.quantity)
when line.quantity then 1
when < line.quantity then 0
when > line.quantity then 2
end
from mytable dnl
But obviously this give error at the second and 3rd WHEN

You are over-optimizing. The Oracle compiler can decide how many times it wants to evaluate sum(dnl.quantity). However, the data movement is usually much more expensive than the calculation of aggregations on a single column.
That said if you are really concerned about this, you can use sign():
(case sign(sum(dnl.quantity) - line_quantity)
when 0 then 1
when -1 then 0
when 1 then 2
end)
Or to be more inscrutible:
sign(sum(dnl.quantity) - line_quantity) + 1

Related

Combining CASE Statements in SQL

What I am trying to do is combine (2) CASE statements that each return a SUM and then take the SUM of those results. Here's what I have so far:
COUNT((
CASE (COUNT(table.CODE))
WHEN 0
THEN 0
ELSE SUM(
CASE table.CODE
WHEN '100'
THEN 1
ELSE 0
END)) (
CASE (COUNT(table.CODE))
WHEN 0
THEN 0
ELSE (SUM(
CASE table.CODE
WHEN '50'
THEN 1
ELSE 0
END))))
|| AS Total Code
I think I'm over-complicating this. The error I am receiving is Missing Keyword (at the beginning of the 2nd CASE statement). Any thoughts?
When you have an overload of parentheticals, you might find it helpful to go overboard with indention to spot problems:
COUNT
(
(
CASE (COUNT(table.CODE))
WHEN 0
THEN 0
ELSE SUM
(
CASE table.CODE
WHEN '100'
THEN 1
ELSE 0
END
)
<--Should be an END here?-->
) <--What is happening here-->
(
CASE (COUNT(table.CODE))
WHEN 0
THEN 0
ELSE
(
SUM
(
CASE table.CODE
WHEN '50'
THEN 1
ELSE 0
END
)
)
<--Should be an END here?-->
)
)
|| AS Total Code
You can see that there are two CASE statements butted up against each other with no operator to make any sense of it.
You might also spot that your first and third CASE are not closed with an END, which is what MySQL is balking about.
Lastly, that double-bar in MySQL is an "OR" operator. But you are not OR'ing anything... Not sure what the plan was here.
Ultimately, though, I think you are going to run into problems Counting a Sum in the same query. You may have to move those SUM(CASE...END) bits into a subquery and then doing the COUNT outside of that.
After looking over the answers to my questions, this is what I came up with:
CASE (COUNT(table.CODE))
WHEN 0
THEN 0
ELSE ((SUM(
CASE table.CODE
WHEN '100'
THEN 1
WHEN '50'
THEN 1
ELSE 0
END))
END) AS Value5
I'm cleaning up old code so I didn't completely understand what I was looking at because its all over the place. This is a lot cleaner and I'm positive that I am getting desired response now.

Multiple columns within a single CASE statement

I'm sure this has be covered many times so please pardon my repeat. I have a query that works but currently has 6 CASE statements within one select. Someone mentioned that it would be best optimized by putting all my WHEN conditions within a single CASE. However, I'm unable to achieve this
select right(RTRIM(region),5) as cell_id,
sum(CASE WHEN LEFT(cparty,3) in ('999','998','997') THEN chargeduration/60 else 0 END) AS OnNet_Minutes,
sum(CASE WHEN LEFT(cparty,3) in ('996','995') THEN chargeduration/60 else 0 END) AS OffNet_C_Minutes,
sum(CASE WHEN LEFT(cparty,3) in ('994','993','992') THEN chargeduration/60 else 0 END) AS OffNet_A_Minutes,
sum(CASE WHEN LEFT(cparty,3) in ('991','990') THEN chargeduration/60 else 0 END) AS OffNet_S_Minutes,
sum(CASE WHEN LEFT(cparty,2) = '00' THEN chargeduration/60 else 0 END) AS OffNet_T_Minutes,
sum(CASE WHEN len(cparty) < 6 and LEFT(cparty,1) <> 0 THEN chargeduration/60 else 0 END) AS SC_Minutes
from August.dbo.cdr20130818
where CHARGEDURATION > 0 and ISNULL(region,'''')<>'''' and LEN(region) > 5
group by right(RTRIM(region),5)
order by right(RTRIM(region),5) asc
In your case, you can't put them all into one CASE, since the results all go into different columns of the select.
BTW, you should remove your ISNULL(region, '''') <> '''' condition, as it's redundant when paired with the LEN(region) > 5 condition. (When region is null, then LEN(region) is also null, and NULL > 5 is false.)
I think you have it right, six different SUM()'s that each have meaning on their own.
If all of your criteria were in the same CASE statement you'd lose detail, you'd be returning the SUM() of your currently separate statements combined into one.
Combining redundant criteria in the WHERE clause can clean up a CASE statement, but you don't have anything completely redundant here.

SQL multiple SELECT too slow (7 min)

This source is good but too slow.
Function:
Selecting all rows if SC and %%5 and 2013.07.11 < date < 2013.07.18
and
some older lines represent lines
Method:
Finding X count rows.
one by one to see whether there is consistency 28 days
select efi_name, efi_id, count(*) as dupes, id, mlap_date
from address m
where
mlap_date > "2013.07.11"
and mlap_date < "2013.07.18"
and mlap_type = "SC"
and calendar_id not like "%%5"
and concat(efi_id,irsz,ucase(city), ucase(address)) in (
select concat(k.efi_id,k.irsz,ucase(k.city), ucase(k.address)) as dupe
from address k
where k.mlap_date > adddate(m.`mlap_date`,-28)
and k.mlap_date < m.mlap_date
and k.mlap_type = "SC"
and k.calendar_id not like "%%5"
and k.status = 'Befejezett'
group by concat(k.efi_id,k.irsz,ucase(k.city), ucase(k.address))
having (count(*) > 1)
)
group by concat(efi_id,irsz,ucase(city), ucase(address))
Thanks for helping!
NOT LIKE plus wildcard-prefixed terms are index-usage killers.
You could also try replacing the IN + inline table with an inner join: does the optimizer run the NOT LIKE query twice (see your explain plan)?
It looks like you might be using MySql, in which case you could build a hash column based on
efi_id
irsz
ucase(city)
ucase(address))
and compare that column directly. This is a way of implementing a hash join in MySql.
I don't think you need a subquery to do this. You should be able to do it just with the outer group by and conditional aggregations.
select efi_name, efi_id,
sum(case when mlap_date > "2013.07.11" and mlap_date < "2013.07.18" then 1 else 0 end) as dupes,
id, mlap_date
from address m
where mlap_type = 'SC' and calendar_id not like '%%5'
group by efi_id,irsz, ucase(city), ucase(address)
having sum(case when m.status = 'Befejezett' and
m.mlap_date <= '2013.07.11' and
k.mlap_date > adddate(date('2013.07.11'), -28)
then 1
else 0
end) > 1
This produces a slightly different result from your query. Instead of looking at the 28 days before each record, it looks at all records in the week period and then at the four weeks before that period. Despite this subtle difference, it is still identifying dupes in the four-week period before the one-week period.

SQL Server Update via Select Statement

I have the following sql statement and I want to update a field on the rows returned from the select statement. Is this possible with my select? The things I have tried are not giving me the desired results:
SELECT
Flows_Flows.FlowID,
Flows_Flows.Active,
Flows_Flows.BeatID,
Flows_Flows.FlowTitle,
Flows_Flows.FlowFileName,
Flows_Flows.FlowFilePath,
Flows_Users.UserName,
Flows_Users.DisplayName,
Flows_Users.ImageName,
Flows_Flows.Created,
SUM(CASE WHEN [Like] = 1 THEN 1 ELSE 0 END) AS Likes,
SUM(CASE WHEN [Dislike] = 1 THEN 1 ELSE 0 END) AS Dislikes
FROM Flows_Flows
INNER JOIN Flows_Users ON Flows_Users.UserID = Flows_Flows.UserID
LEFT JOIN Flows_Flows_Likes_Dislikes ON
Flows_Flows.FlowID=Flows_Flows_Likes_Dislikes.FlowID
WHERE Flows_Flows.Active = '1' AND Flows_Flows.Created < DATEADD(day, -60, GETDATE())
Group By Flows_Flows.FlowID, Flows_Flows.Active, Flows_Flows.BeatID,
Flows_Flows.FlowTitle, Flows_Flows.FlowFileName, Flows_Flows.FlowFilePath,
Flows_Users.UserName, Flows_Users.DisplayName, Flows_Users.ImageName,
Flows_Flows.Created
Having SUM(CASE WHEN [Like] = 1 THEN 1 ELSE 0 END) = '0' AND SUM(CASE WHEN [Dislike] = 1
THEN 1 ELSE 0 END) >= '0'
This select statement returns exactly what I need but I want to change the Active field from 1 to 0.
yes - the general structure might be like this: (note you don't declare your primary key)
UPDATE mytable
set myCol = 1
where myPrimaryKey in (
select myPrimaryKey from mytable where interesting bits happen here )
Because you haven't made your question more clear in what result you want to achieve, I'll provide an answer with my own assumptions.
Assumption
You have a select statement that gives you stuffs, and it works as desired. What you want it to do is to make it return results and update those selected rows on the fly - basically like saying "find X, tell me about X and make it Y".
Anwser
If my assumption is correct, unfortunately I don't think there is any way you can do that. A select does not alter the table, it can only fetch information. Similarly, an update does not provide more detail than the number of rows updated.
But don't give up yet, depending on the result you want to achieve, you have alternatives.
Alternatives
If you just want to update the rows that you have selected, you can
simply write an UPDATE statement to do that, and #Randy has provided
a good example of how it will be written.
If you want to reduce calls to server, meaning you want to make just
one call to the server and get result, as well as to update the
rows, you can write store procedures to do that.
Store procedures are like functions you wrote in programming languages. It essentially defines a set of sql operations and gives them a name. Each time you call that store procedure, the set of operations gets executed with supplied inputs, if any.
So if you want to learn more about store procedures you can take a look at:
http://www.mysqltutorial.org/introduction-to-sql-stored-procedures.aspx
If I understand correctly you are looking for a syntax to be able to select the value of Active to be 0 if it is 1. The syntax for something like that is
SELECT
Active= CASE WHEN Active=1 THEN 0 ELSE Active END
FROM
<Tables>
WHERE
<JOIN Conditions>

SQL COUNT specific unit IF another field <> 0

I have a query that has a select statement that contains the following:
,COUNT(u.[Unit])
,up.[Number_Of_Stops]
I need to only count the units where number of stops <> 0. This has more details in the query so I can't just say WHERE number_of_stops <> 0. It has to be within the select statement.
Thanks
Try:
SUM(CASE WHEN up.[Number_Of_Stops] != 0 THEN 1 ELSE 0 END) AS countWhereNumStopsNotZero
(Edit: original answer said "COUNT" not "SUM")