How to Assign 0 if Calculated Value is <0 within CASE - mysql

I have a MySQL table named users containing simple counts for all users.
user_id
counter1
counter2
1
0
5
2
1
6
3
2
7
4
3
8
I would like to run a single query that updates the counter1 values to counter1-# or 0 (# can be any whole number), whichever is greater for possibly multiple users. The counter1 column is an unsigned int type.
I am executing the following query to update users 2 and 3:
UPDATE
users
SET
counter1 = CASE user_id
WHEN 2 THEN GREATEST(counter1 - 2, 0)
WHEN 3 THEN GREATEST(counter1 - 5, 0)
ELSE counter1 END
WHERE user_id IN(2, 3);
Running the above query returns an error:
BIGINT UNSIGNED value is out of range in '(`user`.`counter1` - 2)'
The end result I'm trying to aim for is for both user 2 and user 3 to have a minimum counter1 value of 0 after the query is executed since in both users' cases, subracting 2/5 from their counter1 values will be a negative number, which of course won't work.
Is there a way to do this with a single query?

The problem is that the expression counter1 - 2 produces a negative value when counter1 is less than 2. But since it's declared UNSIGNED, the result of expressions that use it are also unsigned, so negative values are not allowed.
Instead of subtracting from it, use an IF() expression to prevent calculating these invalid values.
CASE user_id
WHEN 2 THEN IF(counter1 > 2, counter1 - 2, 0)
WHEN 3 THEN IF(counter1 > 5, counter1 - 5, 0)
ELSE counter1
END

Easiest way to do this is:
GREATEST(0, CASE ... END)

Related

Return rows matching one condition and if there aren't any then another in MYSQL

I have the following table as an example:
numbers type
--------------
1 1
5 2
6 1
8 2
9 3
14 2
3 1
From this table I would like to select the closest number that is less or equal to 5 AND of type 1 and if there is no such row matching, then (and only then) I would like to return the first closest number larger than 5 of type 2
I can solve this by running two queries:
SELECT number FROM numbers WHERE number <= 5 AND type = 1 ORDER BY number LIMIT 1
and if above query returns 0 results, I simply run the second query:
SELECT number FROM numbers WHERE number > 5 AND type = 2 ORDER BY number LIMIT 1
But is it possible, to achieve the same result by only using one query?
I was thinking something like
SELECT number FROM numbers WHERE (number <= 5 AND type = 1) OR (number > 5 AND type = 2) ORDER BY number LIMIT 1
But that would only work, if mysql first checks the first conditional in the parentheses against all rows and if it finds a match, it returns it, and if not, then it checks all rows against the second parenthesed conditional. It will not work, if it checks each row against both parentheses and only then moves to the next row, which is how I suspect it works.
This query will do what you want. It selects all numbers that match your two query constraints, and orders the results first by type (so that if there is a result for type 1 it will appear first) and then by either -number or number dependent on type (so that numbers <= 5 sort in descending order but numbers > 5 sort in ascending order):
SELECT number
FROM numbers
WHERE ( number <= 5 AND type = 1 )
OR ( number > 5 AND type = 2 )
ORDER BY type, CASE WHEN type = 1 THEN -number ELSE number END
LIMIT 1
Output:
3
Demo on dbfiddle
Combine the two, and you always prefer type 1 over type 2, hence the ORDER BY and LIMIT. The ABS means whichever is first by type, is the closes to the number 5.
SELECT number, type
FROM numbers
WHERE (number <=5 AND type=1) OR
(number > 5 AND type=2)
ORDER BY type ASC, ABS(number-5) ASC
LIMIT 1

Count of past instances in an appended table

