Właściwy sposób przechowywania wartości, która może być wieloma różnymi typami

11

Mam tabelę odpowiedzi i tabelę pytań .

W tabeli Odpowiedzi ma wartość, ale w zależności od pytania, wartość ta może być bit, nvarcharalbo number(jak dotąd). Pytanie ma pojęcia co jej przeznaczone typ wartości odpowiedź powinna być.

Ważne będzie, aby parsować te wartości odpowiedzi w jednym punkcie, ponieważ przynajmniej liczby będą musiały zostać porównane.

Dla nieco większego kontekstu pytania i potencjalne odpowiedzi (zazwyczaj typ danych dozwolony dla danych wejściowych typu pola tekstowego) są dostarczane przez niektórych użytkowników w pewnego rodzaju ankiecie. Odpowiedzi są następnie dostarczane przez innych określonych użytkowników.

Kilka opcji, które rozważałem to:

A. XML lub łańcuch, który jest analizowany w różny sposób w zależności od zamierzonego typu (który jest śledzony w pytaniu)

B. Trzy oddzielne tabele, które odwołują się do tabeli odpowiedzi (lub do których się odwołują), i są połączone w oparciu o zamierzony typ. W tym przypadku nie jestem pewien, jak najlepiej ustawić ograniczenia, aby każde pytanie miało tylko jedną odpowiedź, czy też należy to pozostawić aplikacji.

C. Trzy oddzielne kolumny w tabeli odpowiedzi, które można pobrać w zależności od zamierzonego typu.

Byłbym szczęśliwy, mogąc uzyskać informacje na temat zalet i wad tych podejść lub alternatywnych podejść, których nie rozważałem.

David Garrison
źródło

Odpowiedzi:

2

To naprawdę zależy od tego, jak Twój fronton uzyskuje dostęp do danych.

Jeśli używasz mapera O / R, skoncentruj się na obiektowym projekcie swoich klas, a nie na projekcie bazy danych. Baza danych następnie odzwierciedla projekt klasy. Dokładny projekt db zależy od używanego mapera O / R i modelu mapowania dziedziczenia.

Jeśli uzyskujesz dostęp do tabel bezpośrednio przez zestawy rekordów, tabele danych, czytniki danych itp., Prostą rzeczą jest konwersja wartości na ciąg przy użyciu niezmiennej kultury i przechowywanie jej w prostej kolumnie tekstowej . I, oczywiście, użyj tej samej kultury ponownie, aby przekonwertować tekst z powrotem na specjalistyczne typy wartości podczas czytania wartości.

Alternatywnie możesz użyć jednej kolumny dla każdego typu wartości. Dzisiaj mamy dyski terabajtowe!

Kolumna XML jest możliwa, ale prawdopodobnie zwiększa złożoność w porównaniu z prostą kolumną tekstową i robi prawie to samo, mianowicie serializowanie / deserializowanie.

Oddzielone połączone tabele to poprawny znormalizowany sposób robienia rzeczy; dodają jednak również trochę złożoności.

Nie komplikuj.

Zobacz także moją odpowiedź na projekt bazy danych kwestionariuszy - który sposób jest lepszy? .

Olivier Jacot-Descombes
źródło
4

Na podstawie tego, co powiedziałeś, użyłbym następującego ogólnego schematu:

