我正在编写一个 PHP 包,我需要存储一组“文档”,每个文档都有自己的属性,这些属性在数量、名称和类型上可能有所不同,就像不同类型产品的属性可能不同一样(例如鞋子可能有 Material 、颜色和款式,但智能手机可能有操作系统、重量、尺寸等。)
| id | name |
|-----|------------|
| 1 | Acme Shoe |
| 2 | Acme Phone |
我希望能够按属性查询我的所有文档或产品。查询的范围可以是非常简单的 WHERE attribute_a = value_a
到更复杂的嵌套子句集,例如 WHERE ((attribute_a = value_a OR attribute_a > value_b) AND attribute_b LIKE '%pattern%')
我的理想场景是使用 MySQL 5.7+ 和 MariaDB 10.2+ 提供的 native JSON 支持来存储每个文档的属性,并使用方便的 JSON_EXTRACT
函数来提取我想要查询的任何属性。
| id | name | attributes |
|-----|------------|----------------------------------------|
| 1 | Acme Shoe | {"material":"canvas","color":"black"} |
| 2 | Acme Phone | {"os":"android","weight":100} |
SELECT *
FROM documents
WHERE (
JSON_EXTRACT(attributes, "$.weight") = 1
OR JSON_EXTRACT(attributes, "$.weight") > 99
)
AND JSON_EXTRACT(attributes, "$.os") LIKE '%droid%'
不幸的是,我的包需要能够支持旧版本的 MySQL 和 MariaDB。我曾考虑过将 JSON 存储在 TEXT 或 LONGTEXT 字段中,并使用 REGEX 解析出进行比较时所需的属性值,但我可以想象这将是令人难以置信的资源密集型且缓慢。如果我错了,请纠正我。
就目前情况而言,我觉得我只能选择 EAV 类型的解决方案:
| id | name |
|-----|------------|
| 1 | Acme Shoe |
| 2 | Acme Phone |
| id | document_id | key | value |
|-----|-------------|----------|---------|
| 1 | 1 | material | canvas |
| 2 | 1 | color | black |
| 3 | 2 | os | android |
| 4 | 2 | weight | 100 |
使用一个 WHERE 子句查找文档相对简单:
SELECT DISTINCT(document_id)
FROM document_attributes
WHERE key = 'material'
AND value = 'canvas'
但是,我不知道如何实现更复杂的 WHERE 子句。特别是,问题在于属性存储在单独的行中。例如
- 获取具有 Canvas Material 且颜色为黑色的文档。
- 获取具有 Android 操作系统且权重为 1 或大于 99 的文档。
如有任何意见或建议,我们将不胜感激。
<小时/>编辑
经过对 EAV 方法的一些考虑,到目前为止,我想出的最好办法是针对查询中涉及的每个属性重复将属性表连接到文档表。从那里,我可以在 WHERE 子句中使用每个属性的值。例如,选择属性“ Material ”为“ Canvas ”或“重量”大于 99 的所有产品:
SELECT d.id AS id, a1.value AS material, a2.value AS weight
FROM documents AS d
LEFT JOIN attributes AS a1 ON a1.document_id = d.id AND a1.name = 'material'
LEFT JOIN attributes AS a2 ON a2.document_id = d.id AND a2.name = 'weight'
WHERE a1.value = 'canvas'
AND a2.value > 99
这似乎产生:
| id | material | weight |
|----|----------|--------|
| 1 | canvas | NULL |
| 2 | NULL | 100 |
最佳答案
假设 document_id/key/value 组合是唯一的,您可以执行以下操作:
SELECT document_id FROM example
WHERE `key`='material' AND `value`='canvas'
OR `key`='color' AND `value`='black'
GROUP BY document_id
HAVING COUNT(*) = 2;
SELECT document_id FROM example
WHERE `key`='os' AND `value`='android'
OR (`key`='weight' AND (`value` = 1) OR (`value` > 99))
GROUP BY document_id
HAVING COUNT(*) = 2;
关于php - MySQL:旧版本 JSON 的替代方案/在一列中存储和查询多个值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44336049/