Domanda

Ho un Ruby on Rails applicazione con un database PostgreSQL; diversi tavoli hanno created_at e updated_at attributi timestamp. Quando viene visualizzato, quelle date sono formattati nel locale dell'utente; per esempio, il timestamp 2009-10-15 16:30:00.435 diventa 15.10.2009 - 16:30 stringa (il formato data per questo esempio è dd.mm.yyyy - hh.mm).

Il requisito è che l'utente deve essere in grado di cercare i record per data, come se fossero stringhe formattate nella localizzazione corrente. Ad esempio, la ricerca di 15.10.2009 sarebbe tornato record con date il 15 ottobre 2009, alla ricerca di 15.10 sarebbe restituire i record con le date il 15 ottobre di ogni anno, alla ricerca di 15 sarebbe tornato tutte le date quella partita 15 (sia esso giorno, mese o anno) . Dal momento che l'utente può utilizzare qualsiasi parte di un data come un termine di ricerca, esso non può essere convertito in una data / timestamp per il confronto.

Un modo (lenta) sarebbe quello di recuperare tutti i record, formattare le date ed eseguire la ricerca su questo. Questo potrebbe essere accelerato recuperando solo l'ID e le date in un primo momento, eseguendo la ricerca, e poi andare a prendere i dati per i record corrispondenti; ma potrebbe essere ancora lento per un gran numero di righe.

Un altro modo (il database agnostico) sarebbe quello di fusione / formattare le date per il formato giusto nel database con funzioni o operatori di PostgreSQL, e hanno il database fare l'abbinamento (con gli operatori di PostgreSQL regexp o roba del genere).

C'è un modo per fare questo in modo efficace (senza caricare tutte le righe) in un modo del database agnostico? O pensi che sto andando nella direzione sbagliata e dovrebbe affrontare il problema in modo diverso?

È stato utile?

Soluzione

Sulla base della risposta da Carlos, questo dovrebbe consentire tutte le tue ricerche senza scansione completa della tabella se si dispone di indici su tutte le informazioni e la data campi di parte. indici basati sulle funzioni sarebbe meglio per la data colonne di parte, ma io non sono il loro utilizzo dal momento che questo non dovrebbe essere base di dati specifici.

CREATE TABLE mytable (
    col1 varchar(10),
    -- ...
    inserted_at timestamp,
    updated_at timestamp);

INSERT INTO mytable
VALUES
    ('a', '2010-01-02', NULL),
    ('b', '2009-01-02', '2010-01-03'),
    ('c', '2009-11-12', NULL),
    ('d', '2008-03-31', '2009-04-18');

ALTER TABLE mytable
    ADD inserted_at_month integer,
    ADD inserted_at_day integer,
    ADD updated_at_month integer,
    ADD updated_at_day integer;

-- you will have to find your own way to maintain these values...
UPDATE mytable
SET
    inserted_at_month = date_part('month', inserted_at),
    inserted_at_day = date_part('day', inserted_at),
    updated_at_month = date_part('month', updated_at),
    updated_at_day = date_part('day', updated_at);

Se l'utente inserisce solo per uso dell'anno in cui Data TRA 'AAAA-01-01' E 'YYYY-12-31'

SELECT *
FROM mytable
WHERE
    inserted_at BETWEEN '2010-01-01' AND '2010-12-31'
    OR updated_at BETWEEN '2010-01-01' AND '2010-12-31';

Se l'utente inserisce Anno e mese uso DOVE Data TRA 'AAAA-MM-01' E 'AAAA-MM-31' (potrebbe essere necessario aggiustamento per 30/29/28)

SELECT *
FROM mytable
WHERE
    inserted_at BETWEEN '2010-01-01' AND '2010-01-31'
    OR updated_at BETWEEN '2010-01-01' AND '2010-01-31';

Se l'utente inserisce i tre valori utilizzano SELEZIONA .... dove Date = 'AAAA-MM-DD'

SELECT *
FROM mytable
WHERE
    inserted_at = '2009-11-12'
    OR updated_at = '2009-11-12';

Se l'utente inserisce mese e giorno

SELECT *
FROM mytable
WHERE
    inserted_at_month = 3
    OR inserted_at_day = 31
    OR updated_at_month = 3
    OR updated_at_day = 31;

Se l'utente inserisce mese o il giorno (si potrebbe ottimizzare per non controllare i valori> 12 come un mese)

SELECT *
FROM mytable
WHERE
    inserted_at_month = 12
    OR inserted_at_day = 12
    OR updated_at_month = 12
    OR updated_at_day = 12;

Altri suggerimenti

Watever l'utente entra, si dovrebbe estrarre tre valori: Year, Month e Day, usando il suo locale come guida. Alcuni valori possono essere vuote.

  • Se l'utente inserisce l'uso solo Year WHERE Date BETWEEN 'YYYY-01-01' AND 'YYYY-12-31'
  • Se l'utente immette Year e uso Month WHERE Date BETWEEN 'YYYY-MM-01' AND 'YYYY-MM-31' (può essere necessario regolare per 30/29/28)
  • Se l'utente inserisce i tre valori utilizzano SELECT .... WHERE Date = 'YYYY-MM-DD'
  • Se l'utente inserisce Month e Day, dovrete usare il modo 'slow'

"Database modo agnostico" di solito è sinonimo di "modo lento", in modo che le soluzioni saranno difficilmente essere efficiente.

L'analisi di tutti i record sul lato client sarebbe la soluzione meno efficace in ogni caso.

È in grado di elaborare la stringa locale sul lato client e formare una condizione di corretto per un LIKE, RLIKE o REGEXP_SUBSRT dell'operatore. Il lato client, naturalmente, dovrebbe essere a conoscenza del database degli usi del sistema.

Poi si dovrebbe applicare all'operatore di una stringa formata secondo il locale con funzione di formattazione di database specifici, come questo (in Oracle):

SELECT  *
FROM    mytable
WHERE   TO_CHAR(mydate, 'dd.mm.yyyy - hh24.mi') LIKE '15\.10'

modo più efficiente (che funziona solo in PostgreSQL, però) sarebbe la creazione di un indice GIN sui singoli dateparts:

CREATE INDEX ix_dates_parts
ON      dates
USING   GIN
        (
        (ARRAY
        [
        DATE_PART('year', date)::INTEGER,
        DATE_PART('month', date)::INTEGER,
        DATE_PART('day', date)::INTEGER,
        DATE_PART('hour', date)::INTEGER,
        DATE_PART('minute', date)::INTEGER,
        DATE_PART('second', date)::INTEGER
        ]
        )
        )

e utilizzarlo in una query:

SELECT  *
FROM    dates
WHERE   ARRAY[11, 19, 2010] <@ (ARRAY
        [
        DATE_PART('year', date)::INTEGER,
        DATE_PART('month', date)::INTEGER,
        DATE_PART('day', date)::INTEGER,
        DATE_PART('hour', date)::INTEGER,
        DATE_PART('minute', date)::INTEGER,
        DATE_PART('second', date)::INTEGER
        ]
        )
LIMIT 10

In questo modo selezionare i record, avendo tutti e tre i numeri (1, 2 e 2010) in una qualsiasi delle dateparts:. Come, tutti i record di Novemer 19 2010 oltre a tutti i record di 19:11 in 2010, etc

IMHO, il risposta breve è No . Ma sicuramente evitare di caricare tutte le righe .

Alcune note:

  • se si ha solo le query semplici per le date esatte o intervalli, mi consiglia di utilizzare formato ISO per DATE (YYYY-MM-DD, ex: 2010-02-01) o DATETIME. Ma dal momento che ti sembra di query necessità come "tutti gli anni per 15 ottobre", è necessario interroga in ogni modo personalizzato.
  • Vi suggerisco di creare un "parser" che prende la query data e si dà la parte della clausola SQL WHERE. Sono certo che si finirà per avere meno di una dozzina di casi, in modo da poter avere WHEREs ottimale per ciascuno di essi. In questo modo si eviterà di caricare tutti i record.
    • che sicuramente non si vuole fare nulla locale specifica in SQL. Pertanto la conversione locale per alcuni standard nel codice non-SQL, quindi utilizzarlo per eseguire la query (localizzazione / globalizzazione fondamentalmente separata e l'esecuzione di query)
    • Quindi è possibile ottimizzare. Se vedi che hai un sacco di domanda solo per year, è possibile creare un COMPUTED COLUMN che conterrebbe solo la YEAR e hanno indice su di esso.
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top