Limiting a field's value in MySQL? - mysql

I am developing a program that uses a MySQL database to store data. I have a table (simplified here):
+---------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------------+------+-----+---------+----------------+
| dataId | int(10) unsigned | NO | PRI | NULL | auto_increment |
| someNum | tinyint(4) | NO | | 0 | |
+---------+------------------+------+-----+---------+----------------+
Now, I update the table with a query like this one.
UPDATE table SET someNum=someNum+2 WHERE dataId=78225;
The ID and the amount someNum changes come dynamically from code.
Now, what I'd like to do is limit someNum's value to between -3 and 3, particularly in that UPDATE. It isn't required, my software can handle it being outside that range, but the data would be clearer with that limit. If someNum+2 would be over 3, I'd just like to set it to 3.
Numeric MIN/MAX would make it easy:
UPDATE table SET someNum=MAX(-3,MIN(3,someNum+2)) WHERE dataId=78225;
I looked at the docs here, but there seems to be no MIN/MAX for numbers. MIN and MAX are found here, but they don't seem to be the right ones for this.
What would be the best way (if any) to implement such a limit in MySQL (not the code that calls MySQL)?

First way: use LEAST() and GREATEST():
UPDATE t SET someNum=GREATEST(-3,LEAST(3,someNum+2)) WHERE dataId=78225;
This is the most easy way because you'll store all the logic inside one UPDATE query.
Second way: create trigger:
DELIMITER //
CREATE TRIGGER catCheck BEFORE UPDATE ON t
FOR EACH ROW
BEGIN
IF NEW.someNum<-3 THEN
NEW.someNum=-3;
END IF;
IF NEW.someNum>3 THEN
NEW.someNum=3;
END IF;
END;//
DELIMITER ;
you can also replace IF with CASE - but I left that two separate constraints for -3 and 3. The benefits here is - that DBMS will handle your data by itself - and you'll be able to pass data as it is and do not worry about ranges. But - there's weakness too: while in first case you can just change query text to adjust desired range, in second case you'll have to re-create trigger again if you'll want to change that constraints (so, less flexibility).
Also you may want to check your data not only on UPDATE statements, but on INSERT too.

The appropriate functions in MySQL are greatest() and least(), not max()/min(). But, I think it is clearer with just a case statement:
UPDATE table
SET someNum = (case when someNum + 2 < -3 then -3
when someNum + 2 > 3 then 3
else someNum + 2
end)
WHERE dataId=78225;

use GREATEST and LEAST instead of MAX and MIN
You could also use a CASE WHEN
update table
set someNum = CASE WHEN SomeNum +2 > 3 THEN 3
WHEN SomeNum +2 < -3 THEN -3
ELSE someNum + 2
END)

Related

MYSQL Update using SUM the same table

Hi so sorry for I know this just to basic. simple update only using sum on the same table. I need to get
total_tbl
+-- month1 --- month2 --- month3 --- total --+
| 3 3 5 |
| 5 3 5 |
| 3 4 |
| 5 5 |
+--------------------------------------------+
I need update the total column using SUM.
I have this statement so far:
UPDATE total_tbl SET total = (SELECT SUM(month1,month2,month3))
I should update even if one column doesn't have a value. Thanks!
SUM() is used to sum an expression across multiple rows, usually using GROUP BY. If you want to add expressions in the same row you just use ordinary addition.
Use COALESCE() to provide a default value for null columns.
UPDATE total_tbl
SET total = COALESCE(month1, 0) + COALESCE(month2, 0) + COALESCE(month3, 0)
You shouldn't need to store this derived information. I would recommend a computed column:
alter table total_tbl
add column total int -- or the datatype you need
generated always as (coalesce(month1, 0) + coalesce(month2, 0) + coalesce(month3, 0)) stored
The additional column gives you an always up-to-date perspective at your data. You can even index it of you like, so it can be queried efficiently.
On the other hand, manually maintaining the values would require updating that column every time a value changes on the row, which can get tedious.

Mysql query like number greater than x

