Come progetteresti un database utente con campi personalizzati


18

Questa domanda riguarda come dovrei progettare un database, può essere un database relazionale / nosql, a seconda di quale sarà la soluzione migliore


Dato un requisito in cui è necessario creare un sistema che coinvolgerà un database per tracciare "Azienda" e "Utente". Un singolo utente appartiene sempre a una sola azienda

  • Un utente può appartenere a una sola società
  • Una società può avere molti utenti

Il design per il tavolo "Azienda" è piuttosto semplice. La società avrà i seguenti attributi / colonne: (manteniamolo semplice)

ID, COMPANY_NAME, CREATED_ON

Primo scenario

Semplice e diretto, gli utenti hanno tutti lo stesso attributo, quindi questo può essere facilmente fatto in stile relazionale, tabella utente:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CREATED_ON

Secondo scenario

Cosa succede se diverse aziende vogliono memorizzare attributi di profilo diversi per i loro utenti. Ogni azienda avrà un set definito di attributi che si applicherebbe a tutti gli utenti di quella società.

Per esempio:

  • La società A vuole archiviare: LIKE_MOVIE (booleano), LIKE_MUSIC (booleano)
  • La società B vuole archiviare: FAV_CUISINE (String)
  • La società C vuole archiviare: OWN_DOG (booleano), DOG_COUNT (int)

Approccio 1

il modo della forza bruta è avere un singolo schema per l'utente e lasciare che abbiano valori nulli quando non appartengono alla società:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, LIKE_MOVIE, LIKE_MUSIC, FAV_CUISINE, OWN_DOG, DOG_COUNT, CREATED_ON

Il che è un po 'brutto perché finirai con un sacco di NULLS e righe utente che hanno colonne che sono irrilevanti per loro (cioè tutti gli utenti appartenenti alla Società A hanno valori NULL per FAV_CUISINE, OWN_DOG, DOG_COUNT)

Approccio 2

un secondo approccio, è avere "campo in forma libera":

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_1, CUSTOM_2, CUSTOM_3, CREATED_ON

Che sarebbe brutto da solo poiché non hai idea di quali campi personalizzati siano, il tipo di dati non rifletterà i valori memorizzati (ad esempio, memorizzeremo il valore int come VARCHAR).

Approccio 3

Ho esaminato il campo JSON di PostgreSQL, nel qual caso avrai:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_PROFILE_JSON, CREATED_ON

In questo caso, come saresti in grado di applicare diversi schemi a un utente? Un utente con la società A avrà uno schema simile

 {"LIKE_MOVIE":"boolean", "LIKE_MUSIC": "boolean"}

Mentre un utente con la società C avrà uno schema diverso:

 {"OWN_DOG ":"boolean", "DOG_COUNT": "int"}

Come devo risolvere questo problema? Come posso progettare correttamente il database per consentire questo schema flessibile per un singolo "oggetto" (Utente) basato sulla relazione che hanno (Azienda)?

soluzione relazionale? soluzione nosql?


Modifica: ho anche pensato a una tabella "CUSTOM_PROFILE" che essenzialmente memorizzerà gli attributi dell'utente nelle righe anziché nelle colonne.

Ci sono 2 problemi con questo approccio:

1) I dati crescono per utente crescono come righe anziché come colonne - e questo significa che per ottenere un quadro completo dell'utente, è necessario eseguire molti join, più join alla tabella "profilo personalizzato" sui diversi attributi personalizzati

2) Il valore dei dati viene sempre archiviato come VARCHAR come generico, anche se sappiamo che i dati dovrebbero essere interi o booleani, ecc.


3
Se diverse aziende hanno set di dati diversi e multivalore su ciascun cliente, allora hai assolutamente bisogno di una tabella di collegamento COMPANY_CUSTOMER. Tutto il resto ti causerà molto dolore molto presto.
Kilian Foth,

In che modo una tabella di collegamento potrebbe aiutare con i dati personalizzati? le colonne dovranno comunque essere diverse
noobcser,

