2016-05-26 35 views
8

Ho una tabella come questa:Come creare un limite dinamico in MySQL?

// notifications 
+----+--------------+------+---------+------------+ 
| id |  event | seen | id_user | time_stamp | 
+----+--------------+------+---------+------------+ 
| 1 | vote   | 1 | 123  | 1464174617 | 
| 2 | comment  | 1 | 456  | 1464174664 | 
| 3 | vote   | 1 | 123  | 1464174725 | 
| 4 | answer  | 1 | 123  | 1464174813 | 
| 5 | comment  | NULL | 456  | 1464174928 | 
| 6 | comment  | 1 | 123  | 1464175114 | 
| 7 | vote   | NULL | 456  | 1464175317 | 
| 8 | answer  | NULL | 123  | 1464175279 | 
| 9 | vote   | NULL | 123  | 1464176618 | 
+----+--------------+------+---------+------------+ 

sto cercando di selezionare almeno 15 righe per utente specifico. Appena ci sono due condizioni:

  1. sempre tutte le righe da leggere (seen = NULL) devono essere abbinate, anche se sono più di 15 righe.

  2. Se il numero di righe non lette è superiore a 15, è inoltre necessario selezionare 2 righe di lettura (seen = 1).


Esempi:read è il numero di righe di lettura e unread è il numero di righe da leggere nella notifications tavolo.

read | unread |   output should be   
------|--------|------------------------------------- 
3 | 8  | 11 rows        
12 | 5  | 15 rows (5 unread, 10 read)   
20 | 30  | 32 rows (30 unread, 2 read)   
10 | 0  | 10 rows (0 unread, 10 read)   
10 | 1  | 11 rows (1 unread, 10 read)   
10 | 6  | 15 rows (6 unread, 9 read)   
100 | 3  | 15 rows (3 unread, 12 read)   
3 | 100 | 102 rows (100 unread, 2 read)  

Ecco il mio query corrente, non supporta seconda condizione.

SELECT id, event, seen, time_stamp 
FROM notifications n 
WHERE id_user = :id AND seen IS NULL 
) UNION 
(SELECT id, event, seen, time_stamp 
FROM notifications n 
WHERE id_user = :id 
ORDER BY (seen IS NULL) desc, time_stamp desc 
LIMIT 15 
) 
ORDER BY (seen IS NULL) desc, time_stamp desc; 
+2

Basta selezionare tutto invisibile e (unione con) 15 visto. Dopo questo troncano (lato client) a 15 se ci sono meno di 15 non visti. –

+0

La tua seconda subquery ha mancato la condizione 'visto non è nulla' –

+0

@vp_arth La tua idea contiene alcuni punti utili. grazie. –

risposta

1

trovo una soluzione. Per aggiungere la seconda condizione (selezionando due righe di lettura se ci sono più di 15 righe non lette), devo usare ancora uno UNION. Qualcosa del genere:

(SELECT id, event, seen, time_stamp 
FROM notifications n 
WHERE id_user = :id AND seen IS NULL 
)UNION 
(SELECT id, event, seen, time_stamp 
FROM notification n 
WHERE id_user = :id AND seen IS NOT NULL 
LIMIT 2 
)UNION 
(SELECT id, event, seen, time_stamp 
FROM notifications n 
WHERE id_user = :id 
ORDER BY (seen IS NULL) desc, time_stamp desc 
LIMIT 15 
) 
ORDER BY (seen IS NULL) desc, time_stamp desc; 

La prima subquery ottiene tutte le righe non visualizzate. Il secondo ottiene due righe viste. Il terzo ottiene quindici file. Lo UNION rimuove i duplicati, ma non viene applicato nessun altro limite.

+1

e ora conosco la differenza tra 'union' e' union all' :) Grazie. –

0

Vorrei forse semplificare la query e utilizzare una logica di post-elaborazione nell'applicazione per gestire il caso bordo intorno a 14 o 15 righe che sono letti. Basta selezionare fino a 17 righe anziché 15 e, mentre si scorre il set di risultati nell'applicazione client, semplicemente non preoccuparsi di recuperare le righe 16 e 17 a meno che le righe 14 e o 15 non siano lette.

Quella domanda potrebbe essere semplice come:

SELECT id, event, seen, time_stamp 
FROM notifications n 
WHERE id_user = :id 
ORDER BY seen DESC, time_stamp DESC 
LIMIT 17 
+0

Grazie per il tuo impegno ma credo che tu non abbia capito il mio punto. 'LIMIT 17' limita l'uscita a 17 righe in max. Cosa succede se ci sono 20 righe non lette? Come ho detto, voglio sempre selezionare ** tutte ** le righe non lette. –

1
SELECT id, event, seen, time_stamp 
FROM notifications n 
WHERE id_user = 123 AND seen IS NULL 