This is my desired output:
CampaignName CampaignDate UsersTargeted CountOfUsersBought
2x1 2018-11-24 1 (UserId 2) 1 (UserId 2)
3x2 2018-11-25 2 (Both) 1 (UserId 1)
'CountOfUsersBought' I want it to be Amongst All Targeted Users.
And the Table (Updated Daily) from where I get the data to fill the previous output has the following structure:
UserId EligibleForOffer(0,1) BoughtToday(0,1) Timestamp
1 0 0 2018-11-24
1 1 0 2018-11-25
1 1 1 2018-11-26
1 0 0 2018-11-27
2 1 0 2018-11-24
2 1 1 2018-11-25
2 1 0 2018-11-26
2 0 1 2018-11-27
I want to store on the variable 'CountOfUsersBought' the count of all the users that actually bought, not only today but all time. How would you go about doing this query?
Please note that users also buy without an offer, so I only want to count the past instances WHERE EligibleForOffer = 1 AND BoughtToday = 1 AND Timestamp <= 'CampaignDate'+ 1 day
I know for another table the users that are targeted for each campaign, I just want to keep for more than 'today' the count of users that took the offer that was given to them.
You can GROUP BY dates and use SUM the find how many users eligible for campaigns and use CASE to do your conditions. And bonus is MIN to find which specific user related with the condition is only one user match.
CREATE TABLE Campain
(
UserId INT
,EligibleForOffer BIT
,BoughtToday BIT
,Timestamp DATE
)
INSERT INTO Campain
VALUES
(1,0,0,'2018-11-24')
,(1,1,0,'2018-11-25')
,(1,1,1,'2018-11-26')
,(1,0,0,'2018-11-27')
,(2,1,0,'2018-11-24')
,(2,1,1,'2018-11-25')
,(2,1,0,'2018-11-26')
,(2,0,1,'2018-11-27')
SELECT Timestamp
,SUM(CAST(EligibleForOffer AS INT)) NumberOfUsersTargeted
,CASE WHEN SUM(CAST(EligibleForOffer AS INT))=1 THEN 'UserId-'+CAST(MIN(UserId) AS VARCHAR) WHEN SUM(CAST(EligibleForOffer AS INT))>1 THEN 'Multiple Users(Both)' ELSE 'No Target' END UsersTargetedDetail
,SUM(CAST(BoughtToday AS INT)) NumberOfBought
,CASE WHEN SUM(CAST(BoughtToday AS INT))=1 THEN 'UserId-'+CAST(MIN(UserId) AS VARCHAR) WHEN SUM(CAST(BoughtToday AS INT))>1 THEN 'Multiple Users(Both)' ELSE 'No Buying' END BoughtDetail
FROM Campain
GROUP BY Timestamp

MySQL : How to keep track of change in value using its trend?