1
Devi rappresentare il fatto "La password di Kilian per IKEA è" gattino "" con una tupla come "AZIENDA: IKEA, CLIENTE: Kilian, ATTRIBUTO: password, VALORE: gattino". Nulla di più semplice non farà il lavoro.
Kilian Foth,

3
Uno schema è una cosa fissa, per definizione; non puoi crearne uno se non sai quali sono i campi di cui hai bisogno. Dai un'occhiata a Entity-Attribute-Value per un modo in cui problemi come questo tendono ad essere risolti in un database relazionale.
Mason Wheeler,

Risposte:


13

Si prega di considerare questo come un'alternativa. I due esempi precedenti richiederanno entrambi di apportare modifiche allo schema man mano che l'ambito dell'applicazione aumenta, inoltre la soluzione "colonna_personalizzata" è difficile da estendere e mantenere. Alla fine ti ritroverai con Custom_510 e poi immagina quanto terribile sarà questa tabella con cui lavorare.

Per prima cosa usiamo il tuo schema Aziende.

[Companies] ComnpanyId, COMPANY_NAME, CREATED_ON

Successivamente utilizzeremo anche il tuo schema Utenti per gli attributi richiesti di livello superiore che verranno utilizzati / condivisi da tutte le società.

[Users] UserId, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CREATED_ON

Successivamente creiamo una tabella in cui definiremo i nostri attributi dinamici che sono specifici degli attributi utente personalizzati di ciascuna azienda. Quindi qui un valore di esempio della colonna Attribute sarebbe "LikeMusic":

[UserAttributeDefinition] UserAttributeDefinitionId, CompanyId, Attribute

Successivamente definiamo una tabella UserAttributes che conterrà i valori degli attributi utente

[UserAttributes] UserAttributeDefinitionId, UserId, Value

Questo può essere modificato in molti modi per migliorare le prestazioni. È possibile utilizzare più tabelle per UserAttributes, ognuna delle quali specifica il tipo di dati archiviato in Value o lasciarlo semplicemente come VarChar e lavorare con esso come archivio di valori chiave.

È inoltre possibile spostare CompanyId dalla tabella UserAttributeDefiniton e in una tabella di riferimenti incrociati per prove future.


grazie - ho pensato a tale approccio - per favore vedi modifica. 2 problemi: 1) I dati crescono come righe, il che significa che per ottenere un quadro completo di un utente, dovrai fare molti join. 2) "valore" sarà sempre memorizzato come VARCHAR come generico, anche se il valore è in realtà int o booleano ecc.
noobcser

1
Se usi int / bigint per le identità della tabella e ti unisci a quelle non avrai problemi di prestazioni fino a quando non ti troverai in un numero estremo di righe. Ora, se inizi a cercare in base ai valori degli attributi, questo potrebbe presentare un problema se inizi a ottenere un numero enorme di record. In questo caso lavorerei con un DBA per determinare se ci sono indici che potrebbero essere creati o forse una vista indicizzata che potrebbe accelerare questo tipo di ricerche. Ho usato uno schema simile e registra 100 milioni di record all'anno senza problemi di prestazioni, quindi il design di base funziona abbastanza bene IMO
P. Roe

Se sono necessari report, filtri, query e attributi diversi possono appartenere a set di dati diversi. Questo approccio sarebbe migliore di NoSQL? Sto cercando di capire la differenza di prestazioni. Situazione simile solo l'utente può definire report che contengono campi definiti dall'utente.
kos

Nell'approccio sopra, come implementiamo la cosa di ricerca, come diff. le aziende desiderano effettuare ricerche nei propri campi, inclusi anche i campi degli utenti. Qual è l'approccio corretto per fornire una ricerca scalabile oltre a questo
techagrammer

Puoi cercarlo normalmente con molti join. È possibile utilizzare uno script ETL per estrarre i dati da cercare e posizionarli in una struttura più denormalizzata. Infine puoi provare a utilizzare le viste indicizzate come metodo di ricerca. Personalmente raccomando il metodo ETL per generare strutture denormalizzate che sono facili da cercare.
P. Roe,

