Use previous value in MySQL to compute the next field? - mysql

How to use previous values in MySQL to compute the next value? I couldn't explain what I mean more clearly in my poor English (anyone can understand what I asked by seeing the title of this post?), so let me explain it in a snippet:
select
year_id,
sum(case when event_cd >= 20 then 1 else 0 end) as h,
sum(case when ab_fl = "T" then 1 else 0 end) as ab,
h/ab as ba
from events
group by year_id
in the above snippet, when you run the query you will get the error, since there are no such columns in events table as h or ab. However, I want to use h and ab which are computed in the previous sum(case when ~) syntax. I remember there are some sort of ways to make it possible, but I don't remember how to do nor as I said, I couldn't find any relevant posts to meet what I'm asking due to my poor English, though since I'm sure this question is already posted here at SO, it's quite helpful even if you just link it with no detailed explanation.
Thanks.
[Update]
Thanks for the answers. I just wanted to use previous values in order to avoid subqueries and hence lots of redundant typing, and make the entire code more readable. I've used either methods (subqueries or write the entire syntax twice just to compute another value) as described by Mosty Mostacho, and if this is not feasible or pretty risky to use in MySQL, I can surely accept those two methods above. Sorry for the confusion.

There are 2 ways in MySQL (BTW, I've simplified the query a bit but it is still legal):
Option #1: Expand the variables
SELECT
year_id,
SUM(event_cd >= 20) h,
SUM(ab_fl = "T") ab,
SUM(event_cd >= 20) / SUM(ab_fl = "T") ba
FROM events
GROUP BY year_id
Option #2: Use a derived table
SELECT year_id, h, ab, h / ab ba FROM (
SELECT
year_id,
SUM(event_cd >= 20) h,
SUM(ab_fl = "T") ab
FROM events
GROUP BY year_id
) s
You might be tempted to think that the second will run faster but it is not the case. The first one is most likely to run faster because it doesn't need a derived table and can solve the issue in just one pass.

try
select * h/ab as ba from
(
select
year_id,
sum(case when event_cd >= 20 then 1 else 0 end) as h,
sum(case when ab_fl = "T" then 1 else 0 end) as ab,
from events
group by year_id)
source

Update
As #MostyMostacho explains in the comment below, this is not an advisable technique, due to the order of evaluation of user variable being undefined.
You can write a query using a MySQL variable using the syntax #varname := 'value' to store your sums in, and then reference that.
SELECT
year_id
, #h := SUM(CASE WHEN event_cd >= 20 THEN 1 ELSE 0 END) AS h
, #ab := SUM(CASE WHEN ab_fl = "T" THEN 1 ELSE 0 END) AS ab
, #h/#ab AS ba
FROM events
GROUP BY year_id

Related

subtract 2 values from the same table with UNION

I hope I can get some help here since i cannot think of anything else.
so i have 2 results that come from the same table which i want to see in one query... so i used UNION, all good until there, now i need the difference between these two results. Let's say i have the next result:
measure 30_days
old_purchases 342
new_purchases 54
i need a new row that gives me the difference between those two, something like that:
measure 30_days
old_purchases 342
new_purchases 54
difference 288
You can use conditional aggregation:
select measure, value
from t
union all
select 'difference',
sum(case when measure = 'old_purchases' then value
when measure = 'new_purchases' then - value
else 0
end)
from t;
If you actually want to add this to the table then use insert:
insert into t (measure, value)
select 'difference',
sum(case when measure = 'old_purchases' then value
when measure = 'new_purchases' then - value
else 0
end)
from t;

SQL query - find values that meet m conditions out of n

Is there any way to find values that meet any m conditions out of given n conditions? For instance, if there are 10 conditions, and I want to find values that meet any 2 of them.
Use CASE expressions in the WHERE clause, 1 for each condition like this:
WHERE 2 =
CASE WHEN <condition1> THEN 1 ELSE 0 END +
CASE WHEN <condition2> THEN 1 ELSE 0 END +
CASE WHEN <condition3> THEN 1 ELSE 0 END +
..........................................
You can change the = sign to > or < to meet your requirement.
There is. It's not gonna be pretty though.
Start with your conditions as SELECT expressions.
select T.*,
case
when T.SOME_NUMERIC_COLUMN > 0 then 1
else 0
end IS_POSITIVE,
(select sign(COUNT(*))
from SOME_OTHER_TABLE
where parent_id = T.ID) HAS_CHILDREN
...
from SOME_TABLE T
Design these expression in such a way that you get 1 when a condition is met and 0 when it's not.
Then sum up the score and add a WHERE clause.
SELECT *
FROM (
SELECT R.*,
IS_POSITIVE + HAS_CHILDREN + ... SCORE
FROM (...) R)
WHERE SCORE > 2
Of course you're gonna pay a hefty price in performance for this. You won't be able to use your conditions directly to limit the resultset so I'd expect the execution plans to be extremely disappointing. That said, it's not like what you have in mind is a standard task for RDBMS so it should be enough for a proof of concept.

IF with SUM produces incorrect results in MySQL

I am getting incorrect results when I try to insert a SUM into an IF clause. The results are correct when I use COUNT, either incorrect or NULL when I use SUM. I have been able to produce the correct results for each statement through another query (as a means of validating the formula). What is the syntax to get correct results for an SUM within an IF statement? Based on another StackOverflow question, I attempted to fix the formula, but it produced an error.
SELECT
IF (Artist LIKE '%Hillsong%' , 'Hillsong', NULL ) as Artist,
COUNT(IF(CCD BETWEEN 28 AND 730,1,NULL)) AS 1_CC, -- result 191, which is correct
IF(CCD BETWEEN 28 AND 730, SUM(CC28),NULL) AS 2_CC, -- result NULL, should be 610 xx
COUNT(IF(CCD > 28,1,NULL)) AS 3_CC, -- result 684, which is correct
COUNT(IF(CCD < 730,1,NULL)) AS 4_CC, -- result 502, which is correct
IF(CCD > 28,SUM(CC28),NULL) AS 5_CC, -- result 2253, should be 1882 xx
--- SUM(IF(CCD > 28,CC28,NULL) AS 6_CC, -- my attempt to fix, creates error
IF(CCD < 730,SUM(CC28),NULL) AS 7_CC -- result NULL, shoul be 981 xx
FROM praisecharts_reporting.large_sales_report
GROUP BY 1;
As a frame of reference, I am a music publisher, and I am trying to get results for all songs where the artist includes "Hillsong", where the Chord Chart (CC) has been available between 28 and 730 days (CCD BETWEEN 28 AND 730). The COUNT should tell me how many song titles qualify, and SUM should tell me the total unit sales for all songs that qualify.
I figured out the answer to my problem. The syntax for putting an IF clause inside a SUM is to use CASE WHEN. Below is the solution to my query above, and it produces correct results all around:
SELECT
IF (Artist LIKE '%Hillsong%' , 'Hillsong', NULL ) as Artist,
COUNT(IF(CCD BETWEEN 28 AND 730,1,NULL)) AS 1_CC,
SUM(CASE WHEN CCD BETWEEN 28 AND 730 THEN CC28 ELSE 0 END) AS 2_CC,
COUNT(IF(CCD > 28,1,NULL)) AS c3_CC,
COUNT(IF(CCD < 730,1,NULL)) AS c4_CC,
SUM(CASE WHEN CCD > 28 THEN CC28 ELSE 0 END) AS 5_CC,
SUM(CASE WHEN CCD < 730 THEN CC28 ELSE 0 END) AS 6_CC
FROM praisecharts_reporting.large_sales_report
GROUP BY 1;
You should be on the right track on your attempt to use SUM - IF
--- SUM(IF(CCD > 28,CC28,NULL) AS 6_CC, -- my attempt to fix, creates error
If you look closely, you are missing an ending parenthesis. We must pay attention to the error we received. Most likely you received a syntax error right after AS 6_CC,.
Just add a closing parenthesis after NULL:
SUM(IF(CCD > 28,CC28,NULL)) AS 6_CC,
Try to use SUM - IF on your other columns as well. Let me know if this works.
With SUM - IF you can have a true / false result, which is as simple as it can be (though you can have nested if's inside, but that would make it unreadable).
With SUM - CASE you have an option to have more results by providing more conditions, just like a SWITCH statement.
Without the underlying data it would be hard to verify the results. But your NULL result may be due to a underlying NULL in data or due to condition clause.
Try replacing SUM(CC28) with SUM(IFNULL(CC28,0))
A sum of Integer and NULL equals NULL. Hence you might be getting NULL in second set.

how to know the source table from a complex sql query

PFB the query in which I want know the actual table where I can find these (pin_ein,engineer_pin,transaction_date,EFFECTIVE_WEEK )....
select count(1),pin_ein,engineer_pin,transaction_date,EFFECTIVE_WEEK from
(SELECT vw1.pin_ein, vw1.engineer_pin,
vw1.transaction_date, vw1.effective_week,
vw1.stores_tools_cost,
(vw1.stores_total_cost - vw1.stores_tools_cost
) stores_total_cost_excl_tools,
vw1.item_count, vw1.stores_visit_count,
CASE
WHEN vw1.stores_total_cost <
5
THEN vw1.stores_visit_count
END stores_low_cost_visit_count,
vw1.actual_ouc ouc, er.eng_name engineer_name,
CASE
WHEN c.home_parked IS NOT NULL
THEN c.home_parked
ELSE 'N'
END home_parker,
CASE
WHEN c.home_parked IS NOT NULL
THEN c.commute_time
ELSE -99
END commute_time,
vw1.stores_com_cost ---v9.3---
FROM (SELECT pin_ein, engineer_pin, actual_ouc,
transaction_date, effective_week,
NVL
(SUM
(CASE
WHEN ( cow LIKE '%TOOL%'
OR cow LIKE
'%TOOLE%'
)
THEN transaction_value
END
),
0
) stores_tools_cost,
SUM
(transaction_value
) stores_total_cost,
SUM (transaction_quantity)
item_count,
COUNT
(DISTINCT sta_code
) stores_visit_count,
NVL
(SUM
(CASE
WHEN cow in (SELECT cow FROM orbit_odw.stores_cow_ref)
THEN transaction_value
END
),
0
) stores_com_cost ---v9.3---
FROM orbit_odw.stores_transaction_dtls
WHERE effective_week BETWEEN 201543
AND 201610
/***Ver 6.0---last 13 weeks data to be considered***/
AND transaction_date
BETWEEN to_date('19-10-2015','dd-mm-yyyy')
AND to_date('06-03-2016','dd-mm-yyyy')
/***Ver 6.0---last 13 weeks data to be considered***/
GROUP BY pin_ein,
engineer_pin,
actual_ouc,
transaction_date,
effective_week) vw1,
(SELECT *
FROM orbit_odw.dim_wms_rmdm
WHERE current_status = 1) er,
(SELECT engineer_ein, commute_time,
home_parked
FROM orbit_odw.eng_parking_at_home_dtls
WHERE rec_end_date > SYSDATE
AND home_parked = 'Y') c
WHERE TO_CHAR (vw1.pin_ein) = er.ein
AND vw1.pin_ein = c.engineer_ein(+)) group by pin_ein,engineer_pin,transaction_date,EFFECTIVE_WEEK having count(1)>1;
Please help..
thanks in advance
Basically if i understand your problem correctly. You need to understand from where your outer SELECT is fetching data. So there is a simple rule set used to get these data. Steps are as follows.
Check the column output i.e in your case it's
pin_ein,engineer_pin,transaction_date,EFFECTIVE_WEEK
Check the inline view or table from which your outer query is
fetching the data. In your case its the only inline view you have
used --> So bit easy to identify :P
Now to identify how your inline view VW1 is populating data. In your
case table orbit_odw.stores_transaction_dtls is used to populate the
required fields.
Hope this much information is required. Also for simple queries you can always go to ALL_TAB_COLUMNS system tables to identify a table's column easily.

MAX with extra criteria

I have the following part of a query I'm working on in MYSQL.
SELECT
MAX(CAST(MatchPlayerBatting.BatRuns AS SIGNED)) AS HighestScore
FROM
MatchPlayerBatting
It returns the correct result. However there is another column I need it to work off.
That is if the maximum value it finds also has a value of "not out" within "BatHowOut", it should show the result as for example 96* rather than just 96.
How could this be done?
To help make the data concrete, consider two cases:
BatRuns BatHowOut
96 not out
96 lbw
BatRuns BatHowOut
96 not out
102 lbw
For the first data, the answer should be '96*'; for the second, '102'.
You can achieve this using self-join like this:
SELECT t1.ID
, CONCAT(t1.BatRuns,
CASE WHEN t1.BatHowOut = 'Not Out' THEN '*' ELSE '' END
) AS HighScore
FROM MatchPlayerBatting t1
JOIN
(
SELECT MAX(BatRuns) AS HighestScore
FROM MatchPlayerBatting
) t2
ON t1.BatRuns = t2.HighestScore
See this sample SQLFiddle with highest "Not Out"
See this another sample SQLFiddle with highest "Out"
See this another sample SQLFiddle with two highest scores
How about ordering the scores in descending order and selecting only the first record?
select concat(BatRuns , case when BatHowOut = 'not out' then '*' else '' end)
from mytable
order by cast(BatRuns as signed) desc,
(case when BatHowOut = 'not out' then 1 else 2 end)
limit 1;
Sample here.
If you want to find highest score score for each player, here is a solution that may not be elegant, but quite effective.
select PlayerID,
case when runs != round(runs)
then concat(round(runs),'*')
else
round(runs)
end highest_score
from (select PlayerID,
max(cast(BatRuns as decimal) +
case when BatHowOut = 'not out' then 0.1 else 0 end
) runs
from MatchPlayerBatting
group by PlayerID) max_runs;
This takes advantage of the fact that, runs can never be fractions, only whole numbers. When there is a tie for highest score and one of them is unbeaten,
adding 0.1 to the unbeaten score will make it the highest. This can be later removed and concatenated with *.
Sample here.