I have a field for comments used to store the title of the item sold on the site as well as the bid number (bid_id). Unfortunately, the bid_id is not stored on its own in that table.
I want to query items that have a number (the bid_id) greater than 4,000 for example.
So, what I have is:
select * from mysql_table_name where comment like '< 4000'
I know this won't work, but I need something similar that works.
Thanks a lot!
Just get your bid_id column cleaned up. Then index is.
create table `prior`
( id int auto_increment primary key,
comments text not null
);
insert `prior` (comments) values ('asdfasdf adfas d d 93827363'),('mouse cat 12345678');
alter table `prior` add column bid_id int; -- add a nullable int column
select * from `prior`; -- bid_id is null atm btw
update `prior` set bid_id=right(comments,8); -- this will auto-cast to an int
select * from `prior`;
+----+-----------------------------+----------+
| id | comments | bid_id |
+----+-----------------------------+----------+
| 1 | asdfasdf adfas d d 93827363 | 93827363 |
| 2 | mouse cat 12345678 | 12345678 |
+----+-----------------------------+----------+
Create the index:
CREATE INDEX `idxBidId` ON `prior` (bid_id); -- or unique index
select * from mysql_table_name where substring(comment,start,length, signed integer) < 4000
This will work, but I suggest create new column and put the bid value in it then compare.
To update value in new column you can use
update table set newcol = substring(comment,start,length)
Hope this will help
There is nothing ready that works like that.
You could write a custom function or loadable UDF, but it would be a significant work, with significant impact on the database. Then you could run WHERE GET_BID_ID(comment) < 4000.
What you can do more easily is devise some way of extracting the bid_id using available string functions.
For example if the bid_id is always in the last ten characters, you can extract those, and replace all characters that are not digits with nil. What is left is the bid_id, and that you can compare.
Of course you need a complex expression with LENGTH(), SUBSTRING(), and REPLACE(). If the bid_id is between easily recognizable delimiters, then perhaps SUBSTRING_INDEX() is more your friend.
But better still... add an INTEGER column, initialize it to null, then store there the extracted bid_id. Or zero if you're positive there's no bid_id. Having data stored in mixed contexts is evil (and a known SQL antipattern to boot). Once you have the column available, you can select every few seconds a small number of items with new_bid_id still NULL and subject those to extraction, thereby gradually amending the database without overloading the system.
In practice
This is the same approach one would use with more complicated cases. We start by checking what we have (this is a test table)
SELECT commento FROM arti LIMIT 3;
+-----------------------------------------+
| commento |
+-----------------------------------------+
| This is the first comment 100 200 42500 |
| Another 7 Q 32768 |
| And yet another 200 15 55332 |
+-----------------------------------------+
So we need the last characters:
SELECT SUBSTRING(commento, LENGTH(commento)-5) FROM arti LIMIT 3;
+-----------------------------------------+
| SUBSTRING(commento, LENGTH(commento)-5) |
+-----------------------------------------+
| 42500 |
| 32768 |
| 55332 |
+-----------------------------------------+
This looks good but it is not; there's an extra space left before the ID. So 5 doesn't work, SUBSTRING is 1-based. No matter; we just use 4.
...and we're done.
mysql> SELECT commento FROM arti WHERE SUBSTRING(commento, LENGTH(commento)-4) < 40000;
+-------------------+
| commento |
+-------------------+
| Another 7 Q 32768 |
+-------------------+
mysql> SELECT commento FROM arti WHERE SUBSTRING(commento, LENGTH(commento)-4) BETWEEN 35000 AND 55000;
+-----------------------------------------+
| commento |
+-----------------------------------------+
| This is the first comment 100 200 42500 |
+-----------------------------------------+
The problem is if you have a number not of the same length (e.g. 300 and 131072). Then you need to take a slice large enough for the larger number, and if the number is short, you will get maybe "1 5 300" in your slice. That's where SUBSTRING_INDEX comes to the rescue: by capturing seven characters, from " 131072" to "1 5 300", the ID will always be in the last space separated token of the slice.
IN THIS LAST CASE, when numbers are not of the same length, you will find a problem. The extracted IDs are not numbers at all - to MySQL, they are strings. Which means that they are compared in lexicographic, not numerical, order; and "17534" is considered smaller than "202", just like "Alice" comes before "Bob". To overcome this you need to cast the string as unsigned integer, which further slows down the operations.
WHERE CAST( SUBSTRING(...) AS UNSIGNED) < 4000