7

Utilizzare un database NoSQL. Ci sarebbero documenti dell'azienda e dell'utente. Gli utenti avrebbero creato parte del loro schema in modo dinamico basato su un modello utente (testo per indicare campi / tipi per quella società.

\Company\<uniqueidentifier>
    - Name: <Name>
    - CreatedOn: <datetime>
    - UserTemplate: <Text>

\User\<uniqueidentifier>
    - COMPANY_ID: <ID>
    - FIRST_NAME: <Text>
    - LAST_NAME: <Text>
    - EMAIL: <Text>
    - CREATED_ON: <datetime>
    - * Dynamically created fields per company

Ecco come potrebbe apparire in qualcosa come Firebase.com Dovresti imparare come farlo in qualunque cosa tu scelga.


questo è ciò a cui sto pensando o forse le colonne JSON. Come sono le prestazioni in termini di query, filtraggio dei report rispetto alla soluzione proposta da PRoe.
kos

1
Ogni volta che comprimi i dati in json o xml e poi li butti in una colonna, sarà terribilmente lento cercare. Se hai bisogno di cercare i dati presentati nella mia risposta sopra, ti consiglio di utilizzare le viste indicizzate per recuperare i dati. Se questa soluzione non è l'ideale, allora consiglierei di usare ETL per copiare i dati in una struttura che può essere facilmente cercata e segnalata.
P. Roe,

Nell'approccio sopra, come implementiamo la cosa di ricerca, come diff. le aziende desiderano effettuare ricerche nei propri campi, inclusi anche i campi degli utenti. Qual è l'approccio corretto per fornire una ricerca scalabile oltre a questo
techagrammer

Nei database nosql, potresti avere dati ridondanti, ma sono strutturati in modo da essere ricercabili. Quello mostrato sopra è per identificativo univoco. Un altro potrebbe essere \ Azienda \ Nome. È simile ad avere più indici.
JeffO,

3

Se ti imbatterai spesso in richieste di campo personalizzate, in realtà lo modellerei in modo abbastanza simile al database. Creare una tabella che contiene i metadati relativi a ciascun campo personalizzato, CompanyCustomField (a chi appartiene, il tipo di dati, ecc.) E un'altra tabella CompanyCustomFieldValues ​​che contiene CustomerId, FieldId e il valore. Se stai usando qualcosa come Microsoft Sql Server, la colonna del valore dovrebbe essere un tipo di dati sql_variant.

Naturalmente questo non è facile poiché avrai bisogno di un'interfaccia che consenta agli amministratori di definire campi personalizzati per ciascun cliente e un'altra interfaccia che utilizza effettivamente questi metadati per creare un'interfaccia utente per raccogliere i valori dei campi. E se hai altri requisiti, come il raggruppamento di campi insieme o la necessità di fare un tipo di campo dell'elenco di selezione, devi accontentarlo con più metadati / altre tabelle (ad esempio, CompanyCustomFieldPickListOptions).

Questo non è banale, ma ha il vantaggio di non richiedere modifiche al database / modifiche al codice per ogni nuovo campo personalizzato. Sarà necessario codificare anche qualsiasi altra funzionalità dei campi personalizzati (ad esempio, se si desidera regex convalidare un valore di stringa o consentire solo date tra determinati intervalli o se è necessario abilitare un campo personalizzato basato su un altro valore di campo personalizzato ).


grazie - ho pensato a tale approccio - per favore vedi modifica. 2 problemi: 1) I dati crescono come righe, il che significa che per ottenere un quadro completo di un utente, dovrai fare molti join. 2) "valore" sarà sempre memorizzato come VARCHAR come generico, anche se il valore è in realtà int o booleano ecc.
noobcser

