I have two database tables customers
which contains data about customers with the scheme like that:
mysql> SELECT * FROM customers; customer_id created_at partner_id 1 "2019-08-20 09:17:58" cats 2 "2019-09-12 11:46:37" dogs
and customers_facts
which keeps the customers facts in a form of fact_name
and corresponding fact_value
.
mysql> SELECT * FROM customers_facts; customer_id fact_name fact_value 1, name Milton 1 city Milan 2 surname Bloom 2 name Orlando
I want to create a pivot table which in each row will have a customer and it’s facts each as a separate column. Something like this:
mysql> SELECT * FROM pivot_table; customer_id created_at partner_id name city surname 1 "2019-08-20 09:17:58" cats Milton Milan 2 "2019-09-12 11:46:37" dogs Orlando Bloom
I’ve found a script that allows me to create such table:
SET @sql = ''; SELECT @sql := CONCAT(@sql,if(@sql='','',', '),temp.output) FROM ( SELECT DISTINCT CONCAT( 'MAX(IF(cf.fact_name = ''', fact_name, ''', cf.fact_value, NULL)) AS ''', fact_name, '''' ) as output FROM customers_facts ) as temp; SET @sql = CONCAT('SELECT c.customer_id, c.created_at, c.partner_id, ', @sql, ' FROM customers c LEFT JOIN customers_facts AS cf ON cf.customer_id = c.customer_id GROUP BY c.customer_id, c.created_at, c.partner_id');
but I have an issue of how to make it so:
a) I will be able to query the pivot table
b) When I add a new entry / update an old one in one of those two original tables the pivot table will be updated
How to solve ? Is it possible ?
Advertisement
Answer
Consider the following:
DROP TABLE IF EXISTS customers; CREATE TABLE customers (customer_id SERIAL PRIMARY KEY ,created_at DATETIME NOT NULL ,partner_id INT NOT NULL ); INSERT INTO customers VALUES (1,"2019-08-20 09:17:58",108), (2,"2019-09-12 11:46:37",110); DROP TABLE IF EXISTS customers_facts ; CREATE TABLE customers_facts (customer_id INT NOT NULL ,fact_name VARCHAR(20) NOT NULL ,fact_value VARCHaR(20) NOT NULL ,PRIMARY KEY(customer_id,fact_name) ); INSERT INTO customers_facts VALUES (1,'name','Milton'), (1,'city','Milan'), (2,'surname','Bloom'), (2,'name','Orlando');
Now we can create a VIEW in the manner you describe…
DROP VIEW IF EXISTS my_pivot; CREATE VIEW my_pivot AS SELECT c.customer_id , c.created_at , c.partner_id , MAX(CASE WHEN fact_name = 'name' THEN fact_value END) name , MAX(CASE WHEN fact_name = 'surname' THEN fact_value END) surname , MAX(CASE WHEN fact_name = 'city' THEN fact_value END) city FROM customers c LEFT JOIN customers_facts f ON f.customer_id = c.customer_id GROUP BY c.customer_id;
We can interrogate this VIEW with a simple query – e.g. SELECT customer_id FROM my_pivot WHERE name = 'Milton'
, however, this cannot use an index, so it’s not very efficient.
Also, because of the way in which we created the VIEW, it cannot be updated…
UPDATE my_pivot SET name = 'Leonardo' WHERE customer_id = 1; ERROR 1288 (HY000): The target table my_pivot of the UPDATE is not updatable
However, had we created the VIEW slightly differently, then it could be updated…
DROP VIEW IF EXISTS my_pivot; CREATE VIEW my_pivot AS SELECT c.customer_id , c.created_at , c.partner_id , name.fact_value name , surname.fact_value surname , city.fact_value city FROM customers c LEFT JOIN customers_facts name ON name.customer_id = c.customer_id AND name.fact_name = 'name' LEFT JOIN customers_facts surname ON surname.customer_id = c.customer_id AND surname.fact_name = 'surname' LEFT JOIN customers_facts city ON city.customer_id = c.customer_id AND city.fact_name = 'city'; UPDATE my_pivot SET name = 'Leonardo' WHERE customer_id = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 SELECT * FROM customers_facts; +-------------+-----------+------------+ | customer_id | fact_name | fact_value | +-------------+-----------+------------+ | 1 | city | Milan | | 1 | name | Leonardo | | 2 | name | Orlando | | 2 | surname | Bloom | +-------------+-----------+------------+
…but this still cannot use an index.
EDIT: To answer the question asked in comments below your question, you can do…
SELECT customer_id FROM customers_facts WHERE ( fact_name,fact_value ) IN (('name','Orlando'),('surname','Bloom')) GROUP BY customer_id HAVING COUNT(*) = 2;
…although I think MySQL can’t use an index in this instance, so the longhand version might be better…
SELECT customer_id FROM customers_facts WHERE ( fact_name = 'name' AND fact_value = 'Orlando' ) OR ( fact_name = 'surname' AND fact_value = 'Bloom' ) GROUP BY customer_id HAVING COUNT(*) = 2;