2010-02-08 13 views
5

Sto provando a selezionare un set di risultati che fornisca i 5 utenti più vicini ordinati per compleanno imminente. Questo funziona perfettamente fino a quando gli anni bisestili entrano in gioco. Per esempio:mysql promemoria di compleanno, anno bisestile

  • 15 maggio - 96 giorni per
  • 15 maggio - 97 giorni per

Il buon risultato è una nascita al 1987 e la più bassa viene dal 1988. u_birth viene memorizzato come aaaa-mm-gg. C'è un modo semplice per ordinare questo problema senza dover riscrivere l'intera query?

SELECT u_birth, IF(DAYOFYEAR(u_birth) >= DAYOFYEAR(NOW()), 
      DAYOFYEAR(u_birth) - DAYOFYEAR(NOW()), 
      DAYOFYEAR(u_birth) - DAYOFYEAR(NOW()) + 
     DAYOFYEAR(CONCAT(YEAR(NOW()), '-12-31')) 
) 
AS distance 
FROM (blog_users) 
WHERE `s_agehide` = 0 
ORDER BY distance ASC 
LIMIT 5 

Questa interrogazione è preso e modificato dal manuale di MySQL: http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#c7489

risposta

7

C'è ovviamente un problema se l'algoritmo dipende dall'anno di nascita della persona. Per risolvere il problema, prima trovare il prossimo compleanno di ogni persona dopo la data corrente, quindi calcolare la differenza tra quella data e ora.

SELECT u_birth, DATEDIFF(next_birthday, NOW()) AS distance FROM (
    SELECT *, ADDDATE(birthday, INTERVAL birthday < DATE(NOW()) YEAR) AS next_birthday 
    FROM (
     SELECT *, ADDDATE(u_birth, INTERVAL YEAR(NOW()) - YEAR(u_birth) YEAR) AS birthday 
     FROM blog_users 
     WHERE s_agehide = 0 
    ) AS T1 
) AS T2 
ORDER BY distance ASC 
LIMIT 5 

Risultati: dati

'1992-02-29', 20 
'1993-03-01', 21 
'1987-05-15', 96 
'1988-05-15', 96 
'1988-09-18', 222 

test:

CREATE TABLE blog_users (u_birth NVARCHAR(100) NOT NULL, s_agehide INT NOT NULL); 
INSERT INTO blog_users (u_birth, s_agehide) VALUES 
('1987-05-15', 0), 
('1988-05-15', 0), 
('1988-09-20', 0), 
('2000-01-02', 0), 
('2000-01-03', 1), 
('1988-09-19', 0), 
('1988-09-18', 0), 
('1992-02-29', 0), 
('1993-03-01', 0); 

noti che chi è nato in un giorno di salto si assume di avere un compleanno del 28 febbraio in un anno non bisestile.

Inoltre, la query non include l'id utente dell'utente. Probabilmente vuoi aggiungere anche questo, immagino.

+0

Grazie! Avrò un'occhiata, dammi un paio di minuti per provarlo =) – moodh

+0

@tired: Nota che ho apportato una piccola modifica in modo che tutte le persone i cui compleanni siano oggi abbiano la distanza 0, non la distanza 365. Ho cambiato un 'NOW()' a 'DATE (NOW())'. –

+0

Funziona come dovrebbe, tuttavia ho una domanda successiva: Come posso recuperare righe aggiuntive dalla tabella? Ho provato in tutte e tre le selezioni ma senza fortuna, diciamo che voglio recuperare anche s_agehide. Grazie per tutto l'aiuto! – moodh

1

Non usare dayofyear come nulla dopo 29 febbraio in un anno bisestile è di un giorno più avanti rispetto al solito. Invece, estrai il mese e il giorno e concatenili insieme con l'anno di oggi. Quindi fai il confronto.

Come così:

SELECT u_birth, 
    IF(DATE(CONCAT(YEAR(NOW()),'-',MONTH(u_birth),'-',DAY(u-birth))) >= DATE(NOW()), 
... 

ETA:

Basta quindi non c'è bisogno di riscrivere l'espressione per la conversione di data di nascita di compleanno, mi atterrei in una variabile. Ti consiglio di scrivere una funzione che esegue la conversione in modo che tu possa utilizzarla direttamente in una query.

SET @birthday= SELECT DATE(CONCAT(YEAR(NOW()),'-',MONTH(u_birth),'-',DAY(u-birth))) FROM users WHERE user_id=x; //obviously only good for one value, but use the example for the calculation 

SELECT u_birth, 
     IF(DATE(@birthday)>=DATE(NOW()), 
      DATEDIFF(DATE_ADD(@birthday, INTERVAL 1 YEAR),NOW()), 
      DATEDIFF(NOW(),@birthday) 
     ) as days 
FROM users WHERE user_id=x; 
+0

Ho provato a sostituire tutto il giorno con gli esempi mostrati, sì, il problema degli anni bisestili non è più visualizzato ma il valore e l'ordinamento sono completamente disattivati. Potresti mostrarmi l'intera richiesta riscritta? – moodh

1

Forse qualcuno si ispira a questa pace del codice.

Ha funzionato perfettamente nel mio caso, ma penso che ci sia molto di data-calcolo ...

SELECT CONCAT(YEAR(CURRENT_DATE()),'-',DATE_FORMAT(FROM_UNIXTIME(date_of_birth), '%m-%d')), fe_users.* 
FROM `fe_users` 
WHERE `date_of_birth` != 0 AND (
    CONCAT(YEAR(CURRENT_DATE())+1,'-',DATE_FORMAT(FROM_UNIXTIME(date_of_birth), '%m-%d')) BETWEEN CURRENT_DATE() AND DATE_ADD(CURRENT_DATE(), INTERVAL 7 DAY) 
    OR 
    CONCAT(YEAR(CURRENT_DATE()),'-',DATE_FORMAT(FROM_UNIXTIME(date_of_birth), '%m-%d')) BETWEEN CURRENT_DATE() AND DATE_ADD(CURRENT_DATE(), INTERVAL 7 DAY) 
) 

Con questa pace di SQL-Code si otterrà tutti gli utenti dei fe_users tavolo il cui date_of_birth (salvato come unix-timestamp) sarà nei prossimi 7 giorni. È possibile estenderlo facilmente a 21 giorni modificando l'intervallo utilizzato dalla funzione date_add().

+0

A proposito: questo script è il migliore in caso di prestazioni - come ho provato ... – SimonSimCity

2
SELECT 
    CONCAT(
     YEAR(CURRENT_DATE()), 
     '-', 
     DATE_FORMAT((birthDate), 
      '%m-%d' 
     ) 
    ), 
    fullName 
FROM 
    employees 
WHERE 
    birthDate != 0 
AND(
    CONCAT(
     YEAR(CURRENT_DATE())+ 1, 
     '-', 
     DATE_FORMAT((birthDate), 
      '%m-%d' 
     ) 
    )BETWEEN CURRENT_DATE() 
    AND DATE_ADD(
     CURRENT_DATE(), 
     INTERVAL 21 DAY 
    ) 
    OR CONCAT(
     YEAR(CURRENT_DATE()), 
     '-', 
     DATE_FORMAT((birthDate), 
      '%m-%d' 
     ) 
    )BETWEEN CURRENT_DATE() 
    AND DATE_ADD(
     CURRENT_DATE(), 
     INTERVAL 21 DAY 
    ) 
) 

ps: birthDate è la colonna in cui ho conservato la data di nascita dei dipendenti. dipendenti è il nome della tabella.