MySQL SELECT order by values of 2 columns

I have a table like this:
CREATE TABLE rows(
UniqueID VARCHAR(225),
Previous VARCHAR(225),
Next VARCHAR(225)
);
With content, that looks like this:
+----------+-----------+-----------+
| UniqueID | Previous | Next |
+----------+-----------+-----------+
| 676 | undefined | 219 |
| 890 | 219 | undefined |
| 219 | 676 | 890 |
+----------+-----------+-----------+
As you can see, the rows have UID's, which the Previous and Next columns refer to.
What I now want, is to write a SELECT * statement, that would order all the results, by the Previous and Next fields. The undefined values mark the end elements. How could I achieve that? In the case of the table showed above, the order I'd want is what's shown there, with the last 2 row positions swapped, so Next of row X Points to a UID of row Y, that has a Previous that points to the UID of the row X. etc.
What you're trying to create is a recursive query. Unfortunately, MySQL does not make this easy. There are relatively simple solutions if the parents always have an index greater than the children, but that is not the case here. There are several questions discussing this type of problem. The following question has answers that explore the different ways to attempt this type of query including using stored procedures.
How to do the Recursive SELECT query in MySQL?
Going with the stored procedure idea, you could try something like:
CREATE PROCEDURE getInOrder()
BEGIN
DECLARE child_id VARCHAR(256);
DECLARE prev_id VARCHAR(256);
SELECT UniqueID INTO prev_id FROM rows WHERE Previous = 'undefined';
SELECT `Next` INTO child_id
FROM rows WHERE UniqueID = prev_id;
CREATE TEMPORARY TABLE IF NOT EXISTS temp_table AS (SELECT * FROM rows WHERE 1=0);
TRUNCATE TABLE temp_table;
WHILE child_id <> 'undefined' DO
INSERT INTO temp_table SELECT * FROM rows WHERE UniqueID = prev_id;
SET prev_id = child_id;
SELECT `Next` INTO child_id
FROM rows WHERE UniqueID = prev_id;
END WHILE;
INSERT INTO temp_table SELECT * FROM rows WHERE UniqueID = prev_id;
SELECT * FROM temp_table;
END;
You can then call the stored procedure to retrieve the table in order.
Working example: http://sqlfiddle.com/#!9/085dec/2
ORDER BY IFNULL(prev, ''), -- some value lower than the rest
IFNULL(next, 'zzzzz') -- some value higher than all values
(Technically, the first part could be simply prev, without the IFNULL.)
If the ids are really numbers, you should use a numeric datatype such as INT UNSIGNED. If they are really strings, do you need 225?
This assumes that prev < next -- Is that necessarily the case? It seems like arbitrary links might not maintain that. If you need to look at next to load the next row based on UniqueId, the code is much more complex.
I think this request lacks on details.
But, you want the final result to be like this?
+----------+-----------+-----------+
| UniqueID | Previous | Next |
+----------+-----------+-----------+
| 676 | undefined | 219 |
| 219 | 676 | 890 |
| 890 | 219 | undefined |
+----------+-----------+-----------+
If I'm right, you can achieve it with (I named the table as demo):
SELECT d.* FROM (
SELECT UniqueID, IF(Previous IS NULL, -1, Previous) AS Previous, IF(Next IS NULL, 999999999999, Next) as Next
FROM demo
)t
JOIN demo d ON d.UniqueID = t.UniqueID
ORDER BY t.Next, t.Previous
;
So, when Previous is NULL you put it with -1 to ensure he's is the first on the list and when Next is NULL you put it with a very high value to ensure it will be the last on the list... then you just have to order the query by Previous and Next.
I must stress that this solution is focused on presented data.