MYSQL : How to keep track of change in value using its trend, if value decreases then trend would be -1, if increases then 1 and 0 for same
For example
id_indicator value Trend
1 0 0
1 1 1
1 5 1
1 4 -1
2 1 0
2 -8 -1
2 0 1
How i can get trend column?
Can i add temporary column in Select statement that will hold trend value?
use a myslq variable to keep track of previous rows
SET #prev = 0;
SELECT
id_indicator,
value,
-- IF(value > #prev, 1, IF(value < #prev , -1, 0)) AS trend,
SIGN(value-#prev) AS trend, -- nicer solution thx to #spencer7593
#prev:=value FROM `the_table`
the fourth column's only purpose is to assign the current value into #prev so you can use it in the next row iteration.
having both current and previous values, you can pretty much write any expression you want with them

Access Calculated Field with IIf and or statement

I am working on a grading database. My students write a paper and are given a score from 6 to 1 on 7 different fields. A score of 6 is the best with 1 being the worst. The student must get an 80% and no more than one score of a 4 in any one field to pass.
edit: I am going to try and clean this up:
Currently there is one table with the following Fields:
Student_Name
Paper_Analysis (This field can have a value of 1 to 6, 6 being the best)
Paper_Purpose (This field can have a value of 1 to 6, 6 being the best)
Paper_Voice (This field can have a value of 1 to 6, 6 being the best)
Paper_Concision (This field can have a value of 1 to 6, 6 being the best)
Paper_Accuracy (This field can have a value of 1 to 6, 6 being the best)
Paper_Content (This field can have a value of 1 to 6, 6 being the best)
Paper_Reasoning (This field can have a value of 1 to 6, 6 being the best)
Paper_Score (This is a calculated field adding the Paper_Purpose, Paper_Voice, Paper_Concision, Paper_Accuracy, Paper_Content, Paper_Reasoning)
Paper_Average (This field is a percent calculated as [Paper_Score]/42
Paper_Pass (For a student to pass they must have above 80% in the Paper Average field. They are also only allowed one 4 in any of the Paper_Purpose, Paper_Voice, Paper_Concision, Paper_Accuracy, Paper_Content, Paper_Reasoning fields.)
The Paper_Pass field is what I am having trouble with. I used IIf([Paper_Average]>0.8,"PASS","FAIL"), but this does not take in to account the only one 4 rule.
Thanks for the welcome. I am willing to use SQL or whatever works best. The only caveat is I am pretty new to SQL.
Assuming the rule for passing is the following:
To pass, the average score has to be 80% or above and only one score is allowed to be 4 or below.
Then you could use this formula:
IIf(Paper_Average >= 0.8 AND -((Paper_Analysis <= 4) + (Paper_Purpose <= 4) + (Paper_Voice <= 4) + (Paper_Concision <= 4) + (Paper_Accuracy <= 4) + (Paper_Content <= 4) + (Paper_Reasoning <= 4)) <= 1,'PASS','FAIL') AS Paper_Pass
Why does this work?
A boolean value (that is something that can be True or False) has a numeric representation with True being -1 and False being 0. At least in Microsoft Office (which Access is a part of). I use this by summing up the result of each of the seven Paper fields being 4 or less. This is an example for the first two Paper fields:
(Paper_Analysis <= 4) + (Paper_Purpose <= 4)
If Paper_Analysis is 4 or less, then the first summand is -1. If Paper_Purpose is 4 or less as well, then the second summand is also -1, so the result is -2. If Paper_Purpose was, say, a 5, then the second summand would be 0 and the result -1.
If you do this for all 7 Paper fields and negate the result, then the result could be anything between 0 and 7, measuring the number of scores of 4 or below. To pass, this value has to be <= 1.

Setting priority in SQL

We have a table test in which we have status_cd as one of the column. Status Code can have three value - Prelim, Approved and confirmed.
I have to write a query in such a way that it should fetch the record for confirmed status cd. If confirmed status cd is not present, then fetch for Approved, if approved is not present, fetch for prelim, if prelim is also not present, fetch for null
id rule_id status_cd
1 1 prelim
2 1 null
3 1 approved
in above example, the query should return approved for rule_id=1
Try this way:
SELECT T1.*
FROM test T1 JOIN
(SELECT *,CASE status_cd WHEN 'confirmed' THEN 1
WHEN 'approved' THEN 2
WHEN 'prelim' THEN 3
ELSE 4 END AS Rank
FROM test) T2 ON T1.id=T2.id AND T1.Rule_id =T2.Rule_id
ORDER BY T2.Rank
LIMIT 1
Result:
ID RULE_ID STATUS_CD
3 1 approved
Sample result in SQL Fiddle.
Use the values '3 Confirmed','2 Approved', '1 Prelim' and '0 Null' or just use a default value of zero in a numerical field and set the value to 1, 2, 3 as the rule progresses (much faster execution, but avoid the null either way by using a default value of '0 Null' in the field if you keep a string implementation).
Then fetch the records with this query:
SELECT status_cd FROM YourTableName WHERE rule_id=1 ORDER BY status_cd DESC;
The first row of the query will always contain the answer you want. Additionally, the other rows will be there to confirm that all the other steps were present, etc. if you want to look at those rows too. If you know you only need the one row, then use
SELECT TOP 1 status_cd FROM ... (the rest is the same)