Split MYSQL results to contain n sizes per slice - php

This question is basically similar to SoulieBaby's question here:Split MYSQL results into 4 arrays, except that I wanted to split the result to contain a specific length.
Say, I wanted the result of an array which has a length of 9 to be splitted and have the splitted array to contain 5 lengths. So first array will have 5 and second will have 4.
Is this possible?
Thanks so much for any help!

The referenced question wanted to always have 4 chunks, therefore the solution was to create chunks of size ceil(count($array) / 5).
This case is easier, the (maximum) size is constant but the number of chunks vary.
Therefore the answer is simply:
array_chunk($array, 5);

Try:
DELIMITER $$
CREATE PROCEDURE split (in data varchar(500),in cad char(1))
BEGIN
declare pos int default 0;
declare numero int default 0;
declare van int default 0;
declare a varchar(500) default '';
drop TEMPORARY table IF EXISTS tmp_split;
create TEMPORARY table tmp_split(dato varchar(500)) ENGINE=MEMORY;
set pos=LOCATE(cad,data);
while pos<>0 do
set numero=numero+1;
set pos=LOCATE(cad,data,pos+1);
end while;
set a=SUBSTRING_INDEX(data,cad,1);
while numero>van do
set data=SUBSTRING(data,LENGTH(a)+2);
insert into tmp_split values (a);
set a=SUBSTRING_INDEX(data,cad,1);
set van=van+1;
end while;
insert into tmp_split values (a);
select * from tmp_split;
END
call split('1,2,5,52,64,365,9714,253,6697,8,9,2,62',',');

Related

Select row if at least one possible SUM of its children equal a given number?

I am trying to filter a set of appartments by computing every possible surface the building has got, and then checking if it matches my criterias or not.
Example : I have one building A that is composed of 3 appartments of 200m² each, let's name them 1, 2, and 3.
In my search, I should be able to retrieve that building if my criterias meet these given cases :
I'm looking for 200m² (We have three appartments that match this criteria)
I'm looking for 400m² (We have a few possible SUMS of surface in the building that would match, whether it's 1+2, 1+3, 2+3 doesn't matter)
I'm looking for 600m² (We have the SUM of all surfaces of the building, 1+2+3)
I am able to answer to the first case with a MIN(), so I get the smallest surface available. I am also able to answer to the last case because I get the max available surface possible with a SUM() of all appartments.
But the second case is troubling me, I don't know if i can compute these "Possible SUMS", as I'd call them, inside a query.
Here's the SQL I've got so far, knowing well that it doesn't answer the second case :
SELECT DISTINCT building.*
FROM building AS building
LEFT JOIN appartment a ON a.id_building = building.id
WHERE (
SELECT MIN(a2.surface)
FROM appartment a2
INNER JOIN building ON a2.id_building= building.id
) >= 399
AND (
SELECT SUM(a2.surface)
FROM appartment a2
INNER JOIN building ON lot.id_building= building.id
) <= 401
I tried to work with the whole set of results with PHP rather than in SQL but it's not my prefered option because it would mean the redoing of a lot of work that hasn't been done by me, so it quickly got harder. I also read about HAVING statements but I don't understand where I should put them and what the condition inside should be.
maybe something like this, but i'm not very sure about your table structure
SET #totalSurface= 0;
SELECT #totalSurface:=#totalSurface+`surface`, `id`, `building` FROM `apartment`
WHERE #totalSurface=400
GROUP BY `building` ;
I finally managed to find something using a stored function. I use this cursor with the building id as parameter to loop through every appartement of said building, sorted by surface. Then, for each loop, i add the surface to my total, and check if I am or not in my interval of given criterias. If yes, set return value to true, if not, return value stays false.
Here it goes, I hope it helps if someone is stuck as I was :
CREATE DEFINER=`homestead`#`%` FUNCTION `buildingHasSurface`(`surface_min` INT, `surface_max` INT, `building_id` INT) RETURNS TINYINT(1)
BEGIN
DECLARE curseurFini INT;
DECLARE valide INT;
DECLARE surface_totale INT;
DECLARE surface_row INT;
DECLARE curs CURSOR FOR SELECT surface_dispo FROM appartement WHERE id_building = building_id ORDER BY surface_dispo ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET curseurFini = 1;
OPEN curs;
SET curseurFini = 0;
SET surface_totale = 0;
SET valide = 0;
REPEAT
FETCH curs INTO surface_row;
IF curseurFini = 0 THEN
SET surface_totale = surface_totale + surface_row;
IF surface_totale >= surface_min
AND surface_totale <= surface_max
THEN
SET valide = 1;
END IF;
END IF;
UNTIL curseurFini END REPEAT;
CLOSE curs;
RETURN valide;
END$$

