MySQL Closure table: Tree rendering when using GUID instead of auto-increment - mysql

I’ve implemented a closure table system in MySQL for a hierarchy group list.
The groups are in table company_groups with columns ID and Name
The closure table is company_groups_treepaths:
CREATE TABLE `company_groups` (
`id` char(36) NOT NULL default '',
`name` varchar(150) NOT NULL default '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `company_groups_treepaths` (
`ParentID` char(36) NOT NULL default '',
`ChildID` char(36) NOT NULL default '',
`PathLength` int(11) NOT NULL default '0',
PRIMARY KEY (`ParentID`,`ChildID`),
KEY `PathLength` (`PathLength`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
And then I am trying to get a tree structure out of it. The problem is that most of the solutions I find is using group_concat on the group id, assuming it’s an INT and auto_increment.
However, I use GUID which makes it harder. I’ve looked through the other examples here, but can’t really get a hang of it.
For example, this query retrieves the right groups, but the wrong tree:
SELECT SQL_CALC_FOUND_ROWS p.`ChildID`, p.ParentID, d.name, CONCAT(REPEAT('-', p.`PathLength`), d.`name`) as path, p.`PathLength` as depth
FROM
`company_groups` AS d
JOIN `company_groups_treepaths` AS p ON d.`id` = p.`ChildID`
JOIN `company_groups_treepaths` AS crumbs ON crumbs.`ChildID` = p.`ChildID`
WHERE
p.`ParentID` = 'aa420c70-7050-11e2-b75d-672efc30777e'
GROUP BY d.id
ORDER BY GROUP_CONCAT(crumbs.`PathLength`)
SQL Fiddle here: http://sqlfiddle.com/#!2/474d4/2
The correct order for that query should be (fetching all children of Swedbank):
Swedbank (aa420c70-7050-11e2-b75d-672efc30777e)
hejsan (44b2b680-7f44-11e2-b04d-918fe8c8d065)
Östergötland (aa420970-7050-11e2-893a-7f63b55a76db)
Regional1 (a6adc800-7050-11e2-9db0-ad8ff41db08c)
asd (56fd15a0-7f44-11e2-b10f-55240ef76c28)
hejsan3 (fc14c320-7f44-11e2-a2bb-ed51f02fd80f)
Under öster (bb6b93a0-80ea-11e2-be1d-fd97d33aad97)
Småland (ae5dc150-7050-11e2-9b11-c96b3591816c)
asdasd (534e3f00-80df-11e2-b92e-fd29e414f3fd)
asd (6e640160-80de-11e2-8c41-d135d36c28db)
hejsan2 (d95a7060-80be-11e2-8179-0b9231964800)
Anyone got any good ideas for tree listing, using GUID?
The function itself won't be called very very often, so I'm fairly open for sub-query suggestions as well if it's necessary to solve the problem.

I reverted to trying out the basics, following outlines found on http://karwin.blogspot.se/2010/03/rendering-trees-with-closure-tables.html
This is the query that eventually worked:
select group_concat(n.name order by a.PathLength desc separator ' -> ') as fullpath, CONCAT(REPEAT('-', d.`PathLength`), cg.`name`) as path, d.ChildID as group_id, d.PathLength as depth, cg.name
from company_groups_treepaths d
join company_groups_treepaths a on (a.ChildID = d.ChildID)
join company_groups n on (n.id = a.ParentId)
join company_groups cg on (cg.id = d.ChildID)
where d.ParentID = 'aa420c70-7050-11e2-b75d-672efc30777e' and cg.deleted = 0
group by d.ChildID
order by fullpath

Related

Can we use FIND_IN_SET() function for multiple column in same table

NOTE : I tried many SF solution, but none work for me. This is bit challenging for, any help will be appreciated.
Below is my SQL-Fiddle link : http://sqlfiddle.com/#!9/6daa20/9
I have tables below:
CREATE TABLE `tbl_pay_chat` (
nId int(11) NOT NULL AUTO_INCREMENT,
npayid int(11) NOT NULL,
nSender int(11) NOT NULL,
nTos varchar(255) binary DEFAULT NULL,
nCcs varchar(255) binary DEFAULT NULL,
sMailBody varchar(500) binary DEFAULT NULL,
PRIMARY KEY (nId)
)
ENGINE = INNODB,
CHARACTER SET utf8,
COLLATE utf8_bin;
INSERT INTO tbl_pay_chat
(nId,npayid,nSender,nTos,nCcs,sMailBody)
VALUES
(0,1,66,'3,10','98,133,10053','Hi this test maail'),
(0,1,66,'3,10','98,133,10053','test mail received');
_____________________________________________________________
CREATE TABLE `tbl_emp` (
empid int(11) NOT NULL,
fullname varchar(45) NOT NULL,
PRIMARY KEY (empid)
)
ENGINE = INNODB,
CHARACTER SET utf8,
COLLATE utf8_bin;
INSERT INTO `tbl_emp` (empid,fullname)
VALUES
(3, 'Rio'),
(10, 'Christ'),
(66, 'Jack'),
(98, 'Jude'),
(133, 'Mike'),
(10053, 'James');
What I want :
JOIN above two tables to get fullname in (nTos & nCcs) columns.
Also, I want total COUNT() of rows.
What I tried is below query but getting multiples time FULLNAME in 'nTos and nCcs column' also please suggest to find proper number of row count.
SELECT a.nId, a.npayid, e1.fullname AS nSender, sMailBody, GROUP_CONCAT(b.fullname ORDER BY b.empid)
AS nTos, GROUP_CONCAT(e.fullname ORDER BY e.empid) AS nCcs
FROM tbl_pay_chat a
INNER JOIN tbl_emp b
ON FIND_IN_SET(b.empid, a.nTos) > 0
INNER JOIN tbl_emp e
ON FIND_IN_SET(e.empid, a.nCcs) > 0
JOIN tbl_emp e1
ON e1.empid = a.nSender
GROUP BY a.nId ORDER BY a.nId DESC;
I hope I made my point clear. Please help.
You have a horrible data model. You should not be storing lists of ids in strings. Why? Here are some reasons:
Numbers should be stored as numbers not strings.
Relationships between tables should be declared using foreign key relationships.
SQL has pretty poor string manipulation capabilities.
The use of functions and type conversion in ON often prevents the use of indexes.
No doubt there are other good reasons. Your data model should be using properly declared junction tables for the n-m relationships.
That said, sometimes we are stuck with other people's really, really, really, really bad design decisions. There are some ways around this. I think the query that you want can be expressed as:
SELECT pc.nId, pc.npayid, s_e.fullname AS nSender, pc.sMailBody,
GROUP_CONCAT(DISTINCT to_e.fullname ORDER BY to_e.empid)
AS nTos,
GROUP_CONCAT(DISTINCT cc_e.fullname ORDER BY cc_e.empid) AS nCcs
FROM tbl_pay_chat pc INNER JOIN
tbl_emp to_e
ON FIND_IN_SET(to_e.empid, pc.nTos) > 0 INNER JOIN
tbl_emp cc_e
ON FIND_IN_SET(cc_e.empid, pc.nCcs) > 0 JOIN
tbl_emp s_e
ON s_e.empid = pc.nSender
GROUP BY pc.nId
ORDER BY pc.nId DESC;
Here is a db<>fiddle.

Query results take too long. Is there a better way to write this MySQL query?

I am trying to optimize a mysql query that works perfectly but is taking way too long. My inventory table is nearly 300,000 records (not too bad). I am not sure if using a subquery or join or additional index would speed up my results. I do have the district_id columns indexed in both the students and inventory tables.
Basically, the query below pulls all the inventory of all students in a teacher's roster. So it first has to search the students table to find which students are in the teacher's roster, then has to search the inventory table for each student. So if a teacher has 30+ students it can be a lot of searches through the inventory and each student can have 30+ pieces of inventory. Any advice would be helpful!
SELECT inventory.inventory_id, items.title, items.isbn, items.item_num,
items.price, conditions.condition_name, inventory.check_out,
inventory.check_in, inventory.student_id, inventory.teacher_id
FROM inventory, conditions, items, students
WHERE students.teacher_id = '$teacher_id'
AND students.district_id = $district_id
AND inventory.student_id = students.s_number
AND inventory.district_id = $district_id
AND inventory.item_id = items.item_id
AND items.consumable !=1
AND conditions.condition_id = inventory.condition_id
ORDER BY inventory.student_id, inventory.inventory_id
Here is the table structure:
CREATE TABLE `inventory` (
`id` int(11) NOT NULL,
`inventory_id` varchar(10) CHARACTER SET utf8 NOT NULL DEFAULT '0',
`item_id` int(6) NOT NULL DEFAULT '0',
`district_id` int(2) NOT NULL DEFAULT '0',
`condition_id` int(1) NOT NULL DEFAULT '0',
`check_out` date NOT NULL DEFAULT '0000-00-00',
`check_in` date NOT NULL DEFAULT '0000-00-00',
`student_id` varchar(10) CHARACTER SET utf8 NOT NULL DEFAULT '0',
`teacher_id` varchar(6) CHARACTER SET utf8 NOT NULL DEFAULT '0',
`acquisition_date` date NOT NULL DEFAULT '0000-00-00',
`notes` text CHARACTER SET utf8 NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
First you rewrite this to use explicit JOINs:
SELECT inventory.inventory_id,
items.title, items.isbn, items.item_num, items.price,
conditions.condition_name,
inventory.check_out, inventory.check_in,
inventory.student_id, inventory.teacher_id
FROM inventory
JOIN conditions ON (conditions.condition_id = inventory.condition_id)
JOIN items ON (inventory.item_id = items.item_id AND items.consumable != 1)
JOIN students ON (inventory.student_id = students.s_number)
WHERE students.teacher_id = '$teacher_id'
AND students.district_id = $district_id
AND inventory.district_id = $district_id
ORDER BY inventory.student_id, inventory.inventory_id
Then you examine the JOINs. For example this:
JOIN items ON (inventory.item_id = items.item_id AND items.consumable != 1)
means that the items table needs to be scanned on item_id and consumable, which might be a constant. It is always better to not use negative conditions if possible. But at the very least you index items on item_id (unless it's already the primary key, as is likely). If consumable can assume, say, values 0, 1, 2, 3, then you go:
JOIN items ON (inventory.item_id = items.item_id AND items.consumable IN (0, 2, 3))
and use CREATE INDEX to add an index on consumable.
You may notice that a few columns from inventory are always used in the other JOINs, and there are also some constant constraints.
So another useful index could be
CREATE INDEX ... ON inventory(district_id, student_id, item_id, condition_id)
Another useful index would be
ON students(teacher_id, district_id, student_id, s_number)
which allows immediately restricting the WHERE on the involved students, and retrieve the information required by the JOINs without ever loading the table, just using the index.
Switch to InnoDB! Some of what I am about to say is less efficient in InnoDB.
SELECT i.inventory_id,
items.title, items.isbn, items.item_num, items.price,
c.condition_name,
i.check_out, i.check_in, i.student_id, i.teacher_id
FROM inventory AS i
JOIN conditions AS c ON c.condition_id = i.condition_id
JOIN items ON i.item_id = items.item_id
JOIN students AS s ON i.student_id = s.s_number
WHERE s.teacher_id = '$teacher_id'
AND s.district_id = $district_id
AND i.student_id = s.s_number
AND i.district_id = $district_id
AND items.consumable != 1
ORDER BY i.student_id, i.inventory_id
To help the Optimizer if it would like to start with students:
students: INDEX(district_id, teacher_id, s_number)
Note: this is also "covering", thereby avoiding bouncing between index BTree and data BTree. (What is the PK of students? Please provide SHOW CREATE TABLE.)
If consuming the ORDER BY is better:
inventory: INDEX(district_id, student_id, inventory_id)
Also needed:
items: (item_id) -- probably already the PRIMARY KEY?
conditions: (condition_id) -- probably already the PRIMARY KEY?
Verify or add those 4 indexes. (The Optimizer will dynamically choose what to do.)

mysql group concat into multiple fields

I have a recipe table, called recipes. There is the IDRecipe field and other parameters of the recipe except the categories. Categories are multi dimensional, so I have another table that connects one to many with one recipe. It is called category table (table 1 below). As you will see below, one recipe can have multiple categories in multiple dimensions. So I have another table (table 2) that describes the categories and dimensions, also below:
-- Table 1
CREATE TABLE `recepti_kategorije` (
`IDRecipe` int(11) NOT NULL,
`IDdimenzija` int(11) NOT NULL,
`IDKategorija` int(11) NOT NULL,
KEY `Iskanje` (`IDdimenzija`,`IDKategorija`,`IDRecipe`) USING BTREE,
KEY `izvlecek_recept` (`IDdimenzija`,`IDRecipe`),
KEY `IDRecipe` (`IDRecept`,`IDdimenzija`,`IDKategorija`) USING BTREE,
KEY `kategorija` (`IDKategorija`,`IDdimenzija`,`IDRecipe`) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_slovenian_ci;
INSERT INTO `recepti_kategorije` VALUES
(1,1,1),
(1,1,2),
(1,2,3),
(1,3,2);
-- Table 2
CREATE TABLE `recipes_dimensions` (
`IDDimenzija` int(11) NOT NULL,
`IDKategorija` int(11) NOT NULL,
`Ime` char(50) COLLATE utf8_slovenian_ci NOT NULL,
KEY `IDDmenzija` (`IDDimenzija`,`IDKategorija`) USING BTREE,
KEY `IDKategorija` (`IDKategorija`,`IDDimenzija`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_slovenian_ci;
INSERT INTO `recipes_dimensions` VALUES
(1,1,'cheese'),
(1,2,'eggs'),
(1,3,'meat'),
(1,4,'vegetables'),
(2,1,'main dish'),
(2,2,'sweet'),
(2,3,'soup'),
(3,1,'summer'),
(3,2,'winter');
-- Table 3
CREATE TABLE `recepti_dimenzije_glavne` (
`IDDimenzija` int(11) NOT NULL,
`DimenzijaIme` char(50) COLLATE utf8_slovenian_ci DEFAULT NULL,
PRIMARY KEY (`IDDimenzija`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_slovenian_ci;
INSERT INTO `recepti_dimenzije_glavne` VALUES
(1,'ingredient'),
(2,'type'),
(3,'season');
Table 2 is the key table to find out the legend of each dimensions and each category.
So from this example we see that my recipe with ID1 has the tag: cheese and eggs from dimension 1 and is soup for winter season.
Now on my recipes page I need to get all this out to print the names of each dimension together with all the category names.
Ok, so there is another table, table 3, to get the names of the dimensions out:
Now what I need is a query that would get me at the same time for recipe ID=1 all the dimensions group concatenated with names, like:
ingredient: cheese, eggs | type: soup | season: winter
I tried doing a query for each of them in SELECT statement and it works, but I need 8 select queries (in total I have 8 dimensions, for the example I only wrote 3), my select query is:
SELECT
r.ID
(
SELECT
group_concat(ime SEPARATOR ', ')
FROM
recepti_kategorije rkat
JOIN recepti_dimenzije rd ON rd.IDKategorija = rkat.IDKategorija
AND rd.IDDimenzija = rkat.IDdimenzija
WHERE
rkat.IDRecipe = r.ID
AND rkat.IDDimenzija = 1
ORDER BY
ime ASC
) AS ingredient,
(
SELECT
group_concat(ime SEPARATOR ', ')
FROM
recepti_kategorije rkat
JOIN recepti_dimenzije rd ON rd.IDKategorija = rkat.IDKategorija
AND rd.IDDimenzija = rkat.IDdimenzija
WHERE
rkat.IDRecipe = r.ID
AND rkat.IDDimenzija = 2
ORDER BY
ime ASC
) AS type,
(
SELECT
group_concat(ime SEPARATOR ', ')
FROM
recepti_kategorije rkat
JOIN recepti_dimenzije rd ON rd.IDKategorija = rkat.IDKategorija
AND rd.IDDimenzija = rkat.IDdimenzija
WHERE
rkat.IDRecipe = r.ID
AND rkat.IDDimenzija = 3
ORDER BY
ime ASC
) AS season
FROM
recipes r
WHERE
r.ID = 1
That works, but it is somehow slow because the explain says it is searching like 6-8 rows each time and it is a long query and I don't get the names of the dimensions out because I need another join.
What would be optimal way to get all the dimensions separated into fields and concated with category names? I need to have this optimised as this is for one recipe presentation that happens each second, I can not fool around here. And whta indexes do I need so that this would be fast.
Something like below, not sure I typed the table/column names right or not, but should be easy to debug:
SELECT c.ID,GROUP_CONCAT(CONCAT(d.DimenzijaIme,': ',c.imes) SEPARATOR ' | ')
FROM (
SELECT
r.ID,rkat.IDDimenzija,
group_concat(rd.ime SEPARATOR ', ' ORDER BY rd.ime) AS imes
FROM recepti_kategorije rkat
JOIN recepti_dimenzije rd
ON rd.IDKategorija = rkat.IDKategorija
AND rd.IDDimenzija = rkat.IDdimenzija
INNER JOIN recipes r
ON r.ID=rkat.IDRecipe
GROUP BY r.ID,rkat.IDDimenzija) c
INNER JOIN recepti_dimenzije_glavne d
ON d.IDDimenzija=c.IDDimenzija
GROUP BY c.ID

SQL alternative to sub-query in FROM

I have a table containing user to user messages. A conversation has all messages between two users. I am trying to get a list of all the different conversations and display only the last message sent in the listing.
I am able to do this with a SQL sub-query in FROM.
CREATE TABLE `messages` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`from_user_id` bigint(20) DEFAULT NULL,
`to_user_id` bigint(20) DEFAULT NULL,
`type` smallint(6) NOT NULL,
`is_read` tinyint(1) NOT NULL,
`is_deleted` tinyint(1) NOT NULL,
`text` longtext COLLATE utf8_unicode_ci NOT NULL,
`heading` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`created_at_utc` datetime DEFAULT NULL,
`read_at_utc` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
);
SELECT * FROM
(SELECT * FROM `messages` WHERE TYPE = 1 AND
(from_user_id = 22 OR to_user_id = 22)
ORDER BY created_at_utc DESC
) tb
GROUP BY from_user_id, to_user_id;
SQL Fiddle:
http://www.sqlfiddle.com/#!2/845275/2
Is there a way to do this without a sub-query?
(writing a DQL which supports sub-queries only in 'IN')
You seem to be trying to get the last contents of messages to or from user 22 with type = 1. Your method is explicitly not guaranteed to work, because the extra columns (not in the group by) can come from arbitrary rows. As explained in the [documentation][1]:
MySQL extends the use of GROUP BY so that the select list can refer to
nonaggregated columns not named in the GROUP BY clause. This means
that the preceding query is legal in MySQL. You can use this feature
to get better performance by avoiding unnecessary column sorting and
grouping. However, this is useful primarily when all values in each
nonaggregated column not named in the GROUP BY are the same for each
group. The server is free to choose any value from each group, so
unless they are the same, the values chosen are indeterminate.
Furthermore, the selection of values from each group cannot be
influenced by adding an ORDER BY clause. Sorting of the result set
occurs after values have been chosen, and ORDER BY does not affect
which values within each group the server chooses.
The query that you want is more along the lines of this (assuming that you have an auto-incrementing id column for messages):
select m.*
from (select m.from_user_id, m.to_user_id, max(m.id) as max_id
from message m
where m.type = 1 and (m.from_user_id = 22 or m.to_user_id = 22)
) lm join
messages m
on lm.max_id = m.id;
Or this:
select m.*
from message m
where m.type = 1 and (m.from_user_id = 22 or m.to_user_id = 22) and
not exists (select 1
from messages m2
where m2.type = m.type and m2.from_user_id = m.from_user_id and
m2.to_user_id = m.to_user_id and
m2.created_at_utc > m.created_at_utc
);
For this latter query, an index on messages(type, from_user_id, to_user_id, created_at_utc) would help performance.
Since this is a rather specific type of data query which goes outside common ORM use cases, DQL isn't really fit for this - it's optimized for walking well-defined relationships.
For your case however Doctrine fully supports native SQL with result set mapping. Using a NativeQuery with ResultSetMapping like this you can easily use the subquery this problem requires, and still map the results on native Doctrine entities, allowing you to still profit from all caching, usability and performance advantages.
Samples found here.
If you mean to get all conversations and all their last messages, then a subquery is necessary.
SELECT a.* FROM messages a
INNER JOIN (
SELECT
MAX(created_at_utc) as max_created,
from_user_id,
to_user_id
FROM messages
GROUP BY from_user_id, to_user_id
) b ON a.created_at_utc = b.max_created
AND a.from_user_id = b.from_user_id
AND a.to_user_id = b.to_user_id
And you could append the where condition as you like.
THE SQL FIDDLE.
I don't think your original query was even doing this correctly. Not sure what the GROUP BY was being used for other than maybe try to only return a single (unpredictable) result.
Just add a limit clause:
SELECT * FROM `messages`
WHERE `type` = 1 AND
(`from_user_id` = 22 OR `to_user_id` = 22)
ORDER BY `created_at_utc` DESC
LIMIT 1
For optimum query performance you need indexes on the following fields:
type
from_user_id
to_user_id
created_at_utc

Using max function, group by and join

I have 3 tables:
CREATE TABLE IF NOT EXISTS `disksinfo` (
`idx` int(10) NOT NULL AUTO_INCREMENT,
`hostinfo_idx` int(10) DEFAULT NULL,
`id` char(30) DEFAULT NULL,
`name` char(30) DEFAULT NULL,
`size` bigint(20) DEFAULT NULL,
`freespace` bigint(20) DEFAULT NULL,
PRIMARY KEY (`idx`)
)
CREATE TABLE IF NOT EXISTS `hostinfo` (
`idx` int(10) NOT NULL AUTO_INCREMENT,
`host_idx` int(11) DEFAULT NULL,
`probetime` datetime DEFAULT NULL,
`processor_load` tinyint(4) DEFAULT NULL,
`memory_total` bigint(20) DEFAULT NULL,
`memory_free` bigint(20) DEFAULT NULL,
PRIMARY KEY (`idx`)
)
CREATE TABLE IF NOT EXISTS `hosts` (
`idx` int(10) NOT NULL AUTO_INCREMENT,
`name` char(30) DEFAULT '0',
PRIMARY KEY (`idx`)
)
Basicaly, hosts ist just fixed list of hostnames used in hostinfo table (hostinfo.host_idx = hosts.idx)
hostinfo is a table which is filled each few minutes with data from all hosts and in addition, for each hostinfo row at least one diskinfo row is created. Each diskinfo row contains informations about at least one disk (so, for some hosts there are 3-4 rows of diskinfo). diskinfo.hostinfo_idx = hostinfo.idx.
hostinfo.probetime is simply the time at which data snapshot was created.
What i want to perform now is to select last hostinfo (.probetime) for each particular distinct host (hostinfo.host_idx), while joing informations about disks (diskinfo table) and host names (hosts table)
I came with this:
SELECT hinfo.idx,
hinfo.host_idx,
hinfo.processor_load,
hinfo.memory_total,
hinfo.memory_free,
hnames.idx,
hnames.name,
disks.hostinfo_idx,
disks.id,
disks.name,
disks.size,
disks.freespace,
Max(hinfo.probetime)
FROM systeminfo.hostinfo AS hinfo
INNER JOIN systeminfo.hosts AS hnames
ON hnames.idx = hinfo.host_idx
INNER JOIN systeminfo.disksinfo AS disks
ON disks.hostinfo_idx = hinfo.idx
GROUP BY disks.id,
hnames.name
ORDER BY hnames.name,
disks.id
It seems to work! But, is it 100% correct? Is it optimal? Thanks for any tip!
It's not 100% correct, no.
Suppose you have this table:
x | y | z
-----------------
a b 1
a c 2
d e 1
d f 2
Now when you only group by x, the rows are collapsing and MySQL picks a random row from the collapsed ones. So you might get
x | y | z
-----------------
a b 2
d e 2
or this
x | y | z
-----------------
a c 2
d f 2
Or another combination, this is not determined. Each time you fire your query you might get a different result. The 2 in column z is always there, because of the MAX() function, but you won't necessarily get the corresponding row to it.
Other RDBMSs would actually do the same, but most forbid this by default (in can be forbidden in MySQL, too). You have two possibilities to fix this (actually there are more, but I'll restrict to two).
Either you put all columns you have in your SELECT clause which are not used in an aggregate function like SUM() or MAX() or whatever into the GROUP BY clause as well, like this:
SELECT hinfo.idx,
hinfo.host_idx,
hinfo.processor_load,
hinfo.memory_total,
hinfo.memory_free,
hnames.idx,
hnames.name,
disks.hostinfo_idx,
disks.id,
disks.name,
disks.size,
disks.freespace,
Max(hinfo.probetime)
FROM systeminfo.hostinfo AS hinfo
INNER JOIN systeminfo.hosts AS hnames
ON hnames.idx = hinfo.host_idx
INNER JOIN systeminfo.disksinfo AS disks
ON disks.hostinfo_idx = hinfo.idx
GROUP BY
hinfo.idx,
hinfo.host_idx,
hinfo.processor_load,
hinfo.memory_total,
hinfo.memory_free,
hnames.idx,
hnames.name,
disks.hostinfo_idx,
disks.id,
disks.name,
disks.size,
disks.freespace
ORDER BY hnames.name,
disks.id
Note that this query might get you a different result! I'm just focusing on the problem, that you might get wrong data to the row you think holds the MAX(hinfo.probetime).
Or you solve it like this (and this will get you what you want):
SELECT hinfo.idx,
hinfo.host_idx,
hinfo.processor_load,
hinfo.memory_total,
hinfo.memory_free,
hnames.idx,
hnames.name,
disks.hostinfo_idx,
disks.id,
disks.name,
disks.size,
disks.freespace,
hinfo.probetime
FROM systeminfo.hostinfo AS hinfo
INNER JOIN systeminfo.hosts AS hnames
ON hnames.idx = hinfo.host_idx
INNER JOIN systeminfo.disksinfo AS disks
ON disks.hostinfo_idx = hinfo.idx
WHERE hinfo.probetime = (SELECT MAX(probetime) FROM systeminfo.hostinfo AS hi
INNER JOIN systeminfo.hosts AS hn
ON hnames.idx = hinfo.host_idx
INNER JOIN systeminfo.disksinfo AS d
ON disks.hostinfo_idx = hinfo.idx
WHERE d.id = disks.id AND hn.name = hnames.name)
GROUP BY disks.id,
hnames.name
ORDER BY hnames.name,
disks.id
There's also a nice example in the manual about this: The Rows Holding the Group-wise Maximum of a Certain Column