INSERT Trigger for a rating system: Division by zero error - mysql

I have followed this paper which suggests what is claimed to be a good approach to 5 star rating systems.
The schema is:
CREATE TABLE `ratings` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`product_id` int(11) DEFAULT NULL,
`positive` float NOT NULL DEFAULT 0,
`negative` float NOT NULL DEFAULT 0,
`stars` float DEFAULT 0,
`total` int(11) NOT NULL,
`lower_bound` float DEFAULT NULL,
PRIMARY KEY (`id`)
)
I setup the INSERT trigger which is:
CREATE
TRIGGER `insert_rating` BEFORE INSERT ON `ratings`
FOR EACH ROW
SET new.total = new.positive + new.negative,
new.stars = ROUND( (((new.positive / new.total) * 4) + 1) * 2, 0) / 2,
new.lower_bound = ((new.positive + 1.9208) / (new.positive + new.negative) - 1.96 * SQRT((new.positive * new.negative) / (new.positive + new.negative) + 0.9604) / (new.positive + new.negative)) / (1 + 3.8416 / (new.positive + new.negative))
but upon my first insert of (Also taken from the paper):
INSERT into ratings (product_id, positive, negative) VALUES (1, 0, 0)
I got a Division by zero error. Is there an SQL error in the design of this method or have I done something wrong? Would like to know how to fix.
There's also an update trigger but I don't think I will ever need to update it with zeros:
CREATE
TRIGGER `update_rating` BEFORE UPDATE ON `ratings`
FOR EACH ROW
SET new.total = new.positive + new.negative,
new.stars = ROUND( (((new.positive / new.total) * 4) + 1) * 2, 0) / 2,
new.lower_bound = ((new.positive + 1.9208) / (new.positive + new.negative) - 1.96 * SQRT((new.positive * new.negative) / (new.positive + new.negative) + 0.9604) / (new.positive + new.negative)) / (1 + 3.8416 / (new.positive + new.negative))

Gordon's answer will prevent the error.
The trigger expects either positive or negative to be non-zero, which makes sense. Your insert statement adds NO actual ratings (either positive or negative); so what's the point?
More importantly, why not save yourself some headaches and change the columns to do the work for you?
In your table definition, change the columns to
...
total INT GENERATED ALWAYS AS (positive+negative) STORED,
stars INT GENERATED ALWAYS AS (ROUND((((positive/NULLIF(positive+negative,0))*4)+1) *2, 0)/2) STORED,
lower_bound DOUBLE GENERATED ALWAYS AS ((positive + 1.9208) / NULLIF(positive + negative, 0) - 1.96 * SQRT((positive * negative) / (positive + negative) + 0.9604) / NULLIF(positive + negative, 0))) / (1 + 3.8416 / NULLIF(positive + negative, 0)) STORED,
...
(And remove all the triggers, of course)

The following line sets new.total to 0
SET new.total = new.positive + new.negative
The next line has new.positive / new.total. Since you just set new.total to 0 you get a divide by 0. I would try testing for this condition and setting new.stars = 0 where is happens.

