Reuse variables in sql query - mysql

I want to know is there a way to reuse variables defined as AS and not to rewrite our code every time or not?
Example
I have such line:
IFNULL((CASE WHEN (((c.gaji_pokok+c.tunjangan+q.jkk+q.jkm)*12)+('c.komisi' * 'c.thr') >= 6000000) then ((c.gaji_pokok+c.tunjangan+q.jkk+q.jkm)*12)+('c.komisi' * 'c.thr')*0.05 ELSE 6000000 end),0) as b_jabatan,
and the next time I need such command I just want to use b_jabatan instead of writing whole line
example
IFNULL((b_jabatan,0) as brutto_tahun,
is that possible?

You can't reuse an alias in the same scope - left apart the order by clause (and, in MySQL, the group by and having clauses).
You either need to repeat the expression, or use a subquery or CTE (which creates a new scode). So:
select t.*, coalesce(b_jabatan,0) as brutto_tahun, ...
from (
select
< your super long expression > as b_jabatan,
...
from ...
) t

Use the least() function:
coalesce(least( ((c.gaji_pokok+c.tunjangan+q.jkk+q.jkm)*12)+(c.komisi * c.thr)*0.05, 6000000
), 0
) as b_jabatan,

Related

Use one conditional function with more than one execution path if condition is met

I have the following case statement,
case WHEN
DATE(DATE_SUB(DATE_ADD(startDate, INTERVAL 6 MONTH),INTERVAL 1 WEEK)) = DATE(NOW()) THEN
CONCAT('You should by now have held a probationary review meeting.','You must get approval from the owners.')
END AS 'emailSentence'
The emailSentence will hold the concatenation of the two sentences. This emailSentence is referenced externally by another program.
I would like to use a different alias such as emailSentence2 and refer to the second part of concat i.e. 'You must get approval from the owners.' all within the same case statement. That would mean getting rid of concat which is fine. Is there a way of doing this ? If there is a way of doing this without using a case statement i would be happy to hear that too, but it needs to be conditional statment/function.
I do realise i can use a different case statement with the same condition but that would mean multiple case statments making the sql bloated.
SQL has a very strict syntax. With * as the only exception, listing a column in a SELECT will always look like
select ... , expression [[as] alias], ...
case will be part of expression, so it cannot span multiple columns, and cannot add/return multiple columns.
Anecdotal, SQL doesn't care too much about the code being short. For example, doing something like select <expression> as value1, <expression> as value2, value1+value2 as value3 or select <expression> as value ... where value=0 is not allowed, you would have to repeat those expressions.
Nevertheless, if you really don't want to repeat that expression, you could use a left join instead and have the comparison (once) as the on-condition:
select ..., e.emailSentence, e.emailSentence2 -- or just e.*
from ...
left join (
select
'You should by now have held a probationary review meeting.' as emailSentence,
'You must get approval from the owners.' AS emailSentence2
) e on DATE(DATE_SUB(DATE_ADD(startDate, INTERVAL 6 MONTH),INTERVAL 1 WEEK)) = DATE(NOW())
To clarify: if the on-condition is not true, the values will be null, just as in the case-version.
This solution will have it's own limitations, but might be an option for you.

Define and use a variable with a subquery?

I know normally "the order of evaluation for expressions involving user variables is undefined" so we can't safely define and use a variable in the same select statement. But what if there's a subquery? As an example, I have something like this:
select col1,
(select min(date_)from t where i.col1=col1) as first_date,
datediff(date_, (select min(date_)from t where i.col1=col1)
) as days_since_first_date,
count(*) cnt
from t i
where anothercol in ('long','list','of','values')
group by col1,days_since_first_date;
Is there a way to use (select #foo:=min(date_)from t where i.col1=col1) safely instead of repeating the subquery? If so, could I do it in the datediff function or the first time the subquery appears (or either one)?
Of course, I could do
select col1,
(select min(date_)from t where i.col1=col1) as first_date,
date_,
count(*) cnt
from t i
where anothercol in ('long','list','of','values')
group by col1,date_;
and then do some simple postprocessing to get the datediff. Or I can write two separate queries. But those don't answer my question, which is whether one can safely define and use the same variable in a query and a subquery.
First, your query doesn't really make sense, because date_ has no aggregation functions. You are going to get an arbitrary value.
That said, you could repeat the subquery, but I don't see why that would be necessary. Just use a subquery:
select t.col1, t.first_date,
datediff(date_, first_date),
count(*)
from (select t.*, (select min(date_) from t where i.col1 = t.col1) as first_date
from t
where anothercol in ('long','list', 'of', 'values')
) t
group by col1, days_since_first_date;
As I mentioned, though, the value of the third column is problematic.
Note: this does occur additional overhead for materializing the subquery. However, there is a group by anyway, so the data is being read and written multiple times.

MySQL Return query result if condition is met, without repeating query

Let's say I have something as basic as
SELECT IF( (1 + 2) >=0, (1 + 2), 0)
FROM DUAL
which will obviously return 3.
Is there a way to NOT repeat the query I want to return (here 1 + 2)?
To put in context, I want to return the number of tasks remaining to do for the current month (at least 4 must be done), but if more than 4 are already done, there is no need to do more so I want to return 0.
What I have done is
IF((4 - IFNULL((LONG QUERY RETURNING THE NUMBER OF TASKS DONE THIS MONTH FOR A PARTICULAR USER ),0)) >= 0,
(4 - IFNULL((LONG QUERY RETURNING THE NUMBER OF TASKS DONE THIS MONTH FOR A PARTICULAR USER ),
0)
But is there a way to not repeat the query since it is long and I don't want the server to execute the same query twice?
Depending your request, you might want to use user-defined variables:
SELECT 1+2 INTO #tmp;
SELECT IF( #tmp >=0, #tmp, 0)
Or if you like one liners
SELECT IF( (#tmp := (1+2)) >=0, #tmp, 0)
-- ^ ^
-- don't forget those parenthesis !
EDIT: This works for "table select" statements too:
CREATE TABLE tbl AS SELECT 1 as a UNION SELECT 2;
SELECT SUM(a) FROM tbl INTO #tmp;
SELECT IF ( #tmp > 0, #tmp, 0);
If you look at http://sqlfiddle.com/#!2/37c33/1 for the execution plan of the above queries, you will see the second SELECT don't use the table.
BTW, please note that with your actual example, this could have been written:
SELECT MAX( (1+2), 0 )
-- ^^^^^ here your expression
... but I think this is not a property of your real query?
You can do this:
SELECT IF( (#a:=(1 + 2)) >=0, #a, 0);
One statement solution
A way to solve this problem in MySQL is to save the output of the heavy query and reuse multiple times. Take a look here:
SELECT CASE
WHEN #Value - 4 > 0
THEN #Value ELSE 0
END
FROM (SELECT #Value := (SELECT 1)) Query
where «SELECT 1» should be replaced by your query.
OT
Another way to perform your query would require the support of CTE (Common Table Expression).
For what I know this feature is missing in MySQL (How to transform a MSSQL CTE query to MySQL?)
Just to give you a taste of its expressiveness in MSSQL, where CTE is available, you could write something like that:
with Query (value)
as
(
select 1
)
select
case
when (select value from Query) - 4 > 0
then (select value from Query)
else
0
end
Indeed CTE are more powerful especially when dealing with recursive relations.

Using AS value in later on in query

Consider the following example query:
SELECT foo.bar,
DATEDIFF(
# Some more advanced logic, such as IF(,,), which shouldn't be copy pasted
) as bazValue
FROM foo
WHERE bazValue >= CURDATE() # <-- This doesn't work
How can I make the bazValue available later on in the query? I'd prefer this, since I believe that it's enough to maintain the code in one place if possible.
There are a couple of ways around this problem that you can use in MySQL:
By using an inline view (this should work in most other versions of SQL, too):
select * from
(SELECT foo.bar,
DATEDIFF(
# Some more advanced logic, such as IF(,,), which shouldn't be copy pasted
) as bazValue
FROM foo) buz
WHERE bazValue >= CURDATE()
By using a HAVING clause (using column aliases in HAVING clauses is specific to MySQL):
SELECT foo.bar,
DATEDIFF(
# Some more advanced logic, such as IF(,,), which shouldn't be copy pasted
) as bazValue
FROM foo
HAVING bazValue >= CURDATE()
As documented under Problems with Column Aliases:
Standard SQL disallows references to column aliases in a WHERE clause. This restriction is imposed because when the WHERE clause is evaluated, the column value may not yet have been determined. For example, the following query is illegal:
SELECT id, COUNT(*) AS cnt FROM tbl_name
WHERE cnt > 0 GROUP BY id;
The WHERE clause determines which rows should be included in the GROUP BY clause, but it refers to the alias of a column value that is not known until after the rows have been selected, and grouped by the GROUP BY.
You can however reuse the aliased expression, and if it uses deterministic functions the query optimiser will ensure that cached results are reused:
SELECT foo.bar,
DATEDIFF(
-- your arguments
) as bazValue
FROM foo
WHERE DATEDIFF(
-- your arguments
) >= CURDATE()
Alternatively, you can move the filter into a HAVING clause (where aliased columns will already have been calculated and are therefore available) - but performance will suffer as indexes cannot be used and the filter will not be applied until after results have been compiled.
As MySQL doesn't support CTE, consider using inline view:
SELECT foo.bar,
FROM foo,
(SELECT DATEDIFF(
# Some more advanced logic, such as IF(,,), which shouldn't be copy pasted
) as bazValue
) AS iv
WHERE iv.bazValue >= CURDATE()

mysql query, reuse columnnames in query

I have a mysql query, something like this:
SELECT users*100 as totalusers, totalusers*90 AS totalsalerys FROM table
As you can see I want to reuse the totalusers when calculating totalsalerys so I son't have to write the same code again. That dosen't seem to work in mysql, is there another easy way to do this?
My query is just an example, change the *100 and *90 to some very long formula and you might see what i mean..
SELECT (#r := users * 100) as totalusers,
#r * 90 AS totalsalerys
FROM table
You can also use a subquery as #Tom Ritter advices, but it's not friendly to ORDER BY ... LIMIT ... clause.
In MySQL, all results of the inner subquery will be fetched before applying any filters in the outer subquery.
I believe you would have to copy/paste the formula or use a subquery. The below would work in T-SQL, but I imagine it'd work in MySQL as well since it's pretty simple.
SELECT
x.totalusers, x.totalusers*90 AS totalsalerys
FROM (
SELECT users*100 as totalusers FROM table
) x