UNION 

(SELECT id, event, seen, time_stamp 
FROM ( 
SELECT id, event, seen, n.id_user, time_stamp, un.CNT 
    FROM notifications n 
    JOIN (
     SELECT COUNT(1) CNT, id_user 
     FROM notifications 
     WHERE id_user = 123 and seen is NULL 
     group by id_user 
     ) un 
    ON n.id_user = un.id_user 
    WHERE CNT > 15 
) t1 
WHERE t1.SEEN is not NULL 
LIMIT 2) 

UNION 

SELECT id, event, seen, time_stamp 
FROM ( 
SELECT id, event, seen, n.id_user, time_stamp, un.CNT 
    FROM notifications n 
    JOIN (
     SELECT COUNT(1) CNT, id_user 
     FROM notifications 
     WHERE id_user = 123 and seen is NULL 
     group by id_user 
     ) un 
    ON n.id_user = un.id_user 
    WHERE CNT < 15 
) t1 
WHERE t1.SEEN is not NULL 
+0

Oh .. questa è una query piuttosto complessa. Non so che funzioni o no, ma preferisco non usarlo. Sento che c'è un approccio più semplice. Grazie comunque e +1 per il tuo tentativo. –

1

Basta selezionare tutto invisibile e (unione con) 15 visto.

SELECT id, event, seen, time_stamp 
FROM notifications n 
WHERE id_user = :id AND seen IS NULL 
UNION ALL 
(SELECT id, event, seen, time_stamp 
FROM notifications n 
WHERE id_user = :id AND seen IS NOT NULL 
LIMIT 15) 

Così ora avete tutte le notifiche di lettura non lette e fino a 15.

Dopodiché è possibile troncare (lato client) a 15 se ci sono meno di 15 non visualizzati.

Il posto migliore per farlo, penso, è il ciclo di recupero.
Basta contare, vedere/non vedere e interrompere il ciclo nel punto in cui si raggiungono abbastanza righe.

Alcuni pseudocodice php:

$read = $unread = 0; 

while($row = $db->fetch()) { 
    if ($row['seen']) $read++; 
    if (!$row['seen']) $unread++; 
    // ... 
    if ($weHaveEnoughRows) break; 
} 
+0

Grazie .. sì può essere corretto. +1. Potresti semplicemente dare un'occhiata alla mia risposta e dirmi la tua opinione al riguardo? –

1

favore prova questa,

rendimenti tavolo T leggere le notifiche con la riga numero d'ordine di time_stamp disc.

Quindi selezionare da T dove riga < = GREATEST (15-Count() di non letto, 2).

e poi l'unione tutte con letto

SELECT id,event,seen,time_stamp 
FROM 
    (SELECT id, event, seen, time_stamp,@row:[email protected]+1 as row 
    FROM notifications n,(SELECT @row := 0)r 
    WHERE id_user = :id AND seen IS NOT NULL 
    ORDER BY time_stamp desc 
    )T 
WHERE T.row <= GREATEST(15- 
        (SELECT COUNT(*) FROM notifications n 
        WHERE id_user = :id AND seen IS NULL),2) 
UNION ALL 
(SELECT id, event, seen, time_stamp 
FROM notifications n 
WHERE id_user = :id 
AND seen is NULL 
) 
ORDER BY (seen IS NULL) desc,time_stamp desc 
1

Prova:

SET @`id_user` := 123; 

SELECT `id`, `event`, `seen`, `time_stamp` 
FROM (SELECT `id`, `event`, `seen`, `time_stamp`, @`unread` := @`unread` + 1 
     FROM `notifications`, (SELECT @`unread` := 0) `unr` 
     WHERE `id_user` = @`id_user` AND `seen` IS NULL 
     UNION ALL 
     SELECT `id`, `event`, `seen`, `time_stamp`, @`read` := @`read` + 1 
     FROM `notifications`, (SELECT @`read` := 0) `r` 
     WHERE `id_user` = @`id_user` AND `seen` IS NOT NULL 
      AND (
       @`read` < (15 - @`unread`) OR 
       ((15 - @`unread`) < 0 AND @`read` < 2) 
      ) 
) `source`; 

SQL Fiddle demo

+0

Comunque nel tuo violino, ci sono 18 righe non lette ('seen = null') e l'output è di 18 righe' :-) '..! il risultato previsto è di 20 righe. –

+0

Oh scusa, hai ragione ... due di quelle 18 righe non lette appartengono a un altro utente. –

+0

La tua query ha solo un piccolo problema .. non ha alcun ordine .. Voglio questo ordine: 'ORDER BY (visto IS NULL) desc, time_stamp desc', potresti aggiungerlo alla tua domanda? –