ãä¾ãã°æ¬¡ã®ãããªãã¼ãã«ããã£ãã¨ããã
-- PostgreSQL CREATE TABLE history ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL, data TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- MySQL CREATE TABLE history ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, data TEXT, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); INSERT INTO history (user_id, data, created_at) VALUES (1, 'First entry of user1', '2024-01-01 10:00:00'), (1, 'Second entry of user1', '2024-01-02 09:30:00'), (2, 'First entry of user2', '2024-01-01 11:00:00'), (2, 'Second entry of user2', '2024-01-02 08:45:00'), (2, 'Third entry of user2', '2024-01-03 07:15:00'), (3, 'First entry of user3', '2024-01-01 12:15:00');
id | user_id | data | created_at |
---|---|---|---|
1 | 1 | First entry of user1 | 2024-01-01 10:00:00.000000 |
2 | 1 | Second entry of user1 | 2024-01-02 09:30:00.000000 |
3 | 2 | First entry of user2 | 2024-01-01 11:00:00.000000 |
4 | 2 | Second entry of user2 | 2024-01-02 08:45:00.000000 |
5 | 2 | Third entry of user2 | 2024-01-03 07:15:00.000000 |
6 | 3 | First entry of user3 | 2024-01-01 12:15:00.000000 |
ã対象ã¦ã¼ã¶1åã§åãåºãå ´åã¯æ¬¡ã®queryã§ããã
SELECT * FROM history WHERE user_id = 2 ORDER BY created_at DESC LIMIT 1;
id | user_id | data | created_at |
---|---|---|---|
5 | 2 | Third entry of user2 | 2024-01-03 07:15:00.000000 |
ãããããå ¨ä½ã§åå¾ããå ´åãuserãè¤æ°åå¾ãããå ´åã«ã«ã¼ãã§åå¾ããå¿ è¦ããããããN+1ã«ãªãã ãã®åé¡ã«å¯¾ãã解決çãæè¨ããã
Windowé¢æ°
ããã®ãããªå ´åãä¸è¬çã«ã¯Windowé¢æ°ã使ã£ã¦åå¾ãããã¨ã«ãªãã
-- MySQLã§ãPostgreSQLã§ãåã SELECT * FROM (SELECT history.*, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS ranking FROM history) latest WHERE ranking = 1;
id | user_id | data | created_at | ranking |
---|---|---|---|---|
2 | 1 | Second entry of user1 | 2024-01-02 09:30:00.000000 | 1 |
5 | 2 | Third entry of user2 | 2024-01-03 07:15:00.000000 | 1 |
6 | 3 | First entry of user3 | 2024-01-01 12:15:00.000000 | 1 |
ãWindowé¢æ°ã«ãã£ã¦userã®æ°ã ãN+1ã§ã«ã¼ãããå¿ è¦ããã£ãqueryããã¼ãã«ã¹ãã£ã³1åã§æ¸ãã
DISTINCT ONï¼PostgreSQLæ¡å¼µï¼
ãPostgreSQLã«ã¯ DISTINCT ON
ã¨ããæ¡å¼µæ§æãããã
ããã使ãã¨ããã·ã³ãã«ã«latestãªè¡ãåå¾ãããã¨ãã§ããã
以ä¸ã®SQLã¯Windowé¢æ°ã®ä¾ã¨å義ã®ã³ã¼ãã«ãªãã
SELECT DISTINCT ON (user_id) * FROM history ORDER BY user_id, created_at DESC;
id | user_id | data | created_at |
---|---|---|---|
2 | 1 | Second entry of user1 | 2024-01-02 09:30:00.000000 |
5 | 2 | Third entry of user2 | 2024-01-03 07:15:00.000000 |
6 | 3 | First entry of user3 | 2024-01-01 12:15:00.000000 |
ãDISTINCT ONã¯PostgreSQLã«ãããªãæ©è½ã§ãããããã³ãã¼ããã¯ã¤ã³ã®ãªã¹ã¯ããããã®ã®ãé常ã«ä½¿ããããå¼·åãªæ§æãªã®ã§è¦ãã¦ããã¦æã¯ãªãã ãã¡ããWindowé¢æ°ã¨åæ§ã«historyã®ãã¼ãã«ã¹ãã£ã³1åã¨ãªãã
historyãã¼ãã«ã®è¥å¤§ãåé¡ã«ãªã£ãã¨ã
ãhistoryãã¼ãã«ã¯å±¥æ´ãç©ã¿ä¸ãã¦ããã¨é常ã«å·¨å¤§ã«ãªãå¯è½æ§ã¯ããã
- ä¸è¦ã«ãªã£ãéå»ã®ãã¼ã¿ãåé¤ãã
- ãã¼ãã£ã·ã§ãã³ã°ãå©ç¨ããæ°´å¹³åå²ãè¡ã
- ææ°ã®æ å ±ãæã£ãlatestãã¼ãã«ãä½ã
ããã¼ã¿ã®åé¤ãã§ãããªãå®æçã«åé¤ããã»ããè¯ãã
ããã¦ãã¼ã¿ãåé¤ã§ããã®ã§ããã° created_at
ã§WHEREãåºæ¥ãã¯ãã ããINDEXãæ´»ç¨ããçµãè¾¼ã¿ãã§ããã¯ãã ã
å¤ãã®å ´åã¯ããã§è§£æ±ºããã®ã§æåªå
ã§æ¤è¨ããã
ãããã§ãä¸ååãªå ´åã«æ®ãã®2ã¤ãæ¤è¨ãããã¨ã
ãã¼ãã£ã·ã§ãã³ã°ãæ´»ç¨ãã
ãMySQLã«ãPostgreSQLã«ããã¼ãã£ã·ã§ãã³ã°ã®æ©è½ãããã å®ç¸¾ã®ããæ©è½ãªã®ã§èª¿ã¹ãã°æ å ±ã¯ããããåºã¦ããã PostgreSQLã®ä¾ã¨ãã¦å¯å£«éã®æ¸ãã¦ããããã¥ã¡ã³ããç´¹ä»ããã
ãä¾ãã°æãé±ãæ¥ãªã©ã®åä½ã§ãã¼ãã¼ã·ã§ã³ãä½æãããã¨ã§ãã¼ãã«ã¹ãã£ã³ã®å¯¾è±¡ãæ¸ãããã¨ã§ããã©ã¼ãã³ã¹åä¸ãçãã ãã ããã¢ã¯ãã£ãã§ã¯ãªãã¦ã¼ã¶ãªã©ãå¤ãhistoryãåç §ããå ´åã¯ããã©ã¼ãã³ã¹ã®å£åã«ãªãå ´åãããã
ããã¼ã¿ã¯åé¤ãããã¨ãã§ããªãããç´è¿ã®ã¢ã¯ãã£ãã¦ã¼ã¶ã®latestãªhistoryããåç §ããªãå ´åãªã©ã§ã¯å¹æçã§ããã
latestãã¼ãã«ãä½ã
ãå¤ãhistoryã®latestãå¿ è¦ãªå ´åããã¼ãã¼ã·ã§ã³ã®ã¹ãã£ã³ãå¿ è¦ã§çµå±ã¹ãã¼ã¯ã¨ãªã«ãªããã¨ãå¤ãã ããã§å¯¾çã¨ãã¦ã¯latestãã¼ãã«ãã¤ã¾ãè¨ç®çµæã®cacheãä½ãã
ãããã¯æ¬å½ã« æçµæ段 ã§ãããã¨ãè¦ãã¦ããã¦ã»ããã cacheã使ããªãã¦ãããªã使ããªãã¦è¯ãããã©ããã¦ãããã©ã¼ãã³ã¹ãæ¹åããªãå ´åã¯ä»¥ä¸ã®ããã°ãèªãã ããã§ç¶ããèªãã§ã»ããã
-- PostgreSQL CREATE TABLE latest_history ( user_id INTEGER PRIMARY KEY, history_id INTEGER NOT NULL, data TEXT, created_at TIMESTAMP NOT NULL ); -- MySQL CREATE TABLE latest_history ( user_id INT NOT NULL, history_id INT NOT NULL, data TEXT, created_at DATETIME NOT NULL, PRIMARY KEY (user_id) );
ããã®ãã¼ãã«ã®ä¾ã¯historyã¨INNER JOINãããã¨ã§ææ°ã®statusãåå¾ãããã¨ãã§ããã ã©ããã¦ãhistoryã¨ã®JOINãé¿ãããå ´åã¯latest_historyã«statusãªã©historyã®æ å ±ãéæ£è¦åãã¦æããã¦ãè¯ãã
UPSERTã使ã
ãå±¥æ´ãã¼ã¿ã«INSERTãããã¿ã¤ãã³ã°ã§latest_historyãã¼ãã«ãæ´æ°ããã user_idãunique keyã¨ãã¦ããã¼ã¿ãç¡ããã°INSERTãããã°UPDATEã¨ãããã¨ã§latestã®ç¶æ ãä¿æãããã¨ãã§ããã ãã®å¦çãUPSERTã¨å¼ã¶ã
ãUPSERTã¯MySQLã«ãPostgreSQLã«ãããããããããå°ãã¥ã¤éãã®ã§èªåã®ç°å¢ã«åããã¦ä¸è¨ã®æ§æã調ã¹ã¦ã»ããã
- MySQLã®å ´å㯠INSERT ON ... DUPLICATE KEY UPDATE æ§æ 㨠REPLACEæ§æ ããã
- INSERT ON DUPLICATE KEY UPDATE æ§æã¯INSERTã«å¤±æãããUPDATE
- REPLACEæ§æã¯DELETEãã¦INSERT
- PostgreSQLã®å ´å㯠INSERT INTO ... ON CONFLICTæ§æ 㨠MERGE INTO ... USING ... ON æ§æ ããã
- INSERT INTO ... ON CONFLICTæ§æã¯MySQLã¨åæ§ã«INSERTã«å¤±æããæã«UPDATE
- MERGEæã¯æ¿å
¥ãã¼ã¿ãæ¿å
¥å
ãã¼ãã«ã¨çµåãã¦ãéè¤ããã¨ãã®å¦çã¨éè¤ããªãã£ãã¨ãã®å¦çãæå®ã§ãã
- éè¤ããã¨ããUPDATEãéè¤ããªãã£ãã¨ãã«INSERTãæå®ããã°UPSERTã«ãªã
- MERGEæã¯PostgreSQL15ãã
ããããå±¥æ´ãã¼ãã«ã«ãã¼ã¿ãINSERTããéã«latestãã¼ãã«ã«é½åº¦è¡ããã¨ã§ææ°ã®ä¸ä»¶ã®ãã¼ã¿ãä¿æãããã¼ãã«ãä½ããã æ¨å¥¨æ¹æ³ã¨ãã¦ã¯ã¢ããªã±ã¼ã·ã§ã³å´ã§å±¥æ´ãã¼ã¿ã«INSERTããå¦çã®éã«åããã¦å®è¡ããã®ãè¯ãã
ããªã¬ã¼ã使ã
ãã©ããã¦ãã¢ããªã±ã¼ã·ã§ã³ãç´ããªããMySQLã¨PostgreSQLã®Transactionåé¢ã¬ãã«ã®éããªã©ã§*1ã§ãã¡ã³ãã ãªã¼ãã®åé¡ããã£ã¦ä¸å¯§ã«ããã¯ãã¨ã£ã¦ãäºææ§ãæ ä¿ã§ããªãã ãªã©ã®é常ã«ããä¸é¨ã®ç¹å®ã®å ´åã¯historyãã¼ãã«ã«INSERTæã®ã¤ãã³ãããªã¬ã¼ã§UPSERTãå®è¡ãããã¨ã§latest_historyãã¼ãã«ãä½æãããã¨ãã§ãã
ãããã¯æçµæ段ã®æçµæ段ãªã®ã§ç©æ¥µçã«æ´»ç¨ãããã¨ã¯ãªã¹ã¹ã¡ããªãããã¢ããªã±ã¼ã·ã§ã³ãå¤æ´ãããã¨ãªãlatestãä¿åãããã¨ãåºæ¥ãã¡ãªãããããã å®éã«ããã¦ã¼ã¹ã±ã¼ã¹ã¨ãã¦ã¯è¤æ°ã®ã¢ããªã±ã¼ã·ã§ã³ããhistoryãã¼ãã«ã«INSERTãå®è¡ããããããlatest_historyãä½ããã¨ãé£ããå ´åãªã©ã«æ´»ç¨ãããã
-- PostgreSQL -- ããªã¬ã¼é¢æ°å®ç¾© CREATE OR REPLACE FUNCTION upsert_latest_history() RETURNS TRIGGER AS $$ BEGIN INSERT INTO latest_history (user_id, history_id, data, created_at) VALUES (NEW.user_id, NEW.id, NEW.data, NEW.created_at) ON CONFLICT (user_id) DO UPDATE SET history_id = EXCLUDED.history_id, data = EXCLUDED.data, created_at = EXCLUDED.created_at; RETURN NEW; END; $$ LANGUAGE plpgsql; -- ããªã¬ã¼ä½æ CREATE TRIGGER trg_upsert_latest_history AFTER INSERT ON history FOR EACH ROW EXECUTE FUNCTION upsert_latest_history(); -- MySQL -- ããªã¬ã¼é¢æ°å®ç¾© DELIMITER // CREATE TRIGGER trg_upsert_latest_history AFTER INSERT ON history FOR EACH ROW BEGIN INSERT INTO latest_history (user_id, history_id, data, created_at) VALUES (NEW.user_id, NEW.id, NEW.data, NEW.created_at) ON DUPLICATE KEY UPDATE history_id = VALUES(history_id), data = VALUES(data), created_at = VALUES(created_at); END; // DELIMITER ;
-- PostgreSQL INSERT INTO public.history (id, user_id, data, created_at) VALUES (DEFAULT, 1, 'Force entry of user1', '2024-12-10 11:50:40.000000')
historyãã¼ãã«
id | user_id | data | created_at |
---|---|---|---|
1 | 1 | First entry of user1 | 2024-01-01 10:00:00.000000 |
2 | 1 | Second entry of user1 | 2024-01-02 09:30:00.000000 |
3 | 2 | First entry of user2 | 2024-01-01 11:00:00.000000 |
4 | 2 | Second entry of user2 | 2024-01-02 08:45:00.000000 |
5 | 2 | Third entry of user2 | 2024-01-03 07:15:00.000000 |
6 | 3 | First entry of user3 | 2024-01-01 12:15:00.000000 |
8 | 1 | Force entry of user1 | 2024-12-10 11:50:40.000000 |
latest_historyãã¼ãã«
user_id | history_id | data | created_at |
---|---|---|---|
1 | 8 | Force entry of user1 | 2024-12-10 11:50:40.000000 |
ã¾ã¨ã
ãåºæ¬çã«ã¯Windowé¢æ°ã使ãã°ãããSQLèªä½ã¯ChatGPTãªã©ã«DDLãè¦ãããã¦ã質åããã°æãã¦ãããã PostgreSQLã¦ã¼ã¶ã¯DISTINCT ONãè¦ãã¦ããã¨ã¡ãã£ã¨ããæã«å©ãããã¨ãããã
ããããç解ããä¸ã§ãã©ããã¦ãããã©ã¼ãã³ã¹ã«åé¡ãããã°ãããããã®ã¢ããã¼ããæ¤è¨ãããã¨ã
*1:isucon14ãªã©