CREATE TABLE [dbo].[PollQuestion]
(
    [PollQuestionId] INT NOT NULL PRIMARY KEY IDENTITY,
    [QuestionText] NVARCHAR(150) NOT NULL, -- Some reasonable character limit
    [Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
    [Archived] DATETIME2(2) NULL,  -- Remove this if you don't need to hide questions
)
CREATE TABLE [dbo].[PollOption]
(
    [PollOptionId] INT NOT NULL PRIMARY KEY IDENTITY,
    [PollQuestionId] INT NOT NULL,  -- Link to the question here because options aren't shared across questions
    [OptionText] NVARCHAR(50) NOT NULL, -- Some reasonable character limit
    [Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
    [Archived] DATETIME2(2) NULL  -- Remove this if you don't need to hide options

    CONSTRAINT [FK_PollOption_PollQuestionId_to_PollQuestion_PollQuestionId] FOREIGN KEY ([PollQuestionId]) REFERENCES [dbo].[PollQuestion]([PollQuestionId])
)
CREATE TABLE [dbo].[PollResponse]
(
    [PollResponseId] INT NOT NULL PRIMARY KEY IDENTITY,
    [PollOptionId] INT NOT NULL,
    [UserId] INT NOT NULL,
    [Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
    [Archived] DATETIME2(2) NULL,  -- Remove this if you don't need to hide answers

    CONSTRAINT [FK_PollResponse_PollOptionId_to_PollOption_PollOptionId] FOREIGN KEY ([PollOptionId]) REFERENCES [dbo].[PollOption]([PollOptionId]),
    CONSTRAINT [FK_PollResponse_UserId_to_User_UserId] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User]([UserId])
)

Naprawdę nie obchodzi cię, czy odpowiedź jest liczbą, datą, słowem itp., Ponieważ dane są odpowiedzią na pytanie, a nie czymś, na co musisz bezpośrednio operować. Ponadto dane mają znaczenie tylko w kontekście pytania. Jako taki nvarchar jest najbardziej wszechstronnym, czytelnym dla człowieka mechanizmem przechowywania danych.

Pytanie i potencjalne odpowiedzi zostaną zebrane od pierwszego użytkownika i wstawione do tabel PollQuestion i PollOption. Drugi użytkownik, który odpowie na pytania, wybierze z listy odpowiedzi (prawda / fałsz = lista 2). Możesz także rozwinąć tabelę PollQuestion, aby w razie potrzeby uwzględnić identyfikator użytkownika twórcy, aby śledzić tworzone przez niego pytania.

W interfejsie użytkownika odpowiedź wybrana przez użytkownika może być powiązana z wartością PollOptionId. Razem z PollQuestionId możesz szybko sprawdzić, czy odpowiedź jest ważna na pytanie. Ich odpowiedź, jeśli jest poprawna, zostanie wpisana do tabeli PollResponse.

Istnieje kilka potencjalnych problemów w zależności od szczegółów twojego przypadku użycia. Jeśli pierwszy użytkownik chce użyć pytania matematycznego, a nie chcesz oferować wielu możliwych odpowiedzi. Inna sytuacja polega na tym, że opcje, które zapewnia początkowy użytkownik, nie są jedynymi opcjami, które może wybrać drugi użytkownik. Możesz przerobić ten schemat w następujący sposób, aby obsługiwać te dodatkowe przypadki użycia.

CREATE TABLE [dbo].[PollResponse]
(
    [PollResponseId] INT NOT NULL PRIMARY KEY IDENTITY,
    [PollOptionId] INT NULL,
    [PollQuestionId] INT NOT NULL,
    [UserId] INT NOT NULL,
    [AlternateResponse] NVARCHAR(50) NULL, -- Some reasonable character limit
    [Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
    [Archived] DATETIME2(2) NULL,  -- Remove this if you don't need to hide answers

    CONSTRAINT [FK_PollResponse_PollOptionId_to_PollOption_PollOptionId] FOREIGN KEY ([PollOptionId]) REFERENCES [dbo].[PollOption]([PollOptionId]),
    CONSTRAINT [FK_PollResponse_UserId_to_User_UserId] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User]([UserId])
)

Prawdopodobnie dodałbym również ograniczenie sprawdzania, aby upewnić się, że podano opcję lub odpowiedź alternatywną, ale nie obie (opcja i odpowiedź alternatywna), w zależności od potrzeb.

Edycja: Komunikowanie typu danych dla AlternateResponse.

W idealnym świecie moglibyśmy użyć koncepcji generycznych do obsługi różnych typów danych dla AlternateReponse. Niestety nie żyjemy w idealnym świecie. Najlepszym kompromisem, jaki mogę wymyślić, jest określenie, jaki typ danych AlternateResponse powinien znajdować się w tabeli PollQuestion, i zapisanie AlternateReponse w bazie danych jako nvarchar. Poniżej znajduje się zaktualizowany schemat pytań i nowa tabela typów danych:

CREATE TABLE [dbo].[PollQuestion]
(
    [PollQuestionId] INT NOT NULL PRIMARY KEY IDENTITY,
    [QuestionText] NVARCHAR(150) NOT NULL, -- Some reasonable character limit
    [QuestionDataTypeId] INT NOT NULL,
    [Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
    [Archived] DATETIME2(2) NULL,  -- Remove this if you don't need to hide questions
    -- Insert FK here for QuestionDataTypeId
)
CREATE TABLE [dbo].[QuestionDataType]
(
    [QuestionDataTypeId] INT NOT NULL PRIMARY KEY IDENTITY,
    [Description] NVARCHAR(50) NOT NULL, -- Some reasonable character limit
)

Możesz wyświetlić listę wszystkich dostępnych typów danych dla twórców pytań, wybierając z tej tabeli QuestionDataType. Twój interfejs użytkownika może odwoływać się do klasy QuestionDataTypeId, aby wybrać odpowiedni format dla alternatywnego pola odpowiedzi. Nie jesteś ograniczony do typów danych TSQL, więc „Numer telefonu” może być typem danych i uzyskasz odpowiednie formatowanie / maskowanie w interfejsie użytkownika. Również w razie potrzeby możesz przesłać swoje dane do odpowiednich typów za pomocą prostej instrukcji przypadku w celu wykonania dowolnego rodzaju przetwarzania (wyboru, weryfikacji itp.) Alternatywnych odpowiedzi.

Erik
źródło
0

W każdym razie spójrz na Co jest tak złego w EAV? autor: Aaron Bertrand, aby uzyskać informacje na temat modelu EAV.

Prawdopodobnie na wiele sposobów lepiej będzie mieć kolumnę dla każdego typu danych zamiast XML lub wielu tabel.

Część wiązania jest łatwa:

CHECK 
(
    CASE WHEN col1 IS NOT NULL THEN 1 ELSE 0 END + 
    CASE WHEN col2 IS NOT NULL THEN 1 ELSE 0 END + 
    CASE WHEN col3 IS NOT NULL THEN 1 ELSE 0 END = 1
)

Istnieje wiele istniejących pytań i odpowiedzi w tej witrynie oznaczonych tagami i prawdopodobnie inne, w których pytający nie wiedział, jak użyć tego terminu w swoim pytaniu.

Gorąco polecam ich przeczytanie, ponieważ prawdopodobnie będą one obejmować wszystkie zalety i wady (zapobiega to ponownemu mieszaniu ich tutaj, gdy w rzeczywistości się nie zmieniły).

Odpowiedź na podstawie komentarzy pozostawionych przez Aarona Bertranda


źródło
-1

Myślę, że problem jest zbytnio przemyślany lub istnieją pewne dodatkowe ograniczenia, dlaczego niektóre odpowiedzi mogą być łatwiejsze do dostosowania niż inne. Obecnie wydaje się, że nie ma dowodów na to, że Odpowiedź musiałaby być przetwarzana w jakikolwiek sposób przez DB, ale tylko jako pole dziennika.

Wybrałbym NVARCHAR (MAX), a potem po prostu pozwoliłem frontendowi zająć się przechowywaniem / odzyskiwaniem zawartości. Prawdopodobnie pole bitowe IS_CORRECT, w którym interfejs może przechowywać, jeśli odpowiedź jest poprawna.

HansLindgren
źródło