Recursive count of employees under a manager using mysql

I need the list of all employees, following the hierarchy of the manager using MYSQL from the following table. In oracle or mssql it is easy job, but could not find any solution in MySQL. Can anyone help me out how to sort it out.
id name manager
1 John 6
2 Gill 7
3 Ben 2
4 Roy 8
5 Lenin 6
6 Nancy 7
7 Sam 0
8 Dolly 3
If you still can limit the maximal number of levels, here is a solution with a recursive procedure. Since recursive functions are not allowed in MySQL, we have here a function (manager_count), which wraps the results from the recursive procedure. Recursion depth is controlled by the max_sp_recursion_depth variable, which takes 255 as its maximum. Use as follows: SELECT *,manager_count(id) FROM my_table. It's not the optimal solution, since it doesn't take into account already counted branches of the hierarchy (a temporary table can actually serve as a cache).
DELIMITER //
DROP FUNCTION IF EXISTS manager_count//
CREATE FUNCTION manager_count(_manager INT) RETURNS INT
BEGIN
DECLARE _count INT DEFAULT 0;
SET max_sp_recursion_depth = 255;
# manager_count_helper does the job
CALL manager_count_helper(_manager, _count);
# subtract 1, because manager_count_helper will count this manager as well
RETURN _count - 1;
END//
DROP PROCEDURE IF EXISTS manager_count_helper//
CREATE PROCEDURE manager_count_helper(IN _manager INT, INOUT _count INT)
BEGIN
IF EXISTS (SELECT 1 FROM my_table WHERE id = _manager) THEN
BEGIN
DECLARE _next_manager INT DEFAULT 0;
DECLARE done BOOLEAN DEFAULT FALSE;
# cursor to loop through the employees
DECLARE _cursor CURSOR FOR SELECT id FROM my_table WHERE manager = _manager;
# if done, the done variable gets TRUE and it's time too leave
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
# count 1, because this guy should be counted as well
SET _count = _count + 1;
OPEN _cursor;
read_loop: LOOP
FETCH _cursor INTO _next_manager;
IF done THEN LEAVE read_loop;
END IF;
CALL manager_count_helper(_next_manager, _count);
END LOOP;
CLOSE _cursor;
END;
END IF;
END

Unique auto increment reference number that resets at the 1st of each month

