value MEMBER OF (array) tests one element, JSON_CONTAINS(target, candidate) requires every candidate element, JSON_OVERLAPS(a, b) requires any shared element, and JSON_CONTAINS_PATH(doc, 'one'|'all', path, ...) tests for keys. The first three are the predicates a multi-valued index can serve (Indexing JSON). Mind the NULLs: a product without tags is not "not tagged php" but unknown.
SELECT sku, 'php' MEMBER OF (attributes->'$.tags') AS php,
JSON_CONTAINS(attributes->'$.tags', '["php", "web"]') AS both_,
JSON_OVERLAPS(attributes->'$.tags', '["sql", "web"]') AS either,
JSON_CONTAINS_PATH(attributes, 'one', '$.color', '$.count') AS merch,
JSON_LENGTH(attributes, '$.tags') AS n_tags, JSON_KEYS(attributes) AS `keys`
FROM products WHERE id IN (4, 5, 7);+-----------+------+-------+--------+-------+--------+-----------------------------+ | sku | php | both_ | either | merch | n_tags | keys | +-----------+------+-------+--------+-------+--------+-----------------------------+ | BK-LAR-01 | 1 | 1 | 1 | 0 | 4 | ["tags", "pages", "format"] | | BK-UX-01 | NULL | NULL | NULL | 0 | NULL | ["pages", "format"] | | AC-MUG-01 | NULL | NULL | NULL | 1 | NULL | ["color", "capacity_ml"] | +-----------+------+-------+--------+-------+--------+-----------------------------+
JSON_SEARCH(attributes, 'one', 'lin%') finds where a string sits: "$.tags[0]" on the Linux book. JSON_MERGE_PATCH() follows RFC 7396 (HTTP PATCH): later values win, arrays are replaced, and null deletes a key, so merging {"pages": 188, "tags": ["mysql"], "format": "ebook"} with {"pages": 196, "tags": ["sql"], "format": null} gave {"tags": ["sql"], "pages": 196}. JSON_MERGE_PRESERVE() keeps both sides, giving "pages": [188, 196]. The old JSON_MERGE() raises deprecation warning 1287.