Reputation: 21081
I want to create a view with for a table in my database so I can use it in joins more easily. The table UserSettings
look like this:
mysql> desc UserSettings; -- cut the output for readability
+-----------+---------------------
| Field | Type
+-----------+---------------------
| userID | bigint(20) unsigned
| timestamp | timestamp
| settingID | text
| setting | text
+-----------+---------------------
4 rows in set (0.00 sec)
In it we store every update of a users settings. Now I want to create a view for this table with the latest settings for each user. So I want the view to contain the rows marked with *
:
mysql> select * from UserSettings;
+--------+---------------------+-----------------------+---------+
| userID | timestamp | settingID | setting |
+--------+---------------------+-----------------------+---------+
| 123 | 2014-04-16 12:08:30 | settings.warnings_off | 1 |
| 123 | 2014-04-16 12:18:56 | settings.warnings_off | 0 |
| 123 | 2014-04-17 06:42:08 | settings.warnings_off | 1 |
| 123 | 2014-04-17 06:49:08 | settings.warnings_off | 0 | *
| 911 | 2014-04-17 06:49:33 | settings.warnings_off | 1 | *
| 123 | 2014-04-17 10:04:12 | settings.test | fooo |
| 123 | 2014-04-17 10:04:22 | settings.test | bar | *
| 911 | 2014-04-17 10:07:40 | settings.test | bar | *
+--------+---------------------+-----------------------+---------+
8 rows in set (0.00 sec)
I can do this with a query containing a subquery:
mysql> SELECT us.*
-> FROM UserSettings us INNER JOIN (
-> SELECT userID, settingID, max(timestamp) as timestamp
-> from UserSettings
-> group by userID, settingID) ts ON ts.userID = us.userID AND
-> ts.settingID = us.settingID AND ts.timestamp = us.timestamp;
+--------+---------------------+-----------------------+---------+
| userID | timestamp | settingID | setting |
+--------+---------------------+-----------------------+---------+
| 123 | 2014-04-17 10:04:22 | settings.test | bar |
| 123 | 2014-04-17 06:49:08 | settings.warnings_off | 0 |
| 911 | 2014-04-17 10:07:40 | settings.test | bar |
| 911 | 2014-04-17 06:49:33 | settings.warnings_off | 1 |
+--------+---------------------+-----------------------+---------+
4 rows in set (0.00 sec)
However I get the error ERROR 1349 (HY000): View's SELECT contains a subquery in the FROM clause
when using the above query in CREATE VIEW CurrentUserSettings AS <THE QUERY ABOVE>
.
Is there a way to create a view of the UserSettings
table that show the same data as the query above? Perhaps using HAVING
in some way?
Upvotes: 1
Views: 83
Reputation: 7244
Like Kickstart advises, do like this
First, create the subquery in a view
CREATE VIEW subqueryview as
SELECT userID, settingID, max(timestamp) as timestamp
from UserSettings group by userID, settingID;
Then you can create the view using the view.
CREATE VIEW finalview AS
SELECT us.*
FROM UserSettings us
-- Here you now use the view created.
INNER JOIN subqueryview ts
ON ts.userID = us.userID
AND ts.settingID = us.settingID
AND ts.timestamp = us.timestamp;
Upvotes: 1
Reputation: 21513
To expand my comment above:-
CREATE VIEW latest_timestamp AS SELECT userID, settingID, max(timestamp) as timestamp
from UserSettings
group by userID, settingID;
CREATE VIEW latest_timestamp AS SELECT us.*
FROM UserSettings us
INNER JOIN latest_timestamp ts
ON ts.userID = us.userID AND
ts.settingID = us.settingID
AND ts.timestamp = us.timestamp;
Another option is to (ab)use the GROUP_CONCAT function. I am not that keen on doing this and there are issues if the fields can contain NULL, plus is entails converting integers to character fields.
Assuming the fields you want are called field1, field2 and field3 then:-
SELECT us.userID,
us.settingID,
SUBSTRING_INDEX(GROUP_CONCAT(us.field1 ORDER BY timestamp DESC SEPARATOR '|~|~'), '|~|~', 1),
SUBSTRING_INDEX(GROUP_CONCAT(us.field2 ORDER BY timestamp DESC SEPARATOR '|~|~'), '|~|~', 1),
SUBSTRING_INDEX(GROUP_CONCAT(us.field3 ORDER BY timestamp DESC SEPARATOR '|~|~'), '|~|~', 1)
FROM UserSettings us
GROUP BY us.userID, us.settingID;
This works by concatenating all the values for a field up into one, ordered by the timestamp descending and with a separator between them (something that will not be in any of the fields), then using SUBSTRING_INDEX to get the field up to the first delimiter.
Upvotes: 2