Cómo almacenar una lista en una columna de una tabla de base de datos

115

Entonces, según la respuesta de Mehrdad a una pregunta relacionada , entiendo que una columna de tabla de base de datos "adecuada" no almacena una lista. Más bien, debe crear otra tabla que contenga efectivamente los elementos de dicha lista y luego vincularla directamente o mediante una tabla de unión. Sin embargo, el tipo de lista que quiero crear estará compuesto por elementos únicos (a diferencia de la fruta de la pregunta vinculadaejemplo). Además, los elementos de mi lista están ordenados explícitamente, lo que significa que si almacenaba los elementos en otra tabla, tendría que ordenarlos cada vez que acceda a ellos. Finalmente, la lista es básicamente atómica en el sentido de que cada vez que desee acceder a la lista, querré acceder a la lista completa en lugar de solo a una parte de ella, por lo que parece una tontería tener que emitir una consulta de base de datos para recopilar partes de la lista.

La solución de AKX (vinculada arriba) es serializar la lista y almacenarla en una columna binaria. Pero esto también parece inconveniente porque significa que tengo que preocuparme por la serialización y deserialización.

¿Existe alguna solución mejor? Si no es ninguna solución mejor, entonces ¿por qué? Parece que este problema debería surgir de vez en cuando.

... solo un poco más de información para que sepas de dónde vengo. Tan pronto como comencé a comprender SQL y las bases de datos en general, me encendí con LINQ to SQL, y ahora estoy un poco malcriado porque espero lidiar con mi modelo de objetos de programación sin tener que pensar en cómo los objetos son consultados o almacenados en la base de datos.

¡Gracias a todos!

Juan

ACTUALIZACIÓN: Entonces, en la primera oleada de respuestas que obtengo, veo "puedes ir por la ruta CSV / XML ... ¡pero NO HAGAS!". Así que ahora estoy buscando explicaciones de por qué. Indícame algunas buenas referencias.

Además, para darte una mejor idea de lo que estoy haciendo: En mi base de datos tengo una tabla de funciones que tendrá una lista de pares (x, y). (La tabla también tendrá otra información que no tiene importancia para nuestra discusión). Nunca necesitaré ver parte de la lista de pares (x, y). Más bien, los tomaré todos y los trazaré en la pantalla. Permitiré que el usuario arrastre los nodos para cambiar los valores ocasionalmente o agregar más valores al gráfico.

JnBrymn
fuente

Respuestas:

183

No, no existe una forma "mejor" de almacenar una secuencia de elementos en una sola columna. Las bases de datos relacionales están diseñadas específicamente para almacenar un valor por combinación de fila / columna. Para almacenar más de un valor, debe serializar su lista en un solo valor para el almacenamiento, luego deserializarlo al recuperarlo. No hay otra forma de hacer lo que estás hablando (porque lo que estás hablando es una mala idea que, en general, nunca debería hacerse ).

Entiendo que pienses que es una tontería crear otra tabla para almacenar esa lista, pero esto es exactamente lo que hacen las bases de datos relacionales. Estás librando una batalla cuesta arriba y violando uno de los principios más básicos del diseño de bases de datos relacionales sin una buena razón. Como dice que está aprendiendo SQL, le recomiendo encarecidamente que evite esta idea y que se ciña a las prácticas recomendadas por los desarrolladores de SQL más experimentados.

El principio que está violando se llama primera forma normal , que es el primer paso en la normalización de la base de datos.

A riesgo de simplificar demasiado las cosas, la normalización de bases de datos es el proceso de definición de la base de datos en base a lo que los datos es , por lo que se puede escribir consultas sensibles, consistentes en contra de ella y ser capaz de mantener fácilmente. La normalización está diseñada para limitar las inconsistencias lógicas y la corrupción en sus datos, y tiene muchos niveles. El artículo de Wikipedia sobre normalización de bases de datos es bastante bueno.

