Як зберігати список у стовпці таблиці БД


114

Отже, за відповіддю Мехрдада на пов'язане питання , я розумію, що стовпець "належної" таблиці бази даних не зберігає список. Скоріше, ви повинні створити іншу таблицю, яка ефективно містить елементи згаданого списку, а потім посилатися на нього безпосередньо або через таблицю з'єднання. Однак тип списку, який я хочу створити, буде складатися з унікальних елементів (на відміну від плодів пов'язаного питанняприклад). Крім того, елементи в моєму списку чітко відсортовані - це означає, що якби я зберігав елементи в іншій таблиці, я повинен був би сортувати їх кожного разу, коли я звертався до них. Нарешті, список в основному є атомним, тому що в будь-який час, коли я хочу отримати доступ до списку, я захочу отримати доступ до всього списку, а не лише до його частини - тому здається дурним необхідність видати запит до бази даних, щоб зібрати разом шматки список.

Рішення AKX (пов'язане вище) полягає в серіалізації списку та зберіганні його у двійковій колонці. Але це також здається незручним, оскільки це означає, що я маю турбуватися про серіалізацію та десеріалізацію.

Чи є краще рішення? Якщо немає ніякого кращого рішення, то чому? Здається, ця проблема час від часу повинна виникати.

... ще трохи інформації, щоб повідомити, звідки я родом. Як тільки я почав розуміти SQL та бази даних взагалі, мене ввімкнули в LINQ до SQL, і тепер я трохи розбещений, тому що я розрахований на мою об'єктну модель програмування, не замислюючись про те, як об'єкти запитуються або зберігаються в базі даних.

Дякую усім!

Джон

ОНОВЛЕННЯ: Отже, під час першого шквалу відповідей, які я отримую, я бачу "ви можете піти по маршруту CSV / XML ... але НЕ!". Тому зараз я шукаю пояснення, чому. Наведіть на мене кілька хороших довідок

Також для кращого уявлення про те, що я збираюся: У своїй базі даних я маю таблицю функцій, у якій буде список (x, y) пар. (У таблиці також буде інша інформація, яка не має жодного наслідку для нашого обговорення.) Мені ніколи не потрібно буде бачити частину списку (x, y) пар. Швидше я візьму їх усіх і викладу на екрані. Я дозволю користувачеві перетягувати вузли навколо, щоб періодично змінювати значення або додавати більше значень до графіку.

Відповіді:


182

Ні, немає "кращого" способу зберігання послідовності елементів в одному стовпчику. Реляційні бази даних розроблені спеціально для зберігання одного значення на комбінацію рядків / стовпців. Щоб зберегти більше одного значення, ви повинні серіалізувати свій список в одне значення для зберігання, а потім дезаріалізувати його при пошуку. Немає іншого способу зробити те, про що ви говорите (адже те, про що ви говорите, - погана ідея, яку взагалі ніколи не слід робити ).

Я розумію, що ви думаєте, що нерозумно створити іншу таблицю для зберігання цього списку, але саме це і роблять реляційні бази даних. Ви боретеся у важкій битві і без поважних причин порушуєте один з найосновніших принципів реляційної бази даних. Оскільки ви заявляєте, що ви просто вивчаєте SQL, я б настійно радив вам уникати цієї ідеї та дотримуватися практики, рекомендованої вам більш досвідченими розробниками SQL.

Принцип, який ви порушуєте, називається першою нормальною формою , що є першим кроком у нормалізації бази даних.

На ризик спрощують речі, нормалізація бази даних являє собою процес визначення бази даних , грунтуючись на тому , що дані є , так що ви можете писати осмислені, послідовні запити до них і бути в змозі підтримувати його легко. Нормалізація покликана обмежувати логічні невідповідності та пошкодження ваших даних, і до них є багато рівнів. Стаття у Вікіпедії про нормалізацію бази даних насправді є досить хорошою.

В основному, перше правило (або форма) нормалізації передбачає, що ваша таблиця повинна представляти відношення. Це означає що:

  • Ви повинні мати можливість відрізняти один рядок від будь-якого іншого рядка (іншими словами, у таблиці має бути щось, що може слугувати первинним ключем. Це також означає, що жоден рядок не повинен дублюватися.
  • Будь-яке впорядкування даних повинно визначатися даними, а не фізичним упорядкуванням рядків (SQL заснований на ідеї набору, тобто єдине впорядкування, на яке слід покластися, - це те, яке ви чітко визначаєте у своєму запиті)
  • Кожен перетин рядка / стовпця повинен містити одне і лише одне значення

Остання точка, очевидно, є помітною точкою тут. SQL призначений для зберігання ваших наборів для вас, а не для того, щоб забезпечити вам «відро» для зберігання набору самостійно. Так, це можна зробити. Ні, світ не закінчиться. Однак ви вже покалічили себе в розумінні SQL та найкращих практик, що поєднуються з ним, негайно вскочивши в використання ORM. LINQ до SQL - це фантастично, як і графічні калькулятори. Однак, вони не повинні використовуватися як замінник того, щоб знати, як реально працюють процеси, які вони використовують.

Ваш список може бути повністю "атомним" зараз, і це може не змінитися для цього проекту. Але ви все-таки увійдете в звичку робити подібні речі в інших проектах, і в кінцевому підсумку (швидше за все, швидко) наткнетесь на сценарій, коли ви зараз підганяєте свій швидкий-n-простий список-в-стовпчик підхід, коли це зовсім недоцільно. Немає великої кількості додаткових робіт зі створення правильної таблиці для того, що ви намагаєтесь зберігати, і інші розробники SQL не будуть насміхатися, коли вони побачать дизайн вашої бази даних. Крім того, LINQ до SQL буде бачити ваше ставлення і дати вам правильний об'єктно-орієнтований інтерфейс до списку автоматично . Чому б ви відмовились від зручності, запропонованої вами ORM, щоб ви могли виконувати нестандартні та непродумані хакерські бази даних?


17
Тож ви твердо вважаєте, що зберігати список у стовпці - це погана ідея, але ви не можете зазначити, чому. Оскільки я тільки починаю з SQL, трохи "чому" було б дуже корисно. Наприклад, ви говорите, що я "веду боротьбу у важкій битві і порушую один із найосновніших принципів реляційної бази даних без поважних причин" ... так що ж це за принцип? Чому причини, які я назвав «недобрими»? (конкретно, відсортований та атомний характер моїх списків)
JnBrymn

6
В основному, це зводиться до багаторічного досвіду, зведеного в кращих практиках. Основна головна мова, про яку йдеться, відома як перша нормальна форма .
Тобі

1
Спасибі Адам. Дуже інформативні. Хороший момент із вашим останнім запитанням.
JnBrymn

8
"[…], І ви не будете знущатися з іншими розробниками SQL, коли вони побачать дизайн вашої бази даних." Є дуже вагомі причини поважати Першу нормальну форму (і ваша відповідь згадує про них), але тиск з боку однолітків / «ось так все робиться тут» немає серед них.
Лінн

5
Ми вже зберігаємо групи списків у стовпцях бази даних щодня. Їх називають "чар" і "варчар". Звичайно, в Postgres їх також називають текстом. Що насправді говорить 1NF, це те, що ви ніколи не хочете розбивати інформацію в будь-якому полі на більш дрібні поля, і якщо ви це зробите, ви поглухалися. Таким чином, ви не зберігаєте ім’я, ви зберігаєте особисте ім’я, прізвища та прізвища (залежно від локалізації) та зшивайте їх разом. Інакше ми взагалі не зберігатимемо рядки тексту. З іншого боку, все, що він хоче - це струнна струна. І для цього є способи.
Хаакон Лотвейт

15

Ви можете просто забути SQL всі разом і піти з підходом "NoSQL". RavenDB , MongoDB і CouchDB приходять до розуму як можливі рішення. За допомогою підходу NoSQL ви не використовуєте реляційну модель. Ви навіть не обмежуєтесь схемами.


11

Те, що я бачив, багато людей це - це (це може бути не найкращий підхід, виправте мене, якщо я помиляюся):

Таблиця, яку я використовую у прикладі, наведена нижче (таблиця містить псевдоніми, які ви дали вашим конкретним подругам. Кожна дівчина має унікальний ідентифікатор):

nicknames(id,seq_no,names)

Припустимо, ви хочете зберегти безліч прізвищ під ідентифікатором. Ось чому ми включили:seq_no поле.

Тепер заповніть ці значення у таблиці:

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

Якщо ви хочете знайти всі імена, які ви дали вашому другові дівчинці id 1, тоді ви можете скористатися:

select names from nicknames where id = 1;

5

Проста відповідь: Якщо, і лише якщо ви впевнені, що список завжди буде використовуватися як список, тоді приєднайтесь до цього списку разом із символом (наприклад, "\ 0"), який не буде використаний у текст будь-коли, і зберігайте це. Потім, коли ви отримаєте його, ви можете розділити на "\ 0". Звичайно, існують інші способи вирішення цього питання, але вони залежать від конкретного постачальника баз даних.

Як приклад, ви можете зберігати JSON в базі даних Postgres. Якщо ваш список є текстовим, і ви просто хочете список без зайвих клопотів, це розумний компроміс.

Інші ризикнули запропонувати серіалізацію, але я не думаю, що серіалізація - це гарна ідея. Частина акуратного питання баз даних полягає в тому, що кілька програм, написаних різними мовами, можуть спілкуватися між собою. І програми, серіалізовані у форматі Java, не зробили б все так добре, якби програма Lisp хотіла її завантажити.

Якщо ви хочете зробити хороший спосіб зробити подібні речі, зазвичай доступні масиви чи подібні типи. Наприклад, Postgres пропонує масив як тип і дозволяє зберігати масив тексту, якщо це те, що ви хочете , і є подібні хитрощі для MySql та MS SQL за допомогою JSON, а DB2 IBM пропонує також тип масиву (у їх власна корисна документація). Це було б не так часто, якби в цьому не було потреби.

Те, що ви втрачаєте, рухаючись цією дорогою, - це поняття списку як купа речей послідовно. Принаймні номінально, бази даних трактують поля як єдині значення. Але якщо це все, що ви хочете, тоді вам слід піти на це. Це ціннісне судження, яке ви повинні зробити для себе.


3

Крім того, що всі інші сказали, я б запропонував вам проаналізувати свій підхід довше, ніж просто зараз. Це в даний час так , що елементи є унікальними. Це в даний час так , що звернення пунктів потребують новий список. Майже потрібно, щоб цей список зараз був коротким. Незважаючи на те, що у мене немає специфіки домену, думати, що ці вимоги можуть змінитись, не дуже сильно. Якщо ви серіалізуєте свій список, ви випікаєте негнучкість, яка не потрібна в більш нормованому дизайні. До речі, це не обов'язково означає повне багато стосунків. Ви можете просто мати одну дочірню таблицю з іноземним ключем від батьків та стовпцем символів для елемента.

Якщо ви все ще хочете піти цією дорогою серіалізації списку, ви можете розглянути можливість зберігання списку в XML. Деякі бази даних, такі як SQL Server, навіть мають тип даних XML. Єдина причина, яку я припускаю, що XML - це те, що майже за визначенням цей список повинен бути коротким. Якщо список довгий, то серіалізація його взагалі - жахливий підхід. Якщо ви йдете по маршруту CSV, вам потрібно врахувати значення, що містять роздільник, що означає, що ви змушені використовувати ідентифіковані котирування. Якщо вважати, що списки короткі, це, ймовірно, не матиме великої різниці, використовуєте ви CSV чи XML.


+1 для передбачення майбутніх змін - завжди розробляйте модель даних як розширювану.
coolgeek

2

Я б просто зберігав його як CSV, якщо це прості значення, то він повинен бути всім, що вам потрібно (XML дуже багатослівний, і серіалізація до / з нього, ймовірно, буде надмірною, але це також буде варіантом).

Ось хороша відповідь про те, як витягнути CSV з LINQ.


Я хоч про це. Це все ще означає, що мені доведеться серіалізувати і десеріалізувати ... але я підозрюю, що це можливо. Я б хотів, щоб був якийсь поблажливий спосіб робити те, що я хочу, але я підозрюю, що немає.
JnBrymn

capnproto.org - це спосіб не потребувати серіалізації та десеріалізації, аналогічно швидкому (порівняно з csv або xml), якщо capnproto не підтримується вашою обраною
VoronoiPotato

2

Якщо вам потрібно здійснити запит у списку, зберігайте його в таблиці.

Якщо ви завжди хочете список, ви можете зберігати його як розділений список у стовпці. Навіть у цьому випадку, якщо у вас є ДУЖЕ конкретні причини цього не робити, зберігайте їх у таблиці пошуку.


1

У відповідях не згадується лише один варіант. Ви можете денормалізувати дизайн БД. Тож вам потрібно дві таблиці. Одна таблиця містить належний список, один елемент у рядку, інша таблиця містить цілий список в одному стовпчику (наприклад, розділена комою).

Ось це "традиційний" дизайн БД:

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

Ось це денормована таблиця:

Lists(ListID, ListContent)

Ідея тут - ви підтримуєте таблицю списків за допомогою тригерів або коду програми. Кожен раз, коли ви змінюєте вміст List_Item, відповідні рядки в Списках оновлюються автоматично. Якщо ви в основному читаєте списки, це може спрацювати непогано. Плюси - списки можна прочитати в одному виписці. Мінуси - оновлення потребує більше часу та зусиль.


0

Якщо ви дійсно хотіли зберігати його у стовпчику та мати його для перегляду, багато баз даних зараз підтримують XML. Якщо немає запиту, ви можете зберігати їх як значення, розділені комами, і аналізувати їх за допомогою функції, коли вам потрібно їх розділити. Я погоджуюся з усіма іншими, хоча якщо ви хочете використовувати реляційну базу даних, велика частина нормалізації - це відокремлення подібних даних. Я не кажу, що всі дані відповідають реляційній базі даних. Ви завжди можете заглянути в інші типи баз даних, якщо багато ваших даних не відповідають моделі.


0

Я думаю, що в певних випадках можна створити ФАЙКОВИЙ "список" елементів у базі даних, наприклад, у товару є кілька зображень, щоб показати його деталі, ви можете об'єднати всі ідентифікатори зображень, розділених комою, і зберегти рядок у БД, тоді вам просто потрібно розібрати рядок, коли вам це потрібно. Зараз я працюю на веб-сайті, і я планую використовувати цей спосіб.


0

Мені було дуже неохоче вибирати шлях, який я нарешті вирішу пройти через багато відповідей. Хоча вони додають більше розуміння того, що таке SQL та його принципи, я вирішив стати поза законом. Я також вагався розмістити свої висновки, оскільки для деяких важливіше роздуватися, коли хтось порушує правила, а не розуміти, що універсальних прав.

Я перевірив це досить широко, і в моєму конкретному випадку він виявився набагато ефективнішим, ніж використання типу масиву (щедро запропонованого PostgreSQL) або запиту іншої таблиці.

Ось моя відповідь: я успішно реалізував список в одному полі в PostgreSQL, використовуючи фіксовану довжину кожного елемента списку. Скажімо, кожен елемент є кольором як шістнадцяткове значення ARGB, це означає 8 знаків. Таким чином, ви можете створити масив максимум 10 елементів, помноживши на довжину кожного елемента:

ALTER product ADD color varchar(80)

Якщо довжина елементів списку відрізняється, ви завжди можете заповнити прокладку \ 0

NB: Очевидно, що це необов'язково найкращий підхід для шістнадцяткового числа, оскільки список цілих чисел забирає менше місця для зберігання, але це лише для того, щоб проілюструвати цю ідею масиву, використовуючи фіксовану довжину, виділену кожному елементу.

Причина, чому: 1 / Дуже зручно: витягніть елемент i в підрядку i * n, (i +1) * n. 2 / Немає накладних запитів між таблицями. 3 / Більш ефективна та економія коштів на стороні сервера. Список нагадує міні-крапку, яку клієнту доведеться розколоти.

Хоча я поважаю людей, що дотримуються правил, багато пояснень є дуже теоретичними і часто не визнають, що в деяких конкретних випадках, особливо при прагненні оптимальних витрат із рішеннями з низькою затримкою, деякі незначні зміни є більш ніж вітаються.

"Не дай Бог, щоб це порушило якийсь священний священний принцип SQL": Застосування більш відкритого та прагматичного підходу до декламації правил - це завжди шлях. Інакше ти можеш опинитися як відвертий фанатик, декламує Три закони робототехніки, перш ніж її скасувати Skynet

Я не претендую на те, що це рішення є проривним і не є ідеальним з точки зору читабельності та гнучкості бази даних, але, безумовно, може дати вам перевагу, коли мова йде про затримку.


Але це дуже специфічний випадок: фіксована кількість предметів фіксованої довжини. Навіть тоді це робить простий пошук на кшталт "всі продукти, що мають принаймні колір x", складніше, ніж стандартний SQL.
Герт Арнольд

Як я вже неодноразово заявляв, я не використовую його для кольору, поле, в якому я його використовую, не повинно індексуватись і не використовуватись як умова, і все ж воно є критичним
Антонін ГАВРЕЛЬ

Я знаю, я намагаюся зазначити, що це дуже специфічно. Якщо якась невелика додаткова вимога прокрадеться, вона швидко стає незручнішою, ніж стандартні рішення. Переважній більшості людей, які спокушаються зберігати списки в одному db-полі, ймовірно, краще не робити цього.
Герт Арнольд

0

Багато баз даних SQL дозволяють таблиці містити підменю як компонент. Звичайний метод - дозволити домен одного з стовпців бути таблицею. Це на додаток до використання деякої умовності на зразок CSV для кодування підструктури способами, невідомими СУБД.

Коли в 1969-1970 рр. Ед Кодд розробляв реляційну модель, він спеціально визначив нормальну форму, яка б забороняла подібне введення таблиць. Нормальна форма пізніше отримала назву Перша нормальна форма. Потім він показав, що для кожної бази даних існує база даних у першій нормальній формі, яка виражає ту саму інформацію.

Чому це турбує? Ну а бази даних у першій нормальній формі дозволяють мати доступ до всіх даних. Якщо ви вказали ім'я таблиці, ключове значення для цієї таблиці та ім’я стовпця, база даних міститиме щонайменше одну клітинку, що містить один елемент даних.

Якщо ви дозволите комірці містити список або таблицю або будь-яку іншу колекцію, тепер ви не можете надати доступ до клавіш до підпунктів, не повністю переробивши ідею ключа.

Класифікований доступ до всіх даних є основою реляційної моделі. Без цієї концепції модель не є реляційною. Щодо того, чому реляційна модель є гарною ідеєю, і які можуть бути обмеження цієї хорошої ідеї, ви повинні подивитися на 50-річний накопичений досвід роботи з реляційною моделлю.


-1

ви можете зберігати його як текст, схожий на список, і створити функцію, яка може повертати його дані як фактичний список. приклад:

база даних:

 _____________________
|  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]'___|

І функція компілятора списку (написана в python, але вона повинна легко перекладатися на більшість інших мов програмування). ТЕКСТ представляє текст, завантажений з таблиці sql. повертає список рядків із рядка, що містить список. якщо ви хочете, щоб він повернув ints замість рядків, зробіть режим рівним 'int'. Аналогічно з 'string', 'bool' або '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

Також тут є функція списку до рядка, якщо вона вам потрібна.

def list_to_string(lst):
    string = "["
    for i in lst:
        string += str(i) + ","
    if string[-1] == ',':
        string = string[:-1] + "]"
    else:
        string += "]"
    return string
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.