Autor Zpráva
Kcko
Profil
Ahoj,
mám poměrně velkou tabulku (zatím 1 mio záznamů, ale bude to růst).

Potřebuji z toho udělat pivotní tabulku, tj součet hodnot pro uživatele per akce.
Tohle je první verze, ale je to pomalé (~2s).

Šlo by to udělat elegantněji? Mám k dispozici i MySQL 8.

Ještě ale potřebuji k součtům za akci doplnit i čas posledn akce.

Věděl by někdo?

SELECT user_id, (
  SUM(IF (action_type = 'login', 1, 0)) 
) AS 'login', (
  SUM(IF (action_type = 'publish', 1, 0)) 
) AS 'publish', (
  SUM(IF (action_type = 'download', 1, 0)) 
) AS 'download', (
  SUM(IF (action_type = 'upload', 1, 0)) 
) AS 'upload', (
  SUM(IF (action_type = 'comment', 1, 0)) 
) AS 'comment',(
  SUM(IF (action_type = 'share', 1, 0)) 
) AS 'share'

FROM ss_stats
GROUP BY user_id
HAVING user_id IS NOT NULL


CREATE TABLE `ss_stats` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `action_type` ENUM('login','view','download','share','publish','upload','comment') NOT NULL COLLATE 'utf8mb4_general_ci',
    `created` DATETIME NOT NULL,
    `ss_domain_id` INT(10) UNSIGNED NOT NULL,
    `user_id` INT(10) UNSIGNED NULL DEFAULT NULL,
    `ss_document_id` INT(10) UNSIGNED NULL DEFAULT NULL,
    `ss_share_id` INT(10) UNSIGNED NULL DEFAULT NULL,
    `ss_comment_id` INT(10) UNSIGNED NULL DEFAULT NULL,
    `file_id` INT(10) UNSIGNED NULL DEFAULT NULL,
    `hash` VARCHAR(32) NULL DEFAULT NULL COLLATE 'utf8mb4_general_ci',
    PRIMARY KEY (`id`) USING BTREE,
    INDEX `ss_stats_ss_domain_id_domain` (`ss_domain_id`) USING BTREE,
    INDEX `ss_stats_user_id_user` (`user_id`) USING BTREE,
    INDEX `ss_stats_ss_document_id_document` (`ss_document_id`) USING BTREE,
    INDEX `ss_stats_ss_share_id_ss_share` (`ss_share_id`) USING BTREE,
    INDEX `ss_start_ss_comment_id_comment` (`ss_comment_id`) USING BTREE,
    INDEX `ss_stats_file_id_file` (`file_id`) USING BTREE,
    INDEX `ss_stats_action_type_key` (`action_type`) USING BTREE,
    INDEX `ss_stats_action_type_user_id_key` (`action_type`, `user_id`) USING BTREE,
    INDEX `ss_stats_action_type_document_id_key` (`action_type`, `ss_document_id`) USING BTREE,
    INDEX `ss_stats_action_type_file_id_key` (`action_type`, `file_id`) USING BTREE,
    CONSTRAINT `ss_start_ss_comment_id_comment` FOREIGN KEY (`ss_comment_id`) REFERENCES `ss_comment` (`id`) ON UPDATE NO ACTION ON DELETE SET NULL,
    CONSTRAINT `ss_stats_ss_document_id_document` FOREIGN KEY (`ss_document_id`) REFERENCES `ss_document` (`id`) ON UPDATE NO ACTION ON DELETE SET NULL,
    CONSTRAINT `ss_stats_ss_domain_id_domain` FOREIGN KEY (`ss_domain_id`) REFERENCES `domain` (`id`) ON UPDATE NO ACTION ON DELETE RESTRICT,
    CONSTRAINT `ss_stats_ss_share_id_ss_share` FOREIGN KEY (`ss_share_id`) REFERENCES `ss_share` (`id`) ON UPDATE NO ACTION ON DELETE SET NULL,
    CONSTRAINT `ss_stats_file_id_file` FOREIGN KEY (`file_id`) REFERENCES `file` (`id`) ON UPDATE NO ACTION ON DELETE SET NULL,
    CONSTRAINT `ss_stats_user_id_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON UPDATE NO ACTION ON DELETE RESTRICT
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1008961
;
Keeehi
Profil
čas poslední akce je jednoduchý, něco ve stylu
  MAX(IF (action_type = 'login', created, 0)) 
) AS 'last_login',

Jinak agregeace se takto dělají, nemáš tam nic obskurního. Jestli se to dá zrychlit nevím, na to odborník nejsem. Já pracuji s řádově mnoha násobně většími tabulkami v cloudu kde se na to nasadí masivní paralelizace. Optimalizaci na jednom stroji jsem moc nedělal. Pokud je něco při selectu pomalé, většinou tam chyb indexy. Ale ty je tam máš. Co říká EXPLAIN?
Kcko
Profil
Keeehi:
Ahoj,
potřebuji to zatím zrychlit na 1 stroji, tohle nemůžu vypustit ven, pak se to asi rozloží, ale to není zatím otázka mě:

EXPLAIN:


ss_stats
---
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | 
| ---: | --- | --- | --- | --- | --- | --- | --- | --- | ---: | ---: | --- | 
| 1 | SIMPLE | ss_stats | \N | index | ss_stats_user_id_user,ss_stats_action_type_user_id_key | ss_stats_user_id_user | 5 | \N | 978004 | 100,00 | \N | 



Jdu změnit strukturu, myslím, že to MySQL líp neumí, pivotní tabulky dělají všichni stejně +/-
Kajman
Profil
Je toto rychlejší?
SELECT user_id,
       ( Sum(IF (action_type = 'login', pocet, 0)) )    AS 'login',
       ( Sum(IF (action_type = 'publish', pocet, 0)) )  AS 'publish',
       ( Sum(IF (action_type = 'download', pocet, 0)) ) AS 'download',
       ( Sum(IF (action_type = 'upload', pocet, 0)) )   AS 'upload',
       ( Sum(IF (action_type = 'comment', pocet, 0)) )  AS 'comment',
       ( Sum(IF (action_type = 'share', pocet, 0)) )    AS 'share'
FROM   (SELECT user_id,
               action_type,
               Count(*) pocet
        FROM   ss_stats
        GROUP  BY action_type,
                  user_id) pocty
WHERE  user_id IS NOT NULL
GROUP  BY user_id
Kcko
Profil
Kajman:
Ahoj,
je ale dotaz trvá cca 1s, což je pořád hodně.

Díky za ochotu.

Stejně si myslím, že bude lepší, jelikož mám pevný počet akcí, to přetavit do tabulky

... | login | publish | download ...
Kajman
Profil
A data si tam budeš měnit pomocí triggerů nad tou tabulkou ss_stats?
Kcko
Profil
Kajman:
Ty tam lezou klasicky přes Inserty z různých částí aplikace.
Kajman
Profil
Takže tam budeš mít stále milion řádků ale více sloupců. To rychlosti nepomůže. Pokud je ta statistika často potřeba, udělěj si druhou tabulku, kde si ty výpočty budeš držet. Pomocí triggerů můžeš zajistit, aby byly aktuální.

Jiné databáze umí materializované view - což je skoro to samé. Data předpočítána a uložena. Ale ty view se musí také refreshovat, aby tam byl aktuální stav.
Kcko
Profil
Kajman:
Uff. To nejde, tahle tabulka slouží pro ruzné výpisy statistiky (per user, per document, vstupují do toho pohledy za časová období atd ...)
S triggery popřemýšlím, díky (ale pořád je problém, že ta statistika bude mít X ruzných podob).
Kajman
Profil
Tu současnou tabulku přeci měnit nebudeš (takže jiné statistiky to nijak neovlivní), jen si uděláš druhou s předpočítanými daty, pokud potřebuješ rychlé statistiky pro všechny uživatele.
Kcko
Profil
Kajman:
Ano, ale já budu grupovat někdy podle dokumentu, jindy podle comment_id a může to být omezeno ještě třeba za rok a konkrétní měsíc atd.
Keeehi
Profil
Kcko:
Ano, ale statistika pro jeden dokument by měla být rychlejší protože řádků pro jeden dokument bude řádově méně. Takže nefiltrovanou statistiku můžeš brát z přepočítané tabulky a statistiku pro jednotlivé dokumenty pouštět live nad touto tabulkou.
Kcko
Profil
Keeehi:
Jasně, ale jeví se za tím hodně práce ;), myslel jsem, že to bude se správným indexem a lepším dotazem rychlejší (mnohem ...)
ttttt
Profil *
Není rychlejší to joinovat?
SELECT
  user_id,
  logins.count AS 'login', 
  publishes.count AS 'publish', 
  downloads.count AS 'download', 
    uploads.count AS 'upload', 
  "comments".count AS 'comment', 
  shares.count AS 'shares'
FROM 
  users 
  LEFT JOIN (SELECT user_id, COUNT(*) FROM ss_stats WHERE action_type = 'login' GROUP BY user_id) logins USING(user_id)
  LEFT JOIN (SELECT user_id, COUNT(*) FROM ss_stats WHERE action_type = 'publish' GROUP BY user_id) publishes USING(user_id)
  LEFT JOIN (SELECT user_id, COUNT(*) FROM ss_stats WHERE action_type = 'download' GROUP BY user_id) downloads USING(user_id)
  LEFT JOIN (SELECT user_id, COUNT(*) FROM ss_stats WHERE action_type = 'upload' GROUP BY user_id) uploads USING(user_id)
  LEFT JOIN (SELECT user_id, COUNT(*) FROM ss_stats WHERE action_type = 'comment' GROUP BY user_id) "comments" USING(user_id)
  LEFT JOIN (SELECT user_id, COUNT(*) FROM ss_stats WHERE action_type = 'share' GROUP BY user_id) shares USING(user_id)
Kcko
Profil
ttttt:
Pecka! Nad 1mio to trvá cca 0,2s :-)

Klobouček! :-)

a samozřejmě díky všem ostatním za snahu a další možné postupy.

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm:

0