I have a database table with various fields involving jobs done on ships including a field named created which uses DATE format. The result i want to achieve is to have a unique reference number for each job. The format i want to use for this reference number is:
example : Lets say the date of the job is 23/11/2013 like today. Then the number would be 1311/1 the next job 1311/2 and goes on. If the month changes and the date of the next job is for example 15/12/2013 the refence number i would like to have if its the first job of the month is 1312/1.
So the two first digits of my reference number would show the year,the next two the month and the number after the slash i would like it to be an auto_increment number that will reset each month.My code so far is :
$job_num = 1;
foreach($random as $rand) {
$vak = $rand->created;
$gas = $rand->id;
$vak1 = substr($vak, 2, 2);
$vak2 = substr($vak, 5, -3);
$vak3 = substr($vak, 8, 10);
if(date(j) > 1) {
echo $vak1.$vak2.'/'.$job_num.'<br>';
$job_num++;
} else {
$job_num = 1;
echo $vak1.$vak2.'/'.$job_num.'<br>';
$job_num++;
}
}
So as u can see i want to achieve all this inside a foreach statement. And although the above code kinda works,the porblem i have is that at the 1st of any month in other words when date(j) = 1 if i insert more than one job in my database the $job_num variable resets as many times as the jobs i have inserted resulting in identical refence numbers.
I am really new in programming and php so if anyone could help me solve this, i would really appreciate it.
Thanks in advance:)
You can't do this with the auto-increment mechanism if you use InnoDB, which is MySQL's default storage engine.
You can do it with the MyISAM storage engine, but you really shouldn't use MyISAM, for many reasons.
So you'll have to assign the repeating numbers yourself. This means you have to lock the table while you check what is the current maximum number for the given month, then insert a new row with the next higher number.
If that seems like it would impair concurrent access to the table, you're right. Keep in mind that MyISAM does a table-lock during insert/update/delete of any row.
If you can use the MyISAM engine, you can get this behavior without procedural code.
create table demo (
yr_mo integer not null,
id integer auto_increment,
other_columns char(1) default 'x',
primary key (yr_mo, id)
) engine=MyISAM;
insert into demo (yr_mo) values (1311);
insert into demo (yr_mo) values (1311);
insert into demo (yr_mo) values (1311);
insert into demo (yr_mo) values (1311);
insert into demo (yr_mo) values (1312);
The last INSERT statement starts a new month.
Now if you look at the autoincrement values the MyISAM engine assigned . . .
select * from demo;
YR_MO ID OTHER_COLUMNS
--
1311 1 x
1311 2 x
1311 3 x
1311 4 x
1312 1 x
This is MyISAM's documented behavior; look for "MyISAM Notes".
If you want the form yymm/n for presentation, use something like this.
select concat(yr_mo, '/', id) as cat_key
from demo;

Complicated ORDER BY needed to sort irregular values

