SQL OR clause priority - mysql

I have a table with user awards, which can be of various different types.
For example, here are the records for the qualification ID 94:
So as you can see, there are 2 users, one has records for the award type of "average", "min", "max" and "final", the other has the same but no "final" award.
What I want is to get only 1 row per user. If they have an award of type "final" I want that, otherwise I want the "average" one, I don't want "min" or "max" at all.
So as an example, here is the query with just a simple IN clause:
So based on that, what I want the result to be is for the user 34562 I want the row with the "final" award, and for the user 6256 i want the row with their "average" award, since they don't have a "final" record.
I'm sure this should be fairly simple, but i'm failing miserably this morning.
I think I should be able to select the final record, then do a UNION ALL, but I can't seem to work it out in my head. Can anyone point me in the right direction?
I should point out that whilst this is MySQL for me, it needs to be compatible with other database platforms.
Thanks.

An easy way would be to check if an average entry exists in the where clause:
SELECT * FROM Table t
WHERE qualid = 94
and (type = 'average' AND
not exists(SELECT * FROM Table t2
WHERE t.qualid=t2.qualid AND t.userid=t2.userid AND type = 'final')
OR type = 'final')

You can accomplish this using MySQL user defined variables which will be more scalable.
SELECT
t.*
FROM
(
SELECT
*,
IF(#sameUser = userid, #rn := #rn + 1,
IF(#sameUser := userid, #rn := 1,#rn := 1)
) AS row_number
FROM moodle.mdl_bcgt_user_qual_awards
CROSS JOIN (SELECT #sameUser := -1, #rn := 1) AS var
WHERE qualid = 94
AND type IN ('final','average')
ORDER BY userid,
CASE WHEN type = 'final' THEN 0 ELSE 1 END
) AS t
WHERE t.row_number <= 1
ORDER BY t.userid
EDIT:
using NOT EXISTS & UNION ALL
SELECT
*
FROM your_table
WHERE qualid = 94
AND type = 'final'
UNION ALL
SELECT
*
FROM your_table A
WHERE qualid = 94
AND type = 'average'
AND NOT EXISTS (
SELECT 1 FROM your_table B WHERE A.qualid = B.qualid AND B.userid = A.userid AND B.type = 'final'
)

You can try this for sql Server
Select * from (
SELECT *,ROW_NUMBER() over(PARTITION BY type ORDER BY type) rowNo
FROM mdl_bcgt_user_qual_awards
WHERE qualid = 94
AND type in ('final','average')
Order By type
) as t
Where rowNo=1
Hear we have done Order by on type as character meet our requirement if any other text then just add case and sort on the basis of that field.

Here is a pure MySQL solution, which should also be generally ANSI compliant across most RDBMS:
SELECT t1.*
FROM yourTable t1
INNER JOIN
(
SELECT qualid,
CASE WHEN MAX(CASE WHEN type = 'final' THEN 100
WHEN type = 'average' THEN 10
ELSE 1
END) = 100 THEN 'final'
WHEN MAX(CASE WHEN type = 'final' THEN 100
WHEN type = 'average' THEN 10
ELSE 1
END) = 10 THEN 'average'
ELSE NULL
END AS type
FROM yourTable
GROUP BY qualid
) t2
ON t1.qualid = t2.qualid AND
t1.type = t2.type
This query employs a trick, which is to aggregate a sum for each qualid group based on whether final, average, or neither be present. I assign a value of 100 for final, 10 for average, and 1 for neither. This allows us to assign a type to each group.

Related

Selecting multiple rows, where a difference in value is greater than x%

I'm facing the following problem...
Given this data:
table : votes
=========
value
=========
10
25
38
90
92
93
98
100
120
I would like to return the value only, if the difference between next and previously accepted value is bigger than 10% of the first one:
if abs(int(a)-int(b))*100/int(a) < 10:
return True
So the end list should be (I have added % difference in square brackets):
==========
result
==========
10 ()
25 (150%)
38 (52%)
90 (136%)
100 (11%)
120 (20%)
The query should also sort those values first.
I'm able to do it with code (as shown above), but haven't got any chance in coming even close to a direct query.
MySQL v.8.0.19
You don't mention what version of MySQL you are using, so I'll assume it's a mordern one (8.x). You can use LAG(). For example:
select
concat('', value,
case when prev_value is null then ''
else concat('', 100 * (value - prev_value) / prev_value, '%')
end
) as result
from (
select
value,
lag(value) over (order by value) as prev_value
from t
) x
where prev_value is null or value > prev_value * 1.1
order by value
In MySQL 8.0, you can do this with lag(). Assuming that you want to sort rows by value, that would be:
select value
from (
select
value,
lag(value, 1, 0) over(order by value) lag_value
from mytable t
) t
where value > lag_value * 1.10
If you want to use an different ordering column, then you can change the order by clause to use the relevant column.
In earlier versions, one option is a correlated subquery:
select value
from mytable t
where value > 1.10 * coalesce(
(
select t1.value
from mytable t1
where t1.value < t.value
order by t1.value desc
limit 1
),
0
)
To use another ordering column here, you need to change the where clause and the order by clause of the subquery.
On the other hand, if you want to select the next row according to the ratio against the previously selected row, then that's a different question. You need some kind of iterative process: in SQL, one approach is a recursive query:
with
data as (
select value, row_number() over(order by value) rn
from mytable t
) d,
cte as (
select 1 is_valid, value, rn from data where rn = 1
union all
select
(d.value > 1.1 * c.value),
case when d.value > 1.1 * c.value then d.value else c.value end,
d.rn
from cte c
inner join data d on d.rn = c.rn + 1
)
select value from cte where is_valid order by value
The query enumerates the values, then walks the dataset sequentially while keeping track of the last selected value, and setting flags on records that should appear in the final resultset.

How to select last and last but one records

I have a table with 3 columns id, type, value like in image below.
What I'm trying to do is to make a query to get the data in this format:
type previous current
month-1 666 999
month-2 200 15
month-3 0 12
I made this query but it gets just the last value
select *
from statistics
where id in (select max(id) from statistics group by type)
order
by type
EDIT: Live example http://sqlfiddle.com/#!9/af81da/1
Thanks!
I would write this as:
select s.*,
(select s2.value
from statistics s2
where s2.type = s.type
order by id desc
limit 1, 1
) value_prev
from statistics s
where id in (select max(id) from statistics s group by type) order by type;
This should be relatively efficient with an index on statistics(type, id).
select
type,
ifnull(max(case when seq = 2 then value end),0 ) previous,
max( case when seq = 1 then value end ) current
from
(
select *, (select count(*)
from statistics s
where s.type = statistics.type
and s.id >= statistics.id) seq
from statistics ) t
where seq <= 2
group by type

How to eliminate only continuous duplicates but not all duplicates in a select query (MySQL)?

I have a table like this:
01-Jul-17 100
02-Jul-17 100
03-Jul-17 300
04-Jul-17 300
05-Jul-17 500
06-Jul-17 500
07-Jul-17 300
08-Jul-17 400
09-Jul-17 100
10-Jul-17 100
What I want to output is (in this order) by eliminating the continuous duplicates but not all duplicates:
100
300
500
300
400
100
I cannot select Distinct, as it will eliminate the second instances of 300, 100. Is there a way to achieve this result in MySQL?
Thanks!
You want to get the previous value. If the dates really have no gaps or duplicates, just do:
select t.*
from t left join
t tprev
on t.col1 = date_add(tprev.col1, interval 1 day)
where tprev.col2 is null or tprev.col2 <> t.col2;
EDIT:
If the dates don't meet these conditions, then you can use variables:
select t.*
from (select t.*,
(#rn := if(#v = col2, #rn + 1,
if(#v := col2, 1, 1)
)
) as rn
from t cross join
(select #v := 0, #rn := 0) params
order by t.col1
) t
where rn = 1;
Note that MySQL does not guarantee the order of evaluation of expressions in the SELECT. So variables should not be assigned in one expression and then used in another -- they should be assigned in a single expression.
One way to handle this problem is by using session variables to track the changes of the values as ordered by your date column. In the query below, we keep track of the value, ordered by date, and assign a row number to each group of identical value. Then, only the first value in each group is retained. Note that this approach is robust to any number of duplicates. It is also robust with respect to there being gaps in your dates, so long as each record can be ordered by date.
SET #rn = 1;
SET #val = NULL;
SELECT t.val
FROM
(
SELECT
#rn:=CASE WHEN #val = val THEN #rn+1 ELSE 1 END rn,
#val:=val AS val,
dt
FROM yourTable
ORDER BY dt
) t
WHERE t.rn = 1
ORDER BY t.dt;
Output:
Demo here:
Rextester
You can make use of lag and lead functions.
select y from (select y , lag(y,1,0) over (order by x) as prev_y from t1) where y <> prev_y;

Nested conditional statements on Mysql

I'm developing a query on phpmyadmin mysql in which I intend to display the running balance of a column say 'CurrentBalance'
The running balance of this column depends when the Activity is a deposit (+), a withdraw (-) , bet (-), payout (+)
What I come up is this
SELECT CONCAT("Trans ID ",`TransactionID`) AS ID,
`DateTime`,`Type` AS Activity, `Amount`, 0 AS Payout,
CASE WHEN (SELECT Activity) = "deposit" THEN `Amount`+ `playerdb`.`CurrentBalance`
ELSE CASE WHEN (SELECT Activity) = "withdraw" OR (SELECT Activity) = "bet"
THEN CASE WHEN (SELECT Payout) >0 THEN (SELECT Payout) + `playerdb`.`CurrentBalance`
ELSE `Amount` - `playerdb`.`CurrentBalance` END END END AS CurrentBalance
FROM `transactiondb` LEFT JOIN `playerdb` ON
`transactiondb`.`PlayerID` = `playerdb`.`PlayerID`
WHERE `transactiondb`.`PlayerID`=10078 UNION ALL
SELECT CONCAT("Bet ID ",`BetID`),`DateTime`,"Bet", `BetAmount`,`Payout`, (SELECT CurrentBalance) FROM `betdb` WHERE `PlayerID`=10078 ORDER BY `DateTime`
The Idea
http://postimg.org/image/x3fsxq2qz/
Doing the (SELECT CurrentBalance) on the 2nd SELECT statement yields this error
1054 - Unknown column 'CurrentBalance' in 'field list'
I need to get the CurrentBalance of the previous record so I tried using the alias
Is that possible?
This is a "teach a man to fish..." answer, since your question is not yet totally clear to me.
In general you can access the previous row with variables.
Have a look at this example:
SELECT
t.*
, #running_total := IF(#foo != foo, 0, #running_total + bar)
, #foo := foo
FROM a_table t
, (SELECT #running_total := 0, #foo := NULL) variable_initialization_subquery
ORDER BY foo;
As the subquery alias suggests, here
, (SELECT #running_total := 0, #foo := NULL) variable_initialization_subquery
we initialize the variables.
We ORDER BY foo because there's no reliable order in data in a database when you don't specify it clearly.
Then the SELECT clause is considered with one column after another. Here the order of the columns is important, too.
First we do this:
, #running_total := IF(#foo != foo, 0, #running_total + bar)
This calculates a running total for every foo. When the foo changes, the running total is reset to 0. The IF() function works like IF(<boolean condition>, <then>, <else>).
Here the variable #foo still has the value of the previous row. The value of the current row is assigned in this line:
, #foo := foo
I hope you get the idea, feel free to ask if anything is unclear. Oh, and you don't need those selects in the case...when... parts.
Just guessing... giving the man a fish - might be the wrong fish
SELECT CONCAT("Trans ID ",TransactionID) ID
, DateTime
, Type Activity
, Amount
, 0 Payout
, CASE WHEN Activity = "deposit"
THEN Amount + playerdb.CurrentBalance
WHEN Activity IN("withdraw","bet")
THEN
CASE WHEN Payout >0
THEN Payout + playerdb.CurrentBalance
ELSE Amount - playerdb.CurrentBalance
END
END CurrentBalance
FROM transactiondb
LEFT
JOIN playerdb
ON transactiondb.PlayerID = playerdb.PlayerID
WHERE transactiondb.PlayerID = 10078
UNION ALL
SELECT CONCAT("Bet ID ",BetID)
, DateTime
, "Bet"
, BetAmount
, Payout
, CurrentBalance
FROM betdb
WHERE PlayerID = 10078
ORDER
BY DateTime;

how do I write an IF ELSE into this query to make it work?

table looks something like this: (yes those are & signs. ignore the dashes)
ID-VALUE-NUM
-1-YES----2-
-1-NO-----3-
-2-YES----1-
-2-NO-----1-
-3-&&&----1-
-3-&------2-
-3-&&-----2-
what I need to do:
for each ID, I need to get the value with the highest NUM, in the case of a tie and VALUE has &s then it would pick the shortest. if the value is YES/NO then it will pick YES.
result desired
ID-VALUE-NUM
-1-NO-----3-
-2-YES----1-
-3-&------2-
I think I have to put a IF statement in there somewhere but I'm not sure how.
Here is one way. The join finds the maximum num. Then the select uses logic to choose the right value based on your rules:
select t.id,
(case when count(*) = 1 then min(value)
when max(value like '%&%') > 0 then min(value)
when max(value = 'Yes') > 0 and max(value = 'No') > 0 then 'Yes'
else max(value)
end) as value,
t.num
from t join
(select id, max(num) as maxnum
from t
group by id
) tm
on t.id = tm.id and t.num = tm.maxnum
group by t.id, t.num