1
@noobcser I dati che crescono come righe non contano davvero, dopo che tutti i database stanno progettando attorno a righe e join. In ogni caso, molto probabilmente useresti le espressioni comuni delle tabelle per questo, che sono abbastanza brave in questo genere di cose. Non sono sicuro se ti sei perso la parte in cui ho detto che puoi usare sql_variant come tipo di dati per la colonna del valore, che memorizza il valore come qualunque tipo in cui ti attacchi. Mentre sto nominando i nomi delle caratteristiche del server MS SQL, mi aspetto che altri DBMS maturi abbiano caratteristiche simili.
Andy,

1
@noobcser FYI In realtà ho incontrato questi requisiti abbastanza frequentemente nella mia carriera e ho esperienza con ciascuna delle soluzioni proposte, quindi sto suggerendo quello che ha funzionato meglio nella mia esperienza. L'uso di tipi di dati XML per questo genere di cose è in parte il motivo per cui odio che MS aggiunga XML come tipo di dati nativo.
Andy,

1

Un'alternativa alle altre risposte è quella di avere una tabella chiamata profile_attrib, o simile che lo schema sia completamente gestito dalla tua applicazione.

Man mano che vengono aggiunti attributi personalizzati ALTER TABLE profile_attrib ADD COLUMN like_movie TINYINT(1), è possibile vietare l'eliminazione. Ciò ridurrebbe al minimo il tuo join, garantendo comunque flessibilità.

Immagino che il piccolo compromesso sia che l'applicazione ora ha bisogno di alterare i privilegi di tabella per il database e devi essere intelligente nel sanificare i nomi delle colonne.


L'espressione regolare [^\w-]+dovrebbe benissimo farlo, non permettendo nulla che non lo sia, 0-9A-Za-z_-ma sì, la sanificazione è un must qui per proteggere dalla malvagità o dalla stupidità.
Regolare Joe,

0

La tua domanda ha molte potenziali soluzioni. Una soluzione è archiviare gli attributi aggiuntivi come XML. L'XML può essere archiviato come testo o se si utilizza un database che supporta i tipi XML come XML (SQL Server). La memorizzazione come testo limita la tua capacità di interrogazione (come la ricerca su un attributo personalizzato), ma se la memorizzazione e il recupero sono tutto ciò di cui hai bisogno, allora è una buona soluzione. Se è necessario eseguire una query, l'archiviazione dell'XML come tipo XML sarebbe un'opzione migliore (sebbene sia più specifica del fornitore).

Ciò darà la possibilità di memorizzare un numero qualsiasi di attributi per un cliente con l'aggiunta di una colonna di aggiunta nella tabella dei clienti. Si potrebbero archiviare gli attributi come hashset o dizionario, si perderà la sicurezza dei tipi poiché tutto sarà una stringa con cui iniziare, ma se si applica una stringa di formato standard per date, numeri, valori booleani, funzionerà OK.

Per maggiori informazioni:

https://msdn.microsoft.com/en-us/library/hh403385.aspx

Anche la risposta di WalterMitty è valida, anche se se si hanno molti clienti con attributi diversi si potrebbe finire con molte tabelle se si segue il modello di ereditarietà. Dipende da quanti attributi personalizzati sono condivisi tra i clienti.


Anche questo può funzionare, ma mi sento limitato quando in realtà devi fare qualcosa contro i dati memorizzati nel campo XML / JSON.
Andy,

@Andy - Vero, c'è un altro livello. Interroga DB e analizza XML anziché solo interrogare DB. Non so se lo definirei limitante, solo più ingombrante. Ma sarebbe qualcosa da considerare se gli attributi personalizzati fossero ampiamente utilizzati.
Jon Raynor l'

In T-SQL è possibile definire il contenuto nella colonna XML / JSON in base a uno spazio dei nomi ed eseguire una query in base agli elementi sui dati personalizzati. Non è difficile
Stephen York,

-1

È necessario normalizzare il database in modo da disporre di 3 tabelle diverse per ogni diverso tipo di profilo aziendale. Usando il tuo esempio, avresti tabelle con colonne:

USER_ID, LIKE_MOVIE, LIKE_MUSIC

