MySQL unisciti sottoquerie
-
28-10-2019 - |
Domanda
Ho le seguenti tabelle:
CREATE TABLE `data` (
`date_time` decimal(26,6) NOT NULL,
`channel_id` mediumint(8) unsigned NOT NULL,
`value` varchar(40) DEFAULT NULL,
`status` tinyint(3) unsigned DEFAULT NULL,
`connected` tinyint(1) unsigned NOT NULL,
PRIMARY KEY (`channel_id`,`date_time`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `channels` (
`channel_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`channel_name` varchar(40) NOT NULL,
PRIMARY KEY (`channel_id`),
UNIQUE KEY `channel_name` (`channel_name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Mi chiedevo se qualcuno potesse darmi qualche consiglio su come ottimizzare o riscrivere la seguente domanda:
SELECT channel_name, t0.date_time, t0.value, t0.status, t0.connected, t1.date_time, t1.value, t1.status, t1.connected FROM channels,
(SELECT MAX(date_time) AS date_time, channel_id, value, status, connected FROM data
WHERE date_time <= 1300818330
GROUP BY channel_id) AS t0
RIGHT JOIN
(SELECT MAX(date_time) AS date_time, channel_id, value, status, connected FROM data
WHERE date_time <= 1300818334
GROUP BY channel_id) AS t1
ON t0.channel_id = t1.channel_id
WHERE channels.channel_id = t1.channel_id
Fondamentalmente ottengo il valore, lo stato e i campi connessi per ciascun canale_name in due volte diversi. Poiché T0 è sempre <= T1, i campi potrebbero esistere per T1, ma non T0, e voglio che sia mostrato. Ecco perché sto usando il giusto join. Se non esiste per T1, allora non esiste per T0, quindi nessuna riga dovrebbe essere restituita.
Il problema sembra essere che dal momento che mi unisco alle domande secondarie, non è possibile utilizzare alcun indice? Ho provato a riscriverlo per fare prima un join su canale della tabella dei dati, ma sono milioni di righe.
Sarebbe anche bello poter aggiungere un campo booleano a ciascuna delle righe finali che è vera quando T0.Value = T1.Value & T0.Status = T1.Status & T0.Connected = T1.Connected.
La ringrazio molto per il vostro tempo.
Soluzione
È possibile ridurre le due sotto-domande a una
SELECT channel_id,
MAX(date_time) AS t1_date_time,
MAX(case when date_time <= {$p1} then date_time end) AS t0_date_time
FROM data
WHERE date_time <= {$p2}
GROUP BY channel_id
Il gruppo di è notoriamente fuorviante in Mysql. Immagina se hai avuto min () e max () nella stessa selezione, da quale riga dovrebbero provenire le colonne non gruppi? Una volta capito, vedrai perché non è deterministico.
Per ottenere le file T0 e T1 complete
SELECT x.channel_id,
t0.date_time, t0.value, t0.status, t0.connected,
t1.date_time, t1.value, t1.status, t1.connected
FROM (
SELECT channel_id,
MAX(date_time) AS t1_date_time,
MAX(case when date_time <= {$p1} then date_time end) AS t0_date_time
FROM data
WHERE date_time <= {$p2}
GROUP BY channel_id
) x
INNER JOIN data t1 on t1.channel_id = x.channel_id and t1.date_time = x.t1_date_time
LEFT JOIN data t0 on t0.channel_id = x.channel_id and t0.date_time = x.t0_date_time
E infine un join per ottenere il nome del canale
SELECT c.channel_name,
t0.date_time, t0.value, t0.status, t0.connected,
t1.date_time, t1.value, t1.status, t1.connected,
t0.value=t1.value AND t1.status=t0.status
AND t0.connected=t1.connected name_me
FROM (
SELECT channel_id,
MAX(date_time) AS t1_date_time,
MAX(case when date_time <= {$p1} then date_time end) AS t0_date_time
FROM data
WHERE date_time <= {$p2}
GROUP BY channel_id
) x
INNER JOIN channels c on c.channel_id = x.channel_id
INNER JOIN data t1 on t1.channel_id = x.channel_id and t1.date_time = x.t1_date_time
LEFT JOIN data t0 on t0.channel_id = x.channel_id and t0.date_time = x.t0_date_time
MODIFICARE
Per eseguire una rlike sul nome del canale, sembra abbastanza semplice da aggiungere una clausola dove alla fine della query c.channel_name
. Può tuttavia funzionare meglio filtrarlo nella sottoquery, utilizzando la funzionalità MySQL dell'elaborazione di una notazione di virgola da sinistra a destra.
SELECT x.channel_name,
t0.date_time, t0.value, t0.status, t0.connected,
t1.date_time, t1.value, t1.status, t1.connected,
t0.value=t1.value AND t1.status=t0.status
AND t0.connected=t1.connected name_me
(
SELECT c.channel_id, c.channel_name,
MAX(d.date_time) AS t1_date_time,
MAX(case when d.date_time <= {$p1} then d.date_time end) AS t0_date_time
FROM channels c, data d
WHERE c.channel_name RLIKE {$expr}
AND c.channel_id = d.channel_id
AND d.date_time <= {$p2}
GROUP BY c.channel_id
) x
INNER JOIN data t1 on t1.channel_id = x.channel_id and t1.date_time = x.t1_date_time
LEFT JOIN data t0 on t0.channel_id = x.channel_id and t0.date_time = x.t0_date_time