MySQL Update Field with some prefix

i have table have prefixed with bok- and inv-
id | number
1 | bok-1
2 | inv-3
3 | bok-2
4 | inv-2
5 | inv-10
6 | bok-3
How can it sorted the field number prefixed with inv-?
Which in this case the result will be:
id | number
1 | bok-1
2 | inv-1
3 | bok-2
4 | inv-2
5 | inv-3
6 | bok-3
You could just use MySQL's SUBSTRING() function:
ORDER BY CAST(SUBSTRING(number, 5) AS SIGNED)
See it on sqlfiddle.
However, it would probably be better to store the prefix and integer parts in separate columns, if at all possible:
ALTER TABLE mytable
ADD COLUMN prefix ENUM('bok', 'inv'),
ADD COLUMN suffix INT;
UPDATE mytable SET
prefix = LEFT(number, 3),
suffix = SUBSTRING(number, 5);
ALTER TABLE mytable
DROP COLUMN number;
Basically you should redesign your database structure. Unfortunately no other options possible processing this efficiently since the database won't index on those dashes. So separate both in 2 fields is the most common practice. Otherwise you will run table scans on every order by clause.
Edit: In addition to the information from the discussion you had: https://chat.stackoverflow.com/rooms/13241/discussion-between-eggyal-and-gusdecool it is clear that this is a wrong design and the operation you are asking for should not be executed at all.
It would be both impossible to realize it without created a decent structure and to create a solution this way which would be legally ok.

mysql 00/00/00 00:00:00 grab and update to a int timestamp in new column

Alright, I have a field which is called timestamp and it is formatted as such:
00/00/00 00:00:00 and I want to grab that field and then updated it in int timestamp form to a field called tm_unix. So how would I do that with a single update? I can do it as a php loop but thought there has to be a way to do it mysql and just need a quick answer.
Unless someone can tell me how to find less than 30 days on the format 00/00/00 00:00:00?
Thanks
Edit: I am using mysql4
UPDATE nameoftable SET tm_unix=UNIX_TIMESTAMP(timestamp)
I don't know the order of day,month and year in 00/00/00 00:00:00
I give you an example
select datediff(curdate(),str_to_date('21/03/11 00:00:00','%d/%m/%Y %T')) -- 32
Put modifier in the right order to match your situation. As you see you can calculate date differences without using unix timestamp. I suggest you to use str_to_date() function with an update query in order to modify your format.
edit. I've added a simple example:
create table example (
id int not null auto_increment primary key,
datestr varchar(20),
unixfield int) engine = myisam;
insert into example (datestr)
values
('01/04/11 15:03:02'),
('22/04/11 19:03:02');
update example
set unixfield = unix_timestamp(str_to_date(datestr,'%d/%m/%Y %T'));
select *,from_unixtime(unixfield) from example;
+----+-------------------+------------+--------------------------+
| id | datestr | unixfield | from_unixtime(unixfield) |
+----+-------------------+------------+--------------------------+
| 1 | 01/04/11 15:03:02 | 1301662982 | 2011-04-01 15:03:02 |
| 2 | 22/04/11 19:03:02 | 1303491782 | 2011-04-22 19:03:02 |
+----+-------------------+------------+--------------------------+
2 rows in set (0.00 sec)
EDIT. SECOND UPDATE.
This is an example of how you can emulate str_to_date() playing with substring() and substring_index() functions.
set #dtstring = '21/03/11 15:23:10';
select str_to_date(#dtstring,'%d/%m/%Y %T'); -- 2011-03-21 15:23:10
select concat('20',substring(#dtstring,7,2),'-',substring(#dtstring,4,2),'-',substring(#dtstring,1,2),' ',substring_index(#dtstring,' ',-1)) -- 2011-03-21 15:23:10
So, my update query will become:
update example
set unixfield = unix_timestamp(concat('20',substring(datestr,7,2),'-',substring(datestr,4,2),'-',substring(datestr,1,2),' ',substring_index(datestr,' ',-1)));