Básicamente, la primera regla (o forma) de normalización establece que su tabla debe representar una relación. Esto significa que:

  • Debe poder diferenciar una fila de cualquier otra fila (en otras palabras, su tabla debe tener algo que pueda servir como clave principal. Esto también significa que no se debe duplicar ninguna fila.
  • Cualquier orden de los datos debe estar definido por los datos, no por el orden físico de las filas (SQL se basa en la idea de un conjunto, lo que significa que el único orden en el que debe confiar es el que defina explícitamente en su consulta)
  • Cada intersección de fila / columna debe contener un solo valor

El último punto es obviamente el punto más destacado aquí. SQL está diseñado para almacenar sus conjuntos por usted, no para proporcionarle un "cubo" para que usted mismo almacene un conjunto. Sí, es posible hacerlo. No, el mundo no se acabará. Sin embargo, ya se ha visto afectado por la comprensión de SQL y las mejores prácticas que lo acompañan al saltar inmediatamente al uso de un ORM. LINQ to SQL es fantástico, al igual que las calculadoras gráficas. En la misma línea, sin embargo, deben no ser utilizado como un sustituto para saber cómo los procesos que emplean realmente el trabajo.

Su lista puede ser completamente "atómica" ahora, y eso no puede cambiar para este proyecto. Pero, sin embargo, adquirirá el hábito de hacer cosas similares en otros proyectos, y eventualmente (probablemente rápidamente) se encontrará con un escenario en el que ahora está ajustando su lista rápida y fácil en una columna. enfoque donde es totalmente inapropiado. No hay mucho trabajo adicional para crear la tabla correcta para lo que está tratando de almacenar, y otros desarrolladores de SQL no se burlarán de usted cuando vean el diseño de su base de datos. Además, LINQ to SQL verá su relación y le dará la interfaz orientada a objetos adecuada a su lista automáticamente . ¿Por qué renunciar a la conveniencia que le ofrece el ORM para poder realizar piratería de bases de datos no estándar y poco aconsejable?

Adam Robinson
fuente
17
De modo que cree firmemente que almacenar una lista en una columna es una mala idea, pero no menciona por qué. Ya que recién estoy comenzando con SQL, un poco del "por qué" sería muy útil. Por ejemplo, usted dice que estoy "librando una batalla cuesta arriba y violando uno de los principios más básicos del diseño de bases de datos relacionales sin una buena razón" ... entonces, ¿cuál es el principio? ¿Por qué las razones que cité "no son buenas"? (específicamente, la naturaleza ordenada y atómica de mis listas)
JnBrymn
6
Básicamente, se trata de años de experiencia condensados ​​en mejores prácticas. El principal básico en cuestión se conoce como 1ª Forma Normal .
Toby
1
Gracias Adam. Muy informativo. Buen punto con tu última pregunta.
JnBrymn
8
“[…] Y no se burlarán de usted otros desarrolladores de SQL cuando vean el diseño de su base de datos”. Hay muy buenas razones para respetar la Primera Forma Normal (y su respuesta las menciona), pero la presión de grupo / “así es como se hacen las cosas por aquí” no se encuentra entre ellas.
Lynn
5
Ya almacenamos grupos de listas en columnas de base de datos todos los días. Se llaman "char" y "varchar". Por supuesto, en Postgres, también se llaman texto. Lo que la 1NF realmente dice es que nunca debería desear dividir la información en ningún campo en campos más pequeños, y si lo hace, no se equivocará. Por lo tanto, no almacena el nombre, almacena el nombre personal, los segundos nombres y los apellidos (según la localización) y los une. De lo contrario, no almacenaríamos cadenas de texto en absoluto. Por otro lado, todo lo que quiere es una cadena de hilos. Y hay formas de hacerlo.
Haakon Løtveit
15

Puede simplemente olvidarse de SQL por completo e ir con un enfoque "NoSQL". RavenDB , MongoDB y CouchDB vienen a la mente como posibles soluciones. Con un enfoque NoSQL, no está utilizando el modelo relacional ... ni siquiera está limitado a esquemas.

jaltiere
fuente
11

Lo que he visto hacer a mucha gente es esto (puede que no sea el mejor enfoque, corrígeme si me equivoco):

La tabla que estoy usando en el ejemplo se muestra a continuación (la tabla incluye apodos que le ha dado a sus novias específicas. Cada novia tiene una identificación única):

nicknames(id,seq_no,names)

Supongamos que desea almacenar muchos apodos bajo una identificación. Por eso hemos incluido un seq_nocampo.

Ahora, complete estos valores en su tabla:

(1,1,'sweetheart'), (1,2,'pumpkin'), (2,1,'cutie'), (2,2,'cherry pie')

Si desea encontrar todos los nombres que le ha dado a su novia id 1, puede usar:

select names from nicknames where id = 1;
H. Pauwelyn
fuente
5

Respuesta simple: si, y solo si, está seguro de que la lista siempre se usará como una lista, entonces únase a la lista al final con un carácter (como '\ 0') que no se usará en el mensaje de texto nunca, y almacenarlo. Luego, cuando lo recupere, puede dividirlo por '\ 0'. Por supuesto, hay otras formas de hacer esto, pero dependen de su proveedor de base de datos específico.

Como ejemplo, puede almacenar JSON en una base de datos de Postgres. Si su lista es de texto y solo quiere la lista sin más problemas, ese es un compromiso razonable.

Otros han aventurado sugerencias de serialización, pero realmente no creo que serializar sea una buena idea: parte de lo bueno de las bases de datos es que varios programas escritos en diferentes lenguajes pueden comunicarse entre sí. Y los programas serializados usando el formato de Java no funcionarían tan bien si un programa Lisp quisiera cargarlo.

Si desea una buena manera de hacer este tipo de cosas, generalmente hay tipos de matrices o similares disponibles. Postgres, por ejemplo, ofrece una matriz como un tipo y le permite almacenar una matriz de texto, si eso es lo que desea , y existen trucos similares para MySql y MS SQL usando JSON, y DB2 de IBM también ofrece un tipo de matriz (en su propia documentación útil ). Esto no sería tan común si no fuera necesario.

Lo que pierdes al seguir ese camino es la noción de la lista como un montón de cosas en secuencia. Al menos nominalmente, las bases de datos tratan los campos como valores únicos. Pero si eso es todo lo que quieres, entonces deberías hacerlo. Es un juicio de valor que tienes que hacer por ti mismo.

Haakon Løtveit
fuente
3

Además de lo que han dicho todos los demás, le sugiero que analice su enfoque en términos más largos que ahora. Es actualmente el caso de que los artículos son únicos. Es actualmente el caso de que el recurso a los artículos requeriría una nueva lista. Es casi necesario que la lista sea actualmente corta. Aunque no tengo los detalles del dominio, no es muy exagerado pensar que esos requisitos podrían cambiar. Si serializa su lista, está horneando con una inflexibilidad que no es necesaria en un diseño más normalizado. Por cierto, eso no significa necesariamente una relación Many: Many completa. Podría tener una única tabla secundaria con una clave externa para el padre y una columna de caracteres para el elemento.

Si aún desea seguir este camino de serializar la lista, podría considerar almacenar la lista en XML. Algunas bases de datos, como SQL Server, incluso tienen un tipo de datos XML. La única razón por la que sugeriría XML es que, casi por definición, esta lista debe ser corta. Si la lista es larga, serializarla en general es un enfoque terrible. Si sigue la ruta CSV, debe tener en cuenta los valores que contienen el delimitador, lo que significa que está obligado a utilizar identificadores entre comillas. Suponiendo que las listas son cortas, probablemente no hará mucha diferencia si usa CSV o XML.

Thomas
fuente
+1 para anticipar cambios futuros: siempre diseñe su modelo de datos para que sea extensible.
coolgeek
2

Simplemente lo almacenaría como CSV, si se trata de valores simples, entonces debería ser todo lo que necesita (XML es muy detallado y la serialización hacia / desde él probablemente sería excesivo, pero esa también sería una opción).

Aquí hay una buena respuesta sobre cómo extraer archivos CSV con LINQ.

David Neale
fuente
Pensé en eso. Todavía significa que tendría que serializar y deserializar ... pero sospecho que es factible. Ojalá hubiera alguna forma tolerada de hacer lo que quiero, pero sospecho que no la hay.
JnBrymn
capnproto.org es una forma de no tener que serializar y deserializar, igualmente rápido (en comparación con csv o xml) en caso de que capnproto no sea compatible con el idioma de su elección msgpack.org/index.html
VoronoiPotato
2

Si necesita realizar una consulta en la lista, guárdelo en una tabla.

Si siempre desea la lista, puede almacenarla como una lista delimitada en una columna. Incluso en este caso, a menos que tenga razones MUY específicas para no hacerlo, guárdelo en una tabla de búsqueda.

tostadas caseras
fuente
1

Solo una opción no se menciona en las respuestas. Puede desnormalizar su diseño de base de datos. Entonces necesitas dos mesas. Una tabla contiene la lista adecuada, un elemento por fila, otra tabla contiene la lista completa en una columna (separada por coma, por ejemplo).

Aquí está el diseño de base de datos 'tradicional':

List(ListID, ListName) 
Item(ItemID,ItemName) 
List_Item(ListID, ItemID, SortOrder)

Aquí está la tabla desnormalizada:

Lists(ListID, ListContent)

La idea aquí: mantienes la tabla de Listas usando desencadenadores o código de aplicación. Cada vez que modifica el contenido de List_Item, las filas correspondientes en Lists se actualizan automáticamente. Si lee principalmente listas, podría funcionar bastante bien. Ventajas: puede leer listas en una sola declaración. Contras: las actualizaciones requieren más tiempo y esfuerzo.

Alsin
fuente
0

Si realmente desea almacenarlo en una columna y poder consultarlo, muchas bases de datos ahora admiten XML. Si no está consultando, puede almacenarlos como valores separados por comas y analizarlos con una función cuando los necesite separados. Estoy de acuerdo con todos los demás, sin embargo, si está buscando usar una base de datos relacional, una gran parte de la normalización es la separación de datos como ese. Sin embargo, no estoy diciendo que todos los datos se ajusten a una base de datos relacional. Siempre puede buscar otros tipos de bases de datos si muchos de sus datos no se ajustan al modelo.

David Daniel
fuente
0

Creo que en ciertos casos, puede crear una "lista" FALSA de elementos en la base de datos, por ejemplo, la mercancía tiene algunas imágenes para mostrar sus detalles, puede concatenar todas las ID de imágenes divididas por comas y almacenar la cadena en la base de datos, entonces solo necesita analizar la cadena cuando la necesite. Ahora estoy trabajando en un sitio web y planeo usarlo de esta manera.

Nen
fuente
0

Estaba muy reacio a elegir el camino que finalmente decidí tomar debido a muchas respuestas. Si bien añaden más comprensión a lo que es SQL y sus principios, decidí convertirme en un forajido. También dudé en publicar mis hallazgos, ya que para algunos es más importante desahogar la frustración con alguien que rompe las reglas en lugar de comprender que hay muy pocas verdades universales.

Lo probé ampliamente y, en mi caso específico, fue mucho más eficiente que usar el tipo de matriz (ofrecido generosamente por PostgreSQL) o consultar otra tabla.

Aquí está mi respuesta: he implementado con éxito una lista en un solo campo en PostgreSQL, haciendo uso de la longitud fija de cada elemento de la lista. Digamos que cada elemento es un color como valor hexadecimal ARGB, significa 8 caracteres. Por lo tanto, puede crear su matriz de un máximo de 10 elementos multiplicando por la longitud de cada elemento:

ALTER product ADD color varchar(80)

En caso de que la longitud de los elementos de su lista difiera, siempre puede llenar el relleno con \ 0

NB: Obviamente, este no es necesariamente el mejor enfoque para el número hexadecimal, ya que una lista de enteros consumiría menos almacenamiento, pero esto es solo con el propósito de ilustrar esta idea de matriz haciendo uso de una longitud fija asignada a cada elemento.

La razón por la cual: 1 / Muy conveniente: recupere el elemento i en la subcadena i * n, (i +1) * n. 2 / Sin gastos generales de consultas de tablas cruzadas. 3 / Más eficiente y económico en el lado del servidor. La lista es como un mini blob que el cliente tendrá que dividir.

Si bien respeto a las personas que siguen las reglas, muchas explicaciones son muy teóricas y, a menudo, no reconocen que, en algunos casos específicos, especialmente cuando se busca un costo óptimo con soluciones de baja latencia, algunos ajustes menores son más que bienvenidos.

"Dios no quiera que esté violando algún principio sagrado y sagrado de SQL": Adoptar un enfoque más abierto y pragmático antes de recitar las reglas es siempre el camino a seguir. De lo contrario, podrías terminar como un fanático sincero recitando las Tres leyes de la robótica antes de ser aniquilado por Skynet.

No pretendo que esta solución sea un gran avance, ni que sea ideal en términos de legibilidad y flexibilidad de la base de datos, pero ciertamente puede darle una ventaja cuando se trata de latencia.

Antonin GAVREL
fuente
Pero este es un caso muy específico: un número fijo de elementos de longitud fija. Incluso entonces, hace una búsqueda simple como "todos los productos que tengan al menos el color x" más difícil de lo que lo haría SQL estándar.
Gert Arnold
Como dije varias veces, no lo uso para el color, el campo para el que lo uso no debe indexarse ​​ni usarse como una condición y, sin embargo, es crítico
Antonin GAVREL
Lo sé, estoy tratando de indicar que esto es muy específico. Si algún pequeño requisito adicional se cuela, rápidamente se vuelve más incómodo que las soluciones estándar. La gran mayoría de las personas que se sienten tentadas a almacenar listas en un campo de base de datos probablemente sea mejor que no lo hagan.
Gert Arnold
0

Muchas bases de datos SQL permiten que una tabla contenga una subtabla como componente. El método habitual es permitir que el dominio de una de las columnas sea una tabla. Esto se suma al uso de alguna convención como CSV para codificar la subestructura de formas desconocidas para el DBMS.

Cuando Ed Codd estaba desarrollando el modelo relacional en 1969-1970, definió específicamente una forma normal que no permitiría este tipo de anidamiento de tablas. La forma normal se denominó más tarde Primera forma normal. Luego pasó a mostrar que para cada base de datos, hay una base de datos en la primera forma normal que expresa la misma información.

¿Por qué molestarse con esto? Bueno, las bases de datos en la primera forma normal permiten el acceso con clave a todos los datos. Si proporciona un nombre de tabla, un valor clave en esa tabla y un nombre de columna, la base de datos contendrá como máximo una celda que contenga un elemento de datos.

Si permite que una celda contenga una lista, una tabla o cualquier otra colección, ahora no puede proporcionar acceso con clave a los subelementos sin reelaborar por completo la idea de una clave.

El acceso con clave a todos los datos es fundamental para el modelo relacional. Sin este concepto, el modelo no es relacional. En cuanto a por qué el modelo relacional es una buena idea y cuáles podrían ser las limitaciones de esa buena idea, hay que mirar los 50 años de experiencia acumulada con el modelo relacional.

Walter Mitty
fuente
-1

puede almacenarlo como texto que parece una lista y crear una función que pueda devolver sus datos como una lista real. ejemplo:

base de datos:

 _____________________
|  word  | letters    |
|   me   | '[m, e]'   |
|  you   |'[y, o, u]' |  note that the letters column is of type 'TEXT'
|  for   |'[f, o, r]' |
|___in___|_'[i, n]'___|

Y la función del compilador de listas (escrita en Python, pero debería ser fácilmente traducible a la mayoría de los otros lenguajes de programación). TEXT representa el texto cargado desde la tabla sql. devuelve la lista de cadenas de la cadena que contiene la lista. si desea que devuelva ints en lugar de cadenas, haga que el modo sea igual a 'int'. Lo mismo ocurre con 'string', 'bool' o 'float'.

def string_to_list(string, mode):
    items = []
    item = ""
    itemExpected = True
    for char in string[1:]:
        if itemExpected and char not in [']', ',', '[']:
            item += char
        elif char in [',', '[', ']']:
            itemExpected = True
            items.append(item)
            item = ""
    newItems = []
    if mode == "int":
        for i in items:
            newItems.append(int(i))

    elif mode == "float":
        for i in items:
            newItems.append(float(i))

    elif mode == "boolean":
        for i in items:
            if i in ["true", "True"]:
                newItems.append(True)
            elif i in ["false", "False"]:
                newItems.append(False)
            else:
                newItems.append(None)
    elif mode == "string":
        return items
    else:
        raise Exception("the 'mode'/second parameter of string_to_list() must be one of: 'int', 'string', 'bool', or 'float'")
    return newItems

También aquí hay una función de lista a cadena en caso de que la necesite.

def list_to_string(lst):
    string = "["
    for i in lst:
        string += str(i) + ","
    if string[-1] == ',':
        string = string[:-1] + "]"
    else:
        string += "]"
    return string
persona el humano
fuente