I am trying to develop a system to display products from a database on a webpage. Normally this is no problem, except that one manufacturer has several abnormal part numbers I need to sort by properly.
Normally, I could just use an invisible column to sort things out, but then that makes inserting new items in-between two older ones much more difficult.
For example, here are some of the part numbers:
1211
1225
14-302
14-303
2015
23157
3507
35280UP
42-3309
42-3312
4241
Now, the normal order by mfgr produces the above order.
What it SHOULD be is something more like this:
14-302
14-303
42-3309
42-3312
1211
1225
2015
3507
4241
23157
35280UP
What is going to be my best bet on sorting this properly? If they were just being made in a csv file and uploaded afterwards this wouldn't be a problem. But because of automatic database changes, the server will be modifying values in real time. So manually updating it is out of the question. This means that a back end will be required to insert new items, but by what method could I insert an item between another? I would prefer to not resort to something like decimals to give me X in between values (Like I have 1.00 and 2.00 and I want to put another between them so I make it 1.50).
Any help would be appreciated.
EDIT:
The way I would like to sort it is this:
if it has a hyphen, ie 14-302, it is sorted by the 14, and then any 14-xxx is sorted by the numbers after the hyphen.
then, just numbers would be sorted by their actual numner, 802 comes before 45768.
Then any number that has a letter(s) after it will be sorted by the number, the the letter so 123a comes before 123b but after 122. And 123b comes before 124c.
and lastly anything that begins with an M- will be sorted last, and by the numbers after the hyphen.
With your adjusted question the principle is still the same.
My first instinct was to go with extra field but you could use stored function for those.
You still need to divide your partnumber in 3 parts (I take it it's not more than that).
Part1 is an INTEGER and has the first number if existing else fill it with maxint (largest for part1).
Part2 is a CHAR and contains letters if existing.
Part3 is an INTEGER containing the second number if existing.
You can sort by calling a function for these values.
Here is the complete source:
For your convenience a link to a working SQL Fiddle.
It's really sloppy/fast programming but it works. Put together in little time and i'm sure there are points for improvement. You can tweak it yourself. Later you can delete the part1, part2 and part3 from the view. (but leave it in the order by) It's only to show how the sort is done.
DROP PROCEDURE IF EXISTS `uGetParts`//
DROP FUNCTION IF EXISTS `uExtractPart1`//
DROP FUNCTION IF EXISTS `uExtractPart2`//
DROP FUNCTION IF EXISTS `uExtractPart3`//
CREATE PROCEDURE `uGetParts`(
IN ins varchar(50),
OUT num1 int unsigned,
OUT num2 int unsigned,
OUT num3 int unsigned)
NO SQL
BEGIN
SET num1=0;
SET num2=0;
SET num3=0;
WHILE (num1<length(ins)) AND
(SUBSTRING(ins,num1+1,1) REGEXP('(^[0-9]+$)')=1) DO
SET num1=num1+1;
END WHILE;
SET num2=num1;
WHILE (num2<length(ins)) AND
(SUBSTRING(ins,num2+1,1) REGEXP('(^[0-9]+$)')=0) DO
SET num2=num2+1;
END WHILE;
SET num3=num2;
WHILE (num3<length(ins)) AND
(SUBSTRING(ins,num3+1,1) REGEXP('(^[0-9]+$)')=1) DO
SET num3=num3+1;
END WHILE;
END//
CREATE FUNCTION `uExtractPart1`(ins varchar(50))
RETURNS int unsigned NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num1>0 THEN
RETURN CAST(SUBSTRING(ins,1,num1) AS UNSIGNED);
ELSE
RETURN ~0 >> 32;
END IF;
END//
CREATE FUNCTION `uExtractPart2`(ins varchar(50))
RETURNS varchar(50) NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num2>num1 THEN
RETURN SUBSTRING(ins,num1+1,num2-num1);
ELSE
RETURN '';
END IF;
END//
CREATE FUNCTION `uExtractPart3`(ins varchar(50))
RETURNS int unsigned NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num3>num2 THEN
RETURN CAST(SUBSTRING(ins,num2+1,num3-num2) AS UNSIGNED);
ELSE
RETURN 0;
END IF;
END//
You can call it like this:
SELECT
id,
TYPE,
uExtractPart1(TYPE) as part1,
uExtractPart2(TYPE) as part2,
uExtractPart3(TYPE) as part3
FROM Article
ORDER BY
uExtractPart1(TYPE),
uExtractPart2(TYPE),
uExtractPart3(TYPE)

Possible to order an SQL query that matches with a REGEX by the number of matches found?

I am using a sql query such as WHERE name REGEXP '[[:<:]]something[[:>:]]'.
Now this all works great but my results are not ordered by number of matches found which is what I am looking for. Any ideas on how to go about doing this or if it is even possible?
Thanks
Full Query is
SELECT `Item`.`id`, `Item`.`name`, `Item`.`short_bio`
FROM `items` AS `Item`
WHERE ((`Item`.`name` REGEXP '[[:<:]]hello[[:>:]]') OR
(`Item`.`name` REGEXP '[[:<:]]world[[:>:]]')
Now this query is generated based on user input, each space breaks the thing into a different part that is searched for. I would like to order the results based on the number of matches of all parts, this way the most relevant results are on the top.
How about something like this (don't know mysql, so it may need tweaking):
SELECT `Item`.`id`, `Item`.`name`, `Item`.`short_bio`
FROM `items` AS `Item`
WHERE ((`Item`.`name` REGEXP '[[:<:]]hello[[:>:]]') OR
(`Item`.`name` REGEXP '[[:<:]]world[[:>:]]')
ORDER BY (`Item`.`name` REGEXP '[[:<:]]hello[[:>:]]') +
(`Item`.`name` REGEXP '[[:<:]]world[[:>:]]') DESC
I found an UDF some time ago to do this. I'm really sorry I can't cite the source though.
DELIMITER //
CREATE DEFINER=`root`#`localhost` FUNCTION `substrCount`(s VARCHAR(255), ss VARCHAR(255)) RETURNS tinyint(3) unsigned
READS SQL DATA
BEGIN
DECLARE count TINYINT(3) UNSIGNED;
DECLARE offset TINYINT(3) UNSIGNED;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET s = NULL;
SET count = 0;
SET offset = 1;
REPEAT
IF NOT ISNULL(s) AND offset > 0 THEN
SET offset = LOCATE(ss, s, offset);
IF offset > 0 THEN
SET count = count + 1;
SET offset = offset + 1;
END IF;
END IF;
UNTIL ISNULL(s) OR offset = 0 END REPEAT;
RETURN count;
END
DELIMITER ;
There's also a nifty solution found here.
Regex matching operators in MySQL return either 1 or 0 depending on whether the match was found or not respectively (or null if either a pattern or string is null). No information about number of matches is available, so sorting is not possible either.

Categories