E 'possibile cercare le date come stringhe in un modo del database agnostico?
-
24-09-2019 - |
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?
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 usoMonth
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
eDay
, 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 avereWHEREs
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 unCOMPUTED COLUMN
che conterrebbe solo laYEAR
e hanno indice su di esso.