178 | | - (cklein) [=#JBPM-approach] Why not implement all of the different `resource_prop*` tables into a single table, where each tuple has multiple attributes, see for example the JBPM datamodel for a working and presumably also fast approach. Here, there exists a process_variable or some similar table that stores all the different value types in single table. |
179 | | - (cboos) not sure how you see that as an advantage; each row will waste all the fields but one; there need to be one index for each type, each index having to deal with lots of NULL values, each update will have to rebuild all indexes, etc.). But it could be worth benchmarking anyway... |
180 | | The schema would be like so: |
| 177 | - (cklein) [=#JBPM-approach] Why not implement all of the different `resource_prop*` tables into a single table, where each tuple has multiple attributes, see for example the JBPM datamodel for a working and presumably also fast approach. Here, there exists a process_variable or some similar table that stores all the different value types in single table. \\ |
| 178 | The schema would be like so: |
| 239 | |
| 240 | |
| 241 | === Experiments |
| 242 | |
| 243 | The usual issue with EAV models is the relative complexity of making //queries//. |
| 244 | If we'd like to support something as flexible as our [/query custom query] module (and even more, as we currently don't support multi-valued fields, needed in particular for TracDev/Proposals/TicketLinks), we need to find an efficient way, which doesn't require too many joins. |
| 245 | |
| 246 | One idea is to combine two queries, the first to get the ids of the matching objects, the second to retrieve the desired columns. |
| 247 | |
| 248 | The two queries could be combined in a single SQL statement, or the set of ids could be first used to see if there is already some corresponding data present in memory that could be reused. Only the remaining ids would then be used for looking up the remaining values (as "full" objects). |
| 249 | |
| 250 | Example: looking for the ticket number, summary, priority and blocked-by values for all tickets having a priority lower than 3 and "test" in their summary or as a keyword. |
| 251 | |
| 252 | With the single table approach: |
| 253 | {{{#!sql |
| 254 | CREATE TABLE ticket_prop ( id bigint |
| 255 | , prop varchar(16) |
| 256 | , ival bigint |
| 257 | , tval text |
| 258 | , seq bigint |
| 259 | ); |
| 260 | |
| 261 | SELECT p.id, p.prop, p.ival, p.tval, p.seq |
| 262 | FROM (SELECT id FROM ticket_prop |
| 263 | WHERE prop = 'priority' AND ival < 3 |
| 264 | INTERSECT |
| 265 | SELECT id FROM |
| 266 | (SELECT id FROM ticket_prop |
| 267 | WHERE prop = 'summary' AND tval LIKE '%test%' |
| 268 | UNION |
| 269 | SELECT id FROM ticket_prop |
| 270 | WHERE prop = 'keyword' AND tval = 'test' ) |
| 271 | ) AS ids |
| 272 | LEFT JOIN ticket_prop p ON ids.id = p.id |
| 273 | WHERE p.prop in ('id', 'summary', 'priority', 'blocked-by') |
| 274 | ORDER BY p.id, p.prop, p.seq; |
| 275 | }}} |
| 276 | |
| 277 | With the multiple table approach: |
| 278 | {{{#!sql |
| 279 | CREATE TABLE ticket_prop_int ( id bigint |
| 280 | , prop varchar(16) |
| 281 | , val bigint |
| 282 | , seq bigint |
| 283 | ); |
| 284 | |
| 285 | CREATE TABLE ticket_prop_text ( id bigint |
| 286 | , prop varchar(16) |
| 287 | , val text |
| 288 | , seq bigint |
| 289 | ); |
| 290 | |
| 291 | SELECT p.id as id, p.prop as prop, NULL as ival, p.val as tval, p.seq as seq |
| 292 | FROM (SELECT id FROM ticket_prop_int |
| 293 | WHERE prop = 'priority' AND val < 3 |
| 294 | INTERSECT |
| 295 | SELECT id FROM |
| 296 | (SELECT id FROM ticket_prop_text |
| 297 | WHERE prop = 'summary' AND val LIKE '%test%' |
| 298 | UNION |
| 299 | SELECT id FROM ticket_prop_text |
| 300 | WHERE prop = 'keyword' AND val = 'test' ) |
| 301 | ) AS ids |
| 302 | LEFT JOIN ticket_prop_text p ON ids.id = p.id |
| 303 | WHERE p.prop = 'summary' |
| 304 | UNION |
| 305 | SELECT p.id as id, p.prop as prop, p.val as ival, NULL as tval, p.seq as seq |
| 306 | FROM (SELECT id FROM ticket_prop_int |
| 307 | WHERE prop = 'priority' AND val < 3 |
| 308 | INTERSECT |
| 309 | SELECT id FROM |
| 310 | (SELECT id FROM ticket_prop_text |
| 311 | WHERE prop = 'summary' AND val LIKE '%test%' |
| 312 | UNION |
| 313 | SELECT id FROM ticket_prop_text |
| 314 | WHERE prop = 'keyword' AND val = 'test' ) |
| 315 | ) AS ids |
| 316 | LEFT JOIN ticket_prop_int p ON ids.id = p.id |
| 317 | WHERE p.prop in ('id', 'priority', 'blocked-by') |
| 318 | ORDER BY id, prop, seq; |
| 319 | }}} |
| 320 | Ok, maybe there's some clever way to reuse the `ids`, but I couldn't find one. Hope the SQL backend is smart enough to detect it's the same subquery ;-) |