Как создать дату рождения в БД и ОРМ для объединения известных и неизвестных частей даты

Запишите вверх, мой вопрос окажется похожим на вопрос SO 1668172.


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

  • NULL значение, то есть DoB не указано
  • 1950-??-?? Известно только значение года DoB, дата/месяц не
  • ????-11-23 Всего месяц, день или комбинация из двух, но без года
  • 1950-11-23 Известен полный DoB

Технологии, которые я использую для моего приложения, следующие:

  • Asp.NET 4 (С#), возможно, с MVC
  • Некоторые решения ORM, возможно, Linq-to-sql или NHibernate's
  • MSSQL Server 2008, сначала только версия Express

Возможности для SQL-бит, которые до сих пор переходили мне в голову:

  • 1) Используйте один нулевой столбец varchar, например. 1950-11-23, и замените unkowns на "X", например. XXXX-11-23 или 1950-XX-XX
  • 2) Используйте три нулевых столбца int, например. 1950, 11 и 23
  • 3) Используйте столбец INT за год, а также столбец datetime для всех известных DoBs

Для конца С# этой проблемы я просто воспользовался этими двумя вариантами:

  • A) Используйте свойство string для представления DoB, конвертируйте только для просмотра.
  • B) Используйте пользовательскую (?) структуру или класс для DoB с тремя целыми числами с нулевым значением.
  • C) Используйте нулевое значение DateTime вместе с нулевым целым числом в течение года

Решения, похоже, образуют согласованные пары в 1A, 2B или 3C. Конечно, 1A не является хорошим решением, но оно устанавливает базовую линию.

Любые советы и ссылки высоко ценятся. Ну, если они связаны, так или иначе:)


Изменить, об ответах. Я принял один ответ как принятый, потому что я думаю, что это сработает для меня. Это стоит посмотреть на другие ответы, хотя, если вы споткнулись здесь с тем же вопросом.

Ответ 1

Сторона SQL

Моя последняя идея по этому вопросу - использовать диапазон для неопределенных дат или может иметь различную специфику. Для двух столбцов:

DobFromDate (inclusive)
DobToDate (exclusive)

Вот как это будет работать с вашими сценариями:

Specificity   DobFromDate   DobToDate
-----------   -----------   ----------
YMD            2006-05-05   2006-05-06
YM             2006-05-01   2006-06-01
Y              2006-01-01   2007-01-01
Unknown        0000-01-01   9999-12-31
-> MD, M, D not supported with this scheme

Обратите внимание, что нет причин, по которым это невозможно выполнить до часа, минуты, секунды, миллисекунды и т.д.

Затем при запросе для людей, родившихся в определенный день:

DECLARE @BornOnDay date = '2006-05-16'

-- Include lower specificity:
SELECT *
FROM TheTable
WHERE
   DobFromDate <= @BornOnDay
   AND @BornOnDay < DobToDate;

-- Exclude lower specificity:
SELECT *
FROM TheTable
WHERE
   DobFromDate = @BornOnDay
   AND DobToDate = DateAdd(Day, 1, @BornOnDay);

Это для меня самое лучшее сочетание ремонтопригодности, простоты использования и выразительной мощности. Он не будет обрабатывать потерю точности в более значимых значениях (например, вы знаете месяц и день, но не год), но если это можно обойти, я думаю, что это победитель.

Если вы когда-либо будете запрашивать дату, тогда в целом лучшие решения (на мой взгляд) будут теми, которые сохраняют элементы как даты на сервере определенным образом.

Также обратите внимание, что если вы ищете диапазон дат, а не один день, с моим решением вам все еще нужны только два условия, а не четыре:

DECLARE
   @FromBornOnDay date = '2006-05-16',
   @ToBornOnDay date = '2006-05-23';

-- Include lower specificity:
SELECT *
FROM TheTable
WHERE
   DobFromDate < @ToBornOnDay
   AND @FromBornOnDay < DobToDate;

Сторона С#

Я бы использовал пользовательский класс со всеми методами, необходимыми для правильной сопоставления даты и даты на нем. Вы знаете требования к бизнесу в отношении того, как вы будете использовать неизвестные даты, и можете кодировать логику внутри класса. Если вам нужно что-то до определенной даты, вы будете использовать только известные или неизвестные предметы? Что вернет ToString()? Это, на мой взгляд, лучше всего решать с помощью класса.

Ответ 2

Мне нравится идея 3 int nullable столбцов и структура из 3 нулевых int в С#.

для обработки db требуется некоторое усилие, но вы можете избежать синтаксического анализа вокруг строк, и вы также можете запросить SQL-запрос непосредственно по году или году и месяцу и так далее...

Ответ 3

Все, что вы делаете, будет грязным DB. Для потребителей таких дат я бы написал специальный класс/структуру, который инкапсулирует, какую дату он (я бы назвал его чем-то вроде PartialDate), чтобы облегчить дело для потребителей - как Мартин Фаулер защищает "Деньги" .

Если вы обнаружите DateTime непосредственно на С#, это может привести к путанице, если у вас была "дата"???? -11-23, и вы хотели определить, был ли клиент старше 18 лет, как бы вы по умолчанию не указали дату, как потребитель узнает, что часть даты была недействительной и т.д.?

Дополнительным преимуществом использования PartialDate является то, что другие люди, читающие ваш код, быстро поймут, что они не являются нормальными, полными датами и не должны рассматриваться как таковые!

Edit

Размышляя о концепции частичных данных, я решил использовать Google. Я обнаружил, что существует понятие Partial on Joda time и интересный PDF файл по теме, которая может быть или не быть полезной для вас.

Ответ 4

Интересная проблема...

Мне нравится решение 2B над решением 3C, потому что с 3C оно не будет нормализовано... когда вы обновляете один из int, вам также придется обновлять DateTime, иначе вы бы не синхронизировались.

Однако, когда вы читаете данные в своем конце С#, у меня будет свойство, которое сверлит все ints в строку, отформатированную так, как вы в решении 1, чтобы ее можно было легко отобразить.

Мне любопытно, какой тип отчетов вам нужно будет делать с этими данными... или если вы просто будете хранить и извлекать его из базы данных.

Ответ 5

Я бы не стал беспокоиться о том, как хранить дату, но я бы сохранил дату в поле datetime, НО, если зная, что какая-то часть даты не была заполнена, у меня будут флаги для каждого раздела дата, которая недействительна, поэтому ваша схема будет:

DBODate как дата DayIsSet как бит MonthIsSet как бит YearIsSet как бит.

Таким образом, вы все равно можете реализовать все допустимые сопоставления дат и все еще знать точность даты, над которой работаете. (что касается даты, я бы всегда по умолчанию считал недостающую часть как мин этого значения: IE Месяц по умолчанию - январь, день - первый, год - 1900 или что-то еще).

Ответ 6

Очевидно, что все упомянутые выше решения действительно представляют собой компромисс.

Поэтому я бы рекомендовал тщательно подумать, какой из "уровней" является наиболее вероятным и оптимизировать для этого. Затем перейдите для правильной обработки исключений для других редких случаев.

Я не знаю, является ли отчетность проблемой для вас прямо сейчас или может быть позже, но вы можете считать это третьим аспектом помимо проблем с DB/С#.