USER_ID, FAVORITE_CUISINE

USER_ID, OWN_DOG, DOG_COUNT

Questo approccio presuppone che conoscerai prima la forma delle informazioni che un'azienda desidera archiviare e che non cambierà spesso. Se la forma dei dati è sconosciuta in fase di progettazione, probabilmente sarebbe meglio andare con quel campo JSON o un database nosql.


-1

Per una ragione o per l'altra, i database sono il campo in cui si manifesta più spesso l'effetto piattaforma interna. Questo è solo un altro caso del pop-up anti-pattern.

In questo caso, stai cercando di combattere la soluzione naturale e corretta. Gli utenti dell'azienda A non sono utenti dell'azienda B e dovrebbero avere le proprie tabelle per i propri campi.

Il fornitore del database non ti addebita in base alla tabella e non hai bisogno del doppio dello spazio su disco per il doppio delle tabelle (in effetti, avere due tabelle è più efficiente perché non memorizzi gli attributi di A per gli utenti di B. Anche archiviando solo NULL occupa spazio).

Naturalmente, se ci sono sufficienti campi comuni, è possibile fattorizzarli in una tabella Users condivisa e disporre di una chiave esterna in ciascuna delle tabelle utente specifiche dell'azienda. Questa è una struttura così semplice che nessun ottimizzatore di query di database si trova in difficoltà. Qualsiasi JOIN necessario è banale.


3
E se hai migliaia di clienti, una tabella per ciascuno può diventare rapidamente non realizzabile, per non parlare del fatto che avrai bisogno di un codice personalizzato per i campi personalizzati di ciascun cliente.
Andy,

@Andy: indovina un po '? La situazione sarà ancora più insostenibile se mescoli mille schemi diversi in un unico tavolo! E sì, probabilmente hai bisogno di un codice personalizzato per i campi personalizzati. Ancora una volta è più semplice, non più difficile, se ogni cliente ha una tabella pulita e separata. Cercare di scegliere i campi dell'azienda X da un migliaio di altri è un casino di sangue.
Salterio

Ti riferisci alla mia risposta o all'idea dei PO di attaccare tutte le colonne extra sul tavolo del cliente?
Andy,

2
L'obiettivo qui è trovare una soluzione mantenibile e scalabile. La creazione di una tabella per cliente è decisamente l'opposto. Ogni volta che entri a bordo di un nuovo cliente, non è realistico: eseguire uno script di creazione tabella, aggiornare il codice (oggetti Entity) e ridistribuire.
tsOverflow,

L'intera idea di utilizzare tabelle condivise per tutti i clienti è di per sé una discussione sull'architettura SaaS separata e ci sono alcuni buoni motivi per mantenere i clienti in tabelle diverse (o anche in database diversi, consentendo il backup / ripristino per cliente e il ridimensionamento). In questo scenario, la creazione di colonne cusotm nella tabella principale è un gioco da ragazzi. Ho votato a fondo e mi chiedo perché la gente abbia votato in questo modo solo perché non gli piace questo approccio. L'effetto della piattaforma interna è una realtà: usando un modello EVA il tuo interrogatorio sarà più difficile, risparmiando di più, integrità più difficile, ecc.
drizin

-1

La mia soluzione presuppone che si chiamerebbe questa query da un programma e si dovrebbe essere in grado di eseguire l'elaborazione post. Puoi avere le seguenti colonne:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_VALUES

CUSTOM_VALUES sarà del tipo stringa che memorizza la coppia chiave e valori. la chiave sarà il nome della colonna e il valore sarà il valore della colonna, ad es

LIKE_MOVIE;yes;LIKE_MUSIC;no;FAV_CUISINE;rice

in questo CUSTOM_VALUES salverai solo le informazioni esistenti. Quando si esegue una query dal programma, è possibile dividere questa stringa e utilizzarla.

Ho usato questa logica e funziona benissimo, è solo che dovrai applicare la logica di filtraggio nel codice e non nelle query.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.