I'm going to struggle to explain this.. so please feel free to ask for more information.
I'm reading data from a MySQL DB Using :
SELECT * FROM resource where date > DATE_SUB(now(), INTERVAL 12 MONTH) ORDER BY `date` DESC
This produces results like this :
ID DATE STACK REF NAME LOCATION CREW
9 2016-01-01 06:34:50 A6YH75F 12 Local List SPAIN 1A
8 2016-01-01 06:34:48 QWE343 23 POINT ONE GERMANY 3A
7 2016-01-01 06:34:46 WER342 43 Insite UK 4A
6 2016-01-01 06:34:44 WFF5G1 34 LANWise FRANCE 5A
5 2016-01-01 06:34:42 2D3D35 21 Polent USA 7A
4 2016-01-01 06:34:40 8541FW 33 Rosta UK 4B
3 2015-12-30 16:48:23 A6YH75F 12 Local List SPAIN 2A
2 2015-11-21 14:32:01 FFCWF4 34 LANWISE FRANCE 6A
1 2015-10-14 11:02:22 POI8H6 75 BALAND IRELAND 6B
This is where it's hard to explain.
I'm looping through the results and echoing out the results into a <table>
When this is live there may appear hundreds of entries for each NAME, what I want to do it highlight output based on some specific criteria.
For example:
If a NAME appears multiple times, compare the latest and previous values of STACK & REF for that name. (so comparing the last two entries per name)
If they are different highlight the output for that entry.
ie:
I can see Local List appears twice in the list. The first date stamp is 2016-01-01 06:34:50 & then at 2015-12-30 16:48:23
Thats fine. On both occasions the STACK and REF are the same.
However LANWISE also appears twice but it's STACK is different on the newer occasion. How do I flag this and highlight the LATEST Entry for LANWISE.
I'm also looking to see if an entry hasn't appeared in the last 65 days.
So looking at the list BALAND last appeared 2015-10-14 11:02:22 so this is greater than 65 days and they haven't appeared since. How do I highlight this ?
Thanks
You can use the following query that returns all of the information required in order to determine records that need to be highlighted:
SELECT ID, `DATE`, STACK, REF, NAME, LOCATION, CREW,
--Calculate difference between `DATE` field and current date
DATEDIFF(NOW(), `DATE`) AS DaysDiff,
-- Get the count of newer records having the same NAME
COALESCE((SELECT COUNT(*)
FROM resource AS r2
WHERE r2.NAME = r1.NAME AND r2.`Date` > r1.`Date`), 0) CountNewer,
-- Find wether an older record exists having the same NAME and
-- different REF or STACK or both
COALESCE((SELECT 1
FROM resource AS r3
WHERE r3.NAME = r1.NAME AND
r3.`Date` < r1.`Date` AND
(r3.REF <> r1.REF OR r3.STACK <> r1.STACK)), 0) IsHighlighted
FROM resource AS r1
WHERE date > DATE_SUB(now(), INTERVAL 12 MONTH)
ORDER BY `date` DESC
I don't know if it's the best way, but I do something like that ($variable is the result of your query)
$already = array();
#The loop where you make your table. I use foreach but use your current loop (probably while if you fetch a query response) :
foreach ($variable as $key => $value) {
$class='';
if(isset($already[$value['name']]) && $already[$value['name']] == $value['date']) {
$class='highlight';
}
#The next time, the variable exist :
$already[$value['name']] = $value['date'];
#do your stuf and add $class to your class in your HTML (and do something inyour CSS)
}
With the isset($already[$value['name']]) you know if the line existing before. And with the == comparator, you know if it's the same value.
Use CSS and class HTML attribute for Highlighting the concerned lines.
Adapt this code for your usage, it's just an example based of your post.
For compare date, you can use DateTime. Example of use :
$datetime1 = new DateTime($xDateDebut[2].'-'.$xDateDebut[1].'-'.$xDateDebut[0]);
$datetime3 = new DateTime(date('Y').'-'.date('m').'-'.date('d'));
$delaisAvantJourJ = $datetime1->diff($datetime3);
($xDateDebut is an explosed SQL date field)
$delaisAvantJourJ is now an object with a lot of usefull information (like the number of day diff)
Related
Firstly I want to point out that I have tried almost everything. I am trying since last 8 hours to make my list in order, and I have applied dozen of solutions found here.
Here is SQL Fiddle with the sample data. I have found a page that manages to sort my list in the right order, and that is:
1
2
2.B3
5
9
10 A-1
10 A-3
10 B-4
10 B-5
11
12
B3-43
B3-44
B3 - 48
B3 - 49
Basztowa 3
Basztowa 4
Basztowa 5
Basztowa 7
Basztowa 9
D.1
D.2
D.10
D.11
D.12
Kabaty ul. Pod lipą 4
But I am not able to reproduce this using MySQL.
I would appreciate any help as I have no more ideas. I consider using PHP to sort my list but as far as I know DBMS are optimized for this kid of operations so if it's possible I would like to avoid doing this using PHP.
#UPDATE
Thanks to #Jakumi I have created two functions that helps me to solve my problem.
You need to create a column to store your values in sort-friendly format (zeropadded_name), create trigger on update and insert to fill zeropadded_name when name changes and that's all! Now just order by zeropadded_name and enjoy!
Helper functions
regex_replace - Its task is to help us sanitize value by removing all non-alphanumeric characters.
lpad_numbers - pads every number in our string. It's a bit ugly, as I don't know MySQL functions much, but hey, it works, quite fast.
Example:
SELECT lpad_numbers(regex_replace('[^a-zA-Z0-9]', ' ', 'B3 - A-5'));
#B0003A0005
DROP FUNCTION IF EXISTS regex_replace;
CREATE FUNCTION `regex_replace`(
pattern VARCHAR(1000)
CHARSET utf8
COLLATE utf8_polish_ci,
replacement VARCHAR(1000)
CHARSET utf8
COLLATE utf8_polish_ci,
original VARCHAR(1000)
CHARSET utf8
COLLATE utf8_polish_ci
) RETURNS varchar(1000) CHARSET utf8
DETERMINISTIC
BEGIN
DECLARE temp VARCHAR(1000)
CHARSET utf8
COLLATE utf8_polish_ci;
DECLARE ch VARCHAR(1)
CHARSET utf8
COLLATE utf8_polish_ci;
DECLARE i INT;
SET i = 1;
SET temp = '';
IF original REGEXP pattern
THEN
loop_label: LOOP
IF i > CHAR_LENGTH(original)
THEN
LEAVE loop_label;
END IF;
SET ch = SUBSTRING(original, i, 1);
IF NOT ch REGEXP pattern
THEN
SET temp = CONCAT(temp, ch);
ELSE
SET temp = CONCAT(temp, replacement);
END IF;
SET i = i + 1;
END LOOP;
ELSE
SET temp = original;
END IF;
RETURN temp;
END;
DROP FUNCTION IF EXISTS lpad_numbers;
CREATE FUNCTION `lpad_numbers`(str VARCHAR(256)) RETURNS varchar(256) CHARSET utf8 COLLATE utf8_polish_ci
BEGIN
DECLARE i, len SMALLINT DEFAULT 1;
DECLARE ret VARCHAR(256) DEFAULT '';
DECLARE num VARCHAR(256) DEFAULT '';
DECLARE c CHAR(1);
IF str IS NULL
THEN
RETURN "";
END IF;
SET len = CHAR_LENGTH(str);
REPEAT
BEGIN
SET c = MID(str, i, 1);
IF c BETWEEN '0' AND '9'
THEN
SET num = c;
SET i = i + 1;
REPEAT
BEGIN
SET c = MID(str, i, 1);
SET num = CONCAT(num, c);
SET i = i + 1;
END;
UNTIL c NOT BETWEEN '0' AND '9' END REPEAT;
SET ret = CONCAT(ret, LPAD(num, 4, '0'));
ELSE
SET ret = CONCAT(ret, c);
SET i = i + 1;
END IF;
END;
UNTIL i > len END REPEAT;
RETURN ret;
END;
splitting according to underlying structure
Technically, the mysql sorting mechanism works correctly but your strings are formatted in the wrong way. The underlying structure of your data is something like the following (Original column kept for ease of association to the example):
alpha1 num1 alpha2 num2 ... Original
1 1
2 2
2 B 3 2.B3
5 5
9 9
10 A 1 10 A-1
10 A 3 10 A-3
10 B 4 10 B-4
10 B 5 10 B-5
11 11
12 12
B 3 43 B3-43
B 3 44 B3-44
B 3 48 B3 - 48
B 3 49 B3 - 49
Basztowa 3 Basztowa 3
Basztowa 4 Basztowa 4
Basztowa 5 Basztowa 5
Basztowa 7 Basztowa 7
Basztowa 9 Basztowa 9
D 1 D.1
D 2 D.2
D 10 D.10
D 11 D.11
D 12 D.12
If you would sort them now with ORDER BY alpha1, num1, alpha2, num2 they would be sorted as you want them. But the already "formatted" version (the Original column) cannot be sorted easily, because the parts that shall be sorted alphabetically and the parts that shall be sorted numerically are mixed together.
zeropadding
There is a somewhat less extensive alternative needing only one extra column where you assume no number ever goes beyond let's say 10000 and you can now replace every number (not digit!) with a zero-padded version, so 10 A-1 would become 0010A0001 (which is 0010 and A and 0001, obviously), but I don't see this being made on-the-fly in an ORDER BY statement.
But for this example, the zeropadded version (Assumption: every number < 10000):
Original Zeropadded
1 0001
2 0002
2.B3 0002B0003
5 0005
9 0009
10 A-1 0010A0001
10 A-3 0010A0003
10 B-4 0010B0004
10 B-5 0010B0005
11 0011
12 0012
B3-43 B00030043
B3-44 B00030043
B3 - 48 B00030048
B3 - 49 B00030049
Basztowa 3 Baztowa0003
Basztowa 4 Baztowa0004
Basztowa 5 Baztowa0005
Basztowa 7 Baztowa0007
Basztowa 9 Baztowa0009
D.1 D0001
D.2 D0002
D.10 D0010
D.11 D0011
D.12 D0012
This would be sortable to your wishes with ORDER BY zeropadded.
So in the end, you probably have to sort in php or create more columns that help you sort via reformatting/sanitizing/splitting your input.
update
zeropadding explained (simplified)
The main idea behind zeropadding is that the natural format of numbers is different from their format in the computer. In the computer the number 2 is effectively the sequence of digits 0..0002 (so the leading zeros are included) similar 10 (0..0010). When the computer compares numbers, it will go from left to right until it finds different digits:
0...0002
0...0010
======!. (the ! marks the point where the first digit is different)
And then it will determine which digit is bigger or smaller. In this case 0 < 1, and therefore 2 < 10. (Of course the computer uses binary, but that doesn't change the idea).
Now, a string is technically a sequence of characters. String comparison works slightly differently. When two strings are compared, they are not (left) padded, so the first character of each string is really the first character and not a padding (like a space for example). So technically the string A10 is a sequence of characters A, 1 and 0. And since the string comparison is used, it is "smaller" than A2, because the string comparison doesn't see the numbers as numbers but as characters (that are digits):
A10
A2
=! (the ! marks the point where the first character is different)
and because 1 < 2 as characters, A10 < A2. Now to circumvent this problem, we force the format of numbers in the string to be the same as it would be in numerical comparisons, by padding the numbers to the same length which is aligning the digits according to their place value:
A0010
A0002
===!. (the ! marks the point where the first character is different)
Now it's effectively the same comparison you would expect in numerical comparisons. However, you have to make some assumption about the maximal length of numbers, so that you can choose the padding appropriately. Without that assumption, you'd have a problem.
The only (logical) point that remains: When the compared string has an alphabetical character where the other has a number, what does the padding change? The answer is: Nothing. We don't change numbers into letters, and numbers are smaller than letters, so everything stays in the same order in that case.
The effect of zeropadding is: We adjust the "number" comparison in strings to be similar to the real number comparison by aligning the digit characters according their value.
SELECT name FROM realestate ORDER BY name ASC;
This should sort your list in alphanumeric data... I don't see the issue.
EDIT: OK, I still don't know if I really understood what is the goal of this issue (is it for a contest?), but I can submit this "twisted" query (that I hope I will never use in my career):
SELECT name FROM realestate
ORDER BY IF(SUBSTRING(name, 1, 2) REGEXP '[A-Z]', 100000, CAST(name AS UNSIGNED)) ASC,
SUBSTRING(name, 1, 2) ASC,
CAST(SUBSTRING(name FROM LOCATE('.', name)+1) AS UNSIGNED) ASC,
REPLACE(name, ' ', '') ASC;
Maybe someone can find an easier way, because I admit my answer is a bit complicated. BUT, Kamil and Jakumi solutions are much more tricky and complicated.
I have a PHP script that helps me to make some financial reports like capital, profits, total sold item, total item in store between 2 dates:
$result[] = 0;
$res[] = 0;
$fetchStat[]=0;
if(isset($_POST['generate_capital']))
{
$from_d = $_POST['from_d'];
$to_d = $_POST['to_d'];
$stat = "SELECT sum(sell_quantity*(sell_price-init_price)) AS 'rebeh', sum(init_price*(quantity-sell_quantity)) AS 'capital', sum(sell_price*sell_quantity) AS 'profits', sum(quantity) AS 'total_item', sum(sell_quantity) AS 'total_sell' FROM purchases WHERE date_now BETWEEN :d1 AND :d2";
$stmtStat = $conn->prepare($stat);
$stmtStat->bindValue(':d1', $from_d);
$stmtStat->bindValue(':d2', $to_d);
$execStat = $stmtStat->execute();
$fetchStat= $stmtStat->fetchAll();
}
Now, the same query in MySQL workbench will give me a specific result:
SELECT sum(sell_quantity*(sell_price-init_price)) AS 'rebeh',
sum(init_price*(quantity-sell_quantity)) AS 'capital', sum(sell_price*sell_quantity) AS 'profits',
sum(quantity) AS 'total_item',
sum(sell_quantity) AS 'total_sell'
FROM purchases WHERE date_now BETWEEN '2016-02-02' AND '2016-05-09'
The result is:
And the same query in the PHP script with the same date range will give me other values:
I've changed the Arabic keywords with English ones so you can see the difference.
Any help is appreciated.
EDIT: Adding var_dump result for 2 dates
As it stands, it appears that the most likely cause is that your date formats have the month and day in the wrong order, for the 2nd of February, that doesn't matter, but for the 9th of May, it would become the 5th of September.
Now, the reason that the data for the 9th of May isn't included when you run the query directly is because a date is actually midnight on that date, making it exclusive of that date when it comes at the end of a between.
It's possible there is something else, so if this doesn't work, try echoing out all your values at every point, until you know exactly what is being run, and it should become clear.
To clarify, when using '/' in a date, MySQL uses the following format:
'%d/%m/%Y'
Which would explain why it works if you enter a string into your text box.
I'm developing a game and I want a script to run every minute (with the help of cron job, I guess).
So the script that I want to run takes the maximum HP of a character (from a column in a database table), calculates to get 10% of that value and then add those 10% to the characters current hp (which is another column in the table). I then want to iterate this over all rows in the database table.
E.g. consider the following table:
charname current_hp max_hp
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
player1 20 30
player2 15 64
player3 38 38
After the script has been run, I want the table to look like this:
charname current_hp max_hp
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
player1 23 30
player2 21 64
player3 38 38
So, I know how I technically could implement this, e.g.
$characters = $db->getCharacterList();
foreach ($characters as $character) {
$maxHp = $character['max_hp'];
$curHp = $character['current_hp'];
$hpToAdd = $maxHp / 10;
if (($curHp + $hpToAdd) >= $maxHp) {
$db->update('characters', $character['id'], array(
'current_hp' => $maxHp
));
} else {
$db->update('characters', $character['id'], array(
'current_hp' => ($curHp + $hpToAdd)
));
}
}
My only question is: Is the solution posted above an efficient way to implement this? Will this work on a table with, say, 10 000 rows, or will it take too long?
Thanks in advance.
Are you using a SQL database? If you have 10,000 rows, your solution will hit the database 10,000 times. A SQL statement like this will only make one database hit:
UPDATE characters
SET current_hp = LEAST(max_hp, current_hp + (max_hp / 10))
The database will still have to do the I/O necessary to update all 10,000 rows, but it will happen more efficiently than thousands of individual queries.
In One SQL statement will do all your requirements for more performance remove any indexes on this table to keep it fast
UPDATE test
SET current_hp= CASE
WHEN ((max_hp/10)+current_hp) >=max_hp THEN max_hp
ELSE ((max_hp/10)+current_hp)
END
I'm trying to get this output.
MDT 1
MDT 2
MDT 3
MDT 11
MDT 44
but, The values are ordered alphabetically, so 123 comes before 2.
example :
MDT 1
MDT 11
MDT 156
MDT 2
MDT 3
MDT 303
MDT 44
and so on.
I'm use this code, but it seem didn't work.
SELECT * FROM file ORDER BY ABS(ID) ASC
How can I solve this?
If your ID is always going to contain the prefix as MDT, then you can use this, to sort as per your requirement:
SELECT * FROM File
ORDER BY CAST(replace(ID, 'MDT ', '') AS UNSIGNED) ASC
SQLFiddle demo
Try that snippet
SELECT * FROM file ORDER BY ID + 0 ASC
Try Like this it will sort based on numeric :
select substr(id,4)*1 from file order by substr(id,4)*1
It will gives
1
2
3
11
44
...
If You want all fields try the below query ("substr(id,4)" or "substr(id,5)") based on your string length (ex: id= proj-001911 --> take SUBSTR( id, 6 ) *1) )
select * from file order by substr(id,4)*1
SELECT * FROM file
ORDER BY CONVERT(SUBSTRING(ID,5),UNSIGNED) ASC
SUBSTRING() will extract all characters after 'MDT ' and CONVERT() will change remaining substring to unsigned integer on which ORDER BY is performed
note SUBSTR() is a synonym for SUBSTRING().
I was searching it out too, but just while reading over here I got it striked in my head and found one solution that if that column is having only numbers as data then you need to make modification in database table and define that column as INT value type, then I am sure what you need will be done.
Eg. {SELECT * FROM file ORDER BY CONVERT(SUBSTRING(ID,5),UNSIGNED) ASC} in such case Convert is column and that needs to be defined as INT(5) will work.
For the mysql "between" operator, is it necessary for the before and after value to be numerically in order?
like:
BETWEEN -10 AND 10
BETWEEN 10 AND -10
Will both of these work or just the first one?
Also, can I do:
WHERE thing<10 AND thing>-10
Will that work or do I have to use between?
Lastly, can I do:
WHERE -10<thing<10
?
BETWEEN -10 AND 10
This will match any value from -10 to 10, bounds included.
BETWEEN 10 AND -10
This will never match anything.
WHERE thing<10 AND thing>-10
This will match any value from -10 to 10, bounds excluded.
Also, if thing is a non-deterministic expression, it is evaluated once in case of BETWEEN and twice in case of double inequality:
SELECT COUNT(*)
FROM million_records
WHERE RAND() BETWEEN 0.6 AND 0.8;
will return a value around 200,000;
SELECT COUNT(*)
FROM million_records
WHERE RAND() >= 0.6 AND RAND() <= 0.8;
will return a value around 320,000
The min value must come before the max value. Also note that the end points are included, so BETWEEN is equivalent to:
WHERE thing>=-10 AND thing<=10
Please keep it to one question per post. Anyway:
http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#operator_between
BETWEEN min AND max, in that order.
from the link:
This is equivalent to the expression (min <= expr AND expr <= max) if
all the arguments are of the same type
The second alternative will also work, of course.
First question:
Will both of these work or just the first one?
yes,both of these work
Second question:
Will that work or do I have to use between?
it also valid but as you can see just empty result
Yes your between must be in order to return the excepted result.
Let's say you have a table with a row called mynumber that contains 10 rows :
MyNumber
--------
1
2
3
4
5
6
7
8
9
10
So
select * from thistable table where table.myNumber BETWEEN 1 and 5
will return
1
2
3
4
5
but
select * from thistable table where table.myNumber BETWEEN 5 and 1
return nothing.
Your 2nd question : yes it is the same thing. but beware in you example you will have to put <= and >= to be the same as between. if not, in our example, you would get
2
3
4
Hope it help
I've already seen such things work with integers :
WHERE -10
But it's better to avoid it. One reason is that it doesn't seem to work well with other types. And MySQL doesn't issue any warning.
I've tried it with datetime columns, and the result was wrong.
My request looked like this one:
SELECT *
FROM FACT__MODULATION_CONSTRAINTS constraints
WHERE constraints.START_VALIDITY<= now() < constraints.END_VALIDITY
The result was not as expected. I got twice as many results as the same request with two inequalities (which returned correct results). Only the 1st part of the expression evaluated correctly.