It is like really hard to tell which division is causing the issue. But the solution is NULLIF():
SET new.total = new.positive + new.negative,
new.stars = ROUND( (((new.positive / NULLIF(new.total, 0)) * 4) + 1) * 2, 0) / 2,
n new.lower_bound = ((new.positive + 1.9208) / NULLIF(new.positive + new.negative, 0) - 1.96 * SQRT((new.positive * new.negative) / NULLIF(new.positive + new.negative, 0) + 0.9604) / NULLIF(new.positive + new.negative, 0))) / (1 + 3.8416 / NULLIF(new.positive + new.negative, 0)
This turns 0s into NULLs, so the result is NULL rather than an error.

Related

Converting Degrees/Minutes/Seconds to Decimals using MySQL

I'm using MySQL version: 5.7.22
I've got two columns Latitude and Longitude both in Degrees/Minutes/Seconds format that I want to convert to Decimal format ex: 48° 52.250' N to 48.93611111
I have the following script but I'm stuck at how to split the degrees minutes and seconds. I cannot hard-code the values as I've done here left(Latitude,2) since the degrees might have 3 decimals as well
SELECT Latitude,
left(Latitude, 2) +
(TRUNCATE((Latitude - TRUNCATE(Latitude)) * 100) / 60) +
(((Latitude * 100) - TRUNCATE(Latitude * 100)) * 100) / (60 * 60) AS DECIMAL_DEGREES
FROM small_ocean_data
The formula for the conversion is this: D + M/60 + S/3600 * -1 if direction in ['W', 'S'] else 1
Any help would be grateful!
Well if I use this formula of yours: D + M/60 + S/3600
Where I believe D is Degrees and M are Minutes and S are Seconds.
With this select:
select SUBSTRING_INDEX('48° 52.250', '°',1) +
(SUBSTRING_INDEX('48° 52.250',' ',1)/60) +
(SUBSTRING_INDEX('48° 52.250','.',1)/3600);
Or for your database:
select SUBSTRING_INDEX(Latitude, '°', 1) +
(SUBSTRING_INDEX(Latitude ,' ',1)/60) +
(SUBSTRING_INDEX(Latitude ,'.',1)/3600) "DECIMAL_DEGREES"
FROM small_ocean_data;
I get 48.81333333333333
48 + 0.8 + 0.013333333333333334
Here is the DEMO

Wilson Score for 1-5 star rating in sql

I found this really well explained calculation but I really don't know if it's correct or not. I've seen a lot of posts on the subject I haven't seen one calculate for 5.
The most important part which is the following code, but I cannot understand the math can't tell if its correct or not.
CREATE
TRIGGER `update_rating` BEFORE UPDATE ON `ratings`
FOR EACH ROW
SET new.total = new.positive + new.negative,
new.stars = ROUND( (((new.positive / new.total) * 4) + 1) * 2, 0) / 2,
new.lower_bound = ((new.positive + 1.9208) / (new.positive + new.negative) - 1.96 * SQRT((new.positive * new.negative) / (new.positive + new.negative) + 0.9604) / (new.positive + new.negative)) / (1 + 3.8416 / (new.positive + new.negative))
I need to know if it's correct or not. And it be really useful some explanation for how that formula works.
ps: Please do not mark this as a repost, I wrote a similar question but that one was more for sql conversion to c# and did not get the response for sql verification.

Translation from Binary to Decimal

How does one translate the following binary to Decimal. And yes the decimal points are with the whole binary value
1) 101.011
b) .111
Each 1 corresponds to a power of 2, the power used is based on the placement of the 1:
101.011
= 1*2^2 + 0*2^1 + 1*2^0 + 0*2^-1 + 1*2^-2 + 2*2^-3
= 1*4 + 1*1 + 1/4 + 1/8
= 5.375
.111
= 1*2^-1 + 1*2^-2 + 1*2^-3
= 1/2 + 1/4 + 1/8
= .875
If you don't like dealing with the decimal point you can always left shift by multiplying by a power of 2:
101.011 * 2^3 = 101011
Then convert that to decimal and, since you multiplied by 2^3 = 8, divide the result by 8 to get your answer. 101011 converts to 43 and 43/8 = 5.375.
1) 101.011
= 2*2^-3 + 1*2^-2 + 0*2^-1 + 1*2^0 + 0*2^1 + 1*2^2
= (1/8) + (1/4) + 0 + 1 + 0 + 4
= 5.375
2) .111
= 1*2^-3 + 1*2^-2 + 1*2^-1
= (1/8) + (1/4) + (1/2)
= .875
101.011 should be converted like below
(101) base2 = (2^0 + 2^2) = (1 + 4) = (5) base10
(.011) base2 = 0/2 + 1/4 + 1/8 = 3/8
So in total the decimal conversion would be
5 3/8 = 5.375
Decimal numbers cannot be represented in binary. It has to be whole numbers.
Here is a simple system
Let's take your binary number for example.
101011
Every position represents a power of 2. With the left-most position representing the highest power of 2s. To visualize this, we can do the following.
1 0 1 0 1 1
2 ^ 6 2 ^ 5 2 ^ 4 2 ^ 3 2 ^ 2 2 ^ 1
We go by each position and do this math
1 * (2 ^6 ) + 0 * (2 ^ 5) + 1 * (2 ^ 4) + 0 * (2 ^ 3) + 1 * (2 ^ 2) + 1 * (2 ^ 1)
Doing the math gives us
(1 * 64) + (0 * 32) + (1 * 16) + (0 * 8) + (1 * 4) + (1 * 2) =
64 + 0 + 16 + 0 + 4 + 2 = 86
We get an answer of 86 this way.

Mysql POINT column for distance search

yes i know this question ever asked plenty of time but all seems outdated from 2012, base of thoses question/ansewers ,
i tried to perform the classic search distance with column POINT
but i have some trouble unresolvable..
is normal my POINT column looks like this ?
0x00000000010100000085B1852007052040C0B2D2A414684840
Here is all my steps, i am not able to see whats wrong,
i did based from last stack questions/answers.
I use mariadb 10 with Heideisql gui.
i have 2 colums lat and lon ,
i created a geopoints POINT column,
populate geopoint like this:
UPDATE geoFRA
SET geopoints = GeomFromText(CONCAT('POINT (', lon, ' ', lat, ')'))
After that my geopoints column looks like this :
0x00000000010100000085B1852007052040C0B2D2A414684840
Then i try to perform the query in 2 maners , first try :
SET#lat = 48.88;
SET#lon = 2.34;
SELECT *
FROM geoFRA
WHERE MBRContains(LineFromText(CONCAT(
'('
, #lon + 700 / ( 111.1 / cos(RADIANS(#lon)))
, ' '
, #lat + 700 / 111.1
, ','
, #lon - 700 / ( 111.1 / cos(RADIANS(#lat)))
, ' '
, #lat - 700 / 111.1
, ')' )
,geopoints)
and second try :
SET#lat = 48.88;
SET#lon = 2.34;
SET #kmRange = 172; -- = 50 Miles
SELECT *, (3956 * 2 * ASIN(SQRT(POWER(SIN((#lat - abs(`lat`)) * pi()/180 / 2),2) + COS(#lat * pi()/180 ) * COS(abs(`lat`) * pi()/180) * POWER(SIN((lon - `lon`) * pi()/180 / 2), 2)))) as distance
FROM `geoFRA`
WHERE MBRContains(LineString(Point(#lat + #kmRange / 111.1, #lon + #kmRange / (111.1 / COS(RADIANS(#lat)))), Point(#lat - #kmRange / 111.1, #lon - #kmRange / (111.1 / COS(RADIANS(#lat))))), `geopoints`)
Order By distance
I begin to think there is some mariadb incompatibility ?! or did i miss something?
thanks for any help..,
flau
CREATE TABLE geoFRA (id int NOT NULL, geopoints point NOT NULL);
INSERT INTO geoFRA (id, geopoints) VALUES
(1, geomFromText('POINT(48 2)')),
(2, geomFromText('POINT(48 3)')),
(3, geomFromText('POINT(48.88 2.34)')),
(4, geomFromText('POINT(49 2)')),
(5, geomFromText('POINT(49 3)'));
SET #p=geomFromText('POINT(48.88 2.34)');
SELECT X(geopoints), Y(geopoints), asText(geopoints), ST_Distance(geopoints, #p) as d
FROM geoFRA
ORDER BY d;
This returns the geopoints ordered by distance. Using geopoints without X(), Y() and asText() returns them in the Well-Known Binary (WKB) format: http://dev.mysql.com/doc/refman/5.7/en/gis-data-formats.html#gis-wkb-format

How to Convert varchar time to Time in SQL Server 2008

I have decimal time and I am able to convert into time. However I need to insert converted time into a table which has a column of time datatype
DECLARE #hours decimal(15,4)
SELECT #hours = 20.5 //20 Hrs and 30 mins
SELECT
RIGHT('00' + CONVERT(varchar(2), FLOOR(#hours)), 2) + ':' +
RIGHT('00' + CONVERT(varchar(2), FLOOR(((#hours - FLOOR(#hours)) * 60))), 2) + ':' +
RIGHT('00' + CONVERT(varchar(2), FLOOR(((#hours - FLOOR(#hours)) * 60) - FLOOR(((#hours - FLOOR(#hours)) * 60))) * 60), 2) AS Time
This is my temp table that I need to insert the value into:
create table #timetest(timetest time)
insert into #timetest
SELECT
RIGHT('00' + CONVERT(varchar(2), FLOOR(#hours)), 2) + ':' +
RIGHT('00' + CONVERT(varchar(2), FLOOR(((#hours - FLOOR(#hours)) * 60))), 2) + ':' +
RIGHT('00' + CONVERT(varchar(2), FLOOR(((#hours - FLOOR(#hours)) * 60) - FLOOR(((#hours - FLOOR(#hours)) * 60))) * 60), 2)
I get this error:
Conversion failed when converting date and/or time from character
string.
Please help me
I believe you cannot have TIME values for more than 24 hours ....
Check the MSDN documentation on TIME:
Range 00:00:00.0000000 through 23:59:59.9999999
Update: if you've changed your time to less than 24 hours, you can do the insert like this:
create table #timetest(timetest time)
insert into #timetest(timetest)
SELECT
CAST(RIGHT('00' + CONVERT(varchar(2), FLOOR(#hours)), 2) + ':' +
RIGHT('00' + CONVERT(varchar(2), FLOOR(((#hours - FLOOR(#hours)) * 60))), 2) + ':' +
RIGHT('00' + CONVERT(varchar(2), FLOOR(((#hours - FLOOR(#hours)) * 60) - FLOOR(((#hours - FLOOR(#hours)) * 60))) * 60), 2) AS TIME)
You need to explicitly CAST your string AS TIME and then everything should work just fine.
declare #hours decimal(15,4)
set #hours = 20.5 --20 Hrs and 30 mins
create table #timetest(timetest time(0))
insert into #timetest(timetest) values(cast(#hours/24 as datetime))
select * from #timetest
drop table #timetest
Result:
20:30:00