2015-09-17 13 views
14

Sto ordinando brani in SQLite (su Android). Voglio ordinarli:ORDINA DI caratteri alfanumerici solo in SQLite

  1. Case-insensitive
  2. con la più cifre alla fine, dal valore intero.
  3. Senza punteggiatura (ad esempio parentesi, punti, trattini, apostrofi)

ho 1 & 2 lavora (vedi sotto). Tuttavia, non riesco a capire come sostituire ogni carattere (diverso da lettere, numeri e spazi) diverso dal chiamare replace() per ogni carattere.

C'è un modo per fare questo oltre a ~ 32 chiamate a replace()?
(valori ASCII 33-47,58-64,91-96,123-126)


Ecco una tabella di test. Il valore 'n' dovrebbe idealmente venire fuori in ordine. (No, non si può ordinare da n;)

create table songs (n integer, name text); 
insert into songs (n,name) values (6,'I''ll Be That Girl'); 
insert into songs (n,name) values (24,'1969'); 
insert into songs (n,name) values (9,'La Moldau'); 
insert into songs (n,name) values (20,'Pule'); 
insert into songs (n,name) values (7,'I''m a Rainbow Too'); 
insert into songs (n,name) values (21,'5 Years'); 
insert into songs (n,name) values (18,'Pressure'); 
insert into songs (n,name) values (13,'Lagan'); 
insert into songs (n,name) values (1,'any old wind that blows'); 
insert into songs (n,name) values (17,'Poles Apart'); 
insert into songs (n,name) values (8,'Imagine'); 
insert into songs (n,name) values (14,'Last Stop before Heaven'); 
insert into songs (n,name) values (3,'I Before E Except After C'); 
insert into songs (n,name) values (4,'i do, i do, i do'); 
insert into songs (n,name) values (22,'99 Luftballons'); 
insert into songs (n,name) values (12,'L''accord parfait'); 
insert into songs (n,name) values (15,'Pluto'); 
insert into songs (n,name) values (19,'The Promise'); 
insert into songs (n,name) values (2,'(Don''t Fear) The Reaper'); 
insert into songs (n,name) values (10,'L.A. Nights'); 
insert into songs (n,name) values (23,'911 is a Joke'); 
insert into songs (n,name) values (5,'Ichthyosaurs Are Awesome'); 
insert into songs (n,name) values (11,'Labradors are Lovely'); 
insert into songs (n,name) values (16,'P.O.D.-Boom'); 

Ecco la soluzione ad appena 1 & 2 di cui sopra:

SELECT n 
FROM songs 
ORDER BY 
    CASE WHEN name GLOB '[0-9]*' THEN 1 
     ELSE 0 
    END, 
    CASE WHEN name GLOB '[0-9]*' THEN CAST(name AS INT) 
     ELSE name 
    END 
COLLATE NOCASE 

Per questo test impostato produce risultati in questo ordine: 2,1,3,4,6,7,5,8,12,10,9,11,13,14,16,15,17,18,20,19,21,22,23,24

Posso risolvere questo particolare set di test con sostituzioni manuali per ogni carattere indesiderato:

SELECT n 
FROM songs 
ORDER BY 
    CASE WHEN name GLOB '[0-9]*' THEN 1 
     ELSE 0 
    END, 
    CASE WHEN name GLOB '[0-9]*' THEN CAST(name AS INT) 
     ELSE 
     replace(
      replace(
      replace(
       replace(name,'.',''), 
       '(','' 
      ), 
      '''','' 
      ), 
      ' ',' ' 
     ) 
    END 
COLLATE NOCASE 
+0

Nel caso in cui sia utile, posso fare affidamento su SQLite 3.8.6 o superiore. Attualmente sto prendendo di mira solo Android L, e presto prenderò di mira Android M. – Phrogz

+0

@ Phrogz..perché non provi a prendere la differenza tra la lunghezza della stringa originale e la lunghezza della stringa con la punteggiatura sostituita da '''' (stringa vuota) e "ordina" per quella differenza per il n. 3 nella domanda? –

+0

non penso che tu mi abbia preso. Questo è quello che stavo cercando di trasmettere. per una stringa 'ab, c' sarebbe len (orig_string) è 4 e len (orig_string_with_punctuation sostituito da '') è 3. In questo modo si ottiene il diff come '1'. E per le stringhe senza punteggiatura questa diff sarebbe pari a 0. Quindi potresti usare queste differenze nella clausola 'order by'. spero che tu mi capisca –

risposta

5

Vorrei aggiungere una colonna aggiuntiva nella tabella, chiamata "SortingName" o qualcosa del genere. Calcola questo valore durante l'inserimento, idealmente non in SQL ma in un linguaggio di livello superiore in cui hai tutte queste belle operazioni con le stringhe.

Non ho davvero capito questa cosa con il numero. Immagino che la cosa più semplice che puoi fare sia estrarre il numero prima di inserirlo e inserirlo in un'altra colonna, come "SortingNumber".

Poi semplicemente sorta in questo modo: (. O viceversa)

Order By 
    SortingName, 
    SortingNumber 

Un altro vantaggio è la prestazione. Di solito leggi i dati molto più spesso di quanto tu scriva. È anche possibile creare indici su queste due colonne di ordinamento, che di solito non è possibile se lo si calcola nella query.

+1

Siamo spiacenti, il database è di sola lettura dal mio punto di vista. La tabella è generata dinamicamente dai metadati delle canzoni trovate. È possibile che io possa essere in grado di aggiungere una seconda tabella con queste informazioni, ma poi devo preoccuparmi che le tabelle non siano sincronizzate man mano che i nuovi brani diventano disponibili o scompaiono. – Phrogz

+0

Sembra uno di questi casi d'uso disperati di un trigger del database ... –

2

Se ti è permesso di creare funzioni, questo è quello che mi piacerebbe creare (tratto da How to strip all non-alphabetic characters from string in SQL Server? e modificato un po '):

Create Function [dbo].[RemoveNonAlphaNumericCharacters](@Temp VarChar(1000)) 
Returns VarChar(1000) 
AS 
Begin 

    Declare @KeepValues as varchar(50) 
    Set @KeepValues = '%[^a-zA-Z0-9\s]%' 
    While PatIndex(@KeepValues, @Temp) > 0 
     Set @Temp = Stuff(@Temp, PatIndex(@KeepValues, @Temp), 1, '') 

    Return @Temp 
End 

Ciò fronte alla vostra richiesta # 3 e togliere tutta la spazzatura fuori della stringa, quindi la query sarà simile a questa:

SELECT n 
FROM songs 
ORDER BY 
    CASE WHEN [dbo].[RemoveNonAlphaNumericCharacters](name) GLOB '[0-9]*' THEN 1 
     ELSE 0 
    END, 
    CASE WHEN [dbo].[RemoveNonAlphaNumericCharacters](name) GLOB '[0-9]*' THEN CAST(name AS INT) 
     ELSE [dbo].[RemoveNonAlphaNumericCharacters](name) 
    END 
COLLATE NOCASE 

Non sembra carina e potrebbe non avere le migliori prestazioni. Probabilmente lo farei, cosa suggerì Stefan. Analizza i tuoi nomi dei brani e inserisci quelli tagliati in una colonna separata solo per l'ordine (e ovviamente hai un indice su quella colonna). Dovrebbe essere la soluzione migliore.

+0

Grazie per l'idea. Non penso sia possibile poiché questo non è il mio database, ma è fornito dal gestore dei media. http://stackoverflow.com/a/8283265 – Phrogz

+0

@Phrogz Avrei dovuto essere più attento quando guardavo i prodotti taggati. Immagino che non ci sia altro modo reale, piuttosto che spogliare manualmente tutto il cianfrusaglia con oltre 30 sostituzioni. –

3

A mio parere, l'approccio più performante consiste nel creare un trigger per riempire un nuovo campo denominato sort_key. Avrai bisogno di una chiave primaria.

CREATE TABLE songs (n INTEGER, name TEXT, 
        sort_key TEXT, 
        ID INTEGER PRIMARY KEY AUTOINCREMENT); 

CREATE TRIGGER songs_key_trigger 
    AFTER INSERT ON songs FOR EACH ROW 
    BEGIN n 
     Declare @sort_key as varchar(255) 
     -- calculate and call here your slugify function 
     -- to fill sort_key from 'new.n' and 'new.name' 
     UPDATE songs 
      SET sort_key = @sort_key 
      WHERE ID = new.ID; 
    END 

rendo conto che questo approccio è indice amichevole, è possibile creare un indice di nuova colonna della tabella per evitare operazioni di scansione completa.

2

È possibile utilizzare sqlite3 Android NDK Bindings per accedere all'API sqlite3 completa utilizzando le chiamate JNI.

Quindi è possibile Define New Collating Sequences utilizzando il sqlite3_create_collation_v2() e le relative funzioni.

Questo approccio non modifica il database, in quanto le regole di confronto vengono sovrascritte solo sulla connessione al database corrente. Quindi soddisfa tale requisito in quanto funziona se il database è di sola lettura.

Avviso Dico che puoi. Non sto dicendo che dovresti farlo! Pesare i pro e i contro di questo approccio poiché nella maggior parte dei casi probabilmente non ne vale la pena.

3

La prima soluzione (quando DB e l'applicazione possono essere modificati):

Aggiungi alla tua tavola singola colonna per esempio solumntForSorting. Quindi, prima di inserire la tua applicazione, concatena la tua seconda condizione ("Con cifre iniziali alla fine, per valore intero.") Come 0 o 1 al nome del brano che per prima cosa "è stato ripulito" da simboli indesiderati. Quindi su solumntForSorting otterrete qualcosa del genere: 0Im a Rainbow Too e 1911 è uno scherzo.

La seconda soluzione (in cui solo applicazione può essere modificato):

Se è necessario ordinare i dati esclusi alcuni simboli e non ti è permesso di cambiare il vostro DB, si otterrà una selezione più lenta a causa di filtraggio valori indesiderati. La maggior parte del sovraccarico avverrà in termini di tempo e memoria della CPU.

L'uso della funzione di sostituzione è noioso dal mio punto di vista, è per questo che suggerisco di usare CTE con l'elenco di valori che si desidera eliminare, come questo ('.', '.', ';', '(', ')', '' '', '-'). Il CTE sarà voluminoso come sostituire più volte ma è più facile da modificare e mantenere.

Prova questa soluzione:

WITH RECURSIVE 
ordering_name_substr(len, name, subsstr, hex_subsstr, number) 
AS (SELECT length(name), name, substr(name, 1, 1), hex(substr(name, 1, 1)), 1 
     FROM songs 
     UNION ALL 
    SELECT len, name, substr(name, number + 1, 1), 
      hex(substr(name, number + 1, 1)), number + 1 
     FROM ordering_name_substr WHERE number < len), 
last_order_cretaria(value, old_name) 
    AS (select GROUP_CONCAT(subsstr, ''), name 
      from ordering_name_substr 
     where hex_subsstr not in 
     ('28', '29', '2C', '2E', '27') group by name) 

SELECT S.n, S.name 
FROM songs AS S LEFT JOIN last_order_cretaria AS OC 
ON S.name = OC.old_name 
ORDER BY 
    CASE WHEN name GLOB '[0-9]*' THEN 1 
     ELSE 0 
    END, 
    CASE WHEN name GLOB '[0-9]*' THEN CAST(name AS INT) 
     ELSE 
     OC.value 
    END 
COLLATE NOCASE 

I have tested on sqlfiddle.

Nell'elenco ('28', '29', '2C', '2E', '27') si hanno valori di codici ASCII (in esadecimale), che si vuole fuggire da essere considerato in ordine.

È inoltre possibile provare a utilizzare valori come: ('.', '.', ';', '(', ')', '''', '-').

WITH RECURSIVE 
ordering_name_substr(len, name, subsstr, number) 
AS (SELECT length(name), name, substr(name, 1, 1), 1 
     FROM songs 
     UNION ALL 
    SELECT len, name, substr(name, number + 1, 1), 
      number + 1 
     FROM ordering_name_substr WHERE number < len), 
last_order_cretaria(value, old_name) 
    AS (select GROUP_CONCAT(subsstr, ''), name 
      from ordering_name_substr 
     where subsstr not in 
     ('.', '.', ';', '(', ')', '''', '-') group by name) 

SELECT S.n, S.name 
FROM songs AS S LEFT JOIN last_order_cretaria AS OC 
ON S.name = OC.old_name 
ORDER BY 
    CASE WHEN name GLOB '[0-9]*' THEN 1 
     ELSE 0 
    END, 
    CASE WHEN name GLOB '[0-9]*' THEN CAST(name AS INT) 
     ELSE 
     OC.value 
    END 
COLLATE NOCASE 

Per rendere questo lavoro di ordinamento rapido e semplice, è necessario poter modificare il DB e l'applicazione.

Problemi correlati