Дизайн SQL приближается для поиска таблицы с неограниченным количеством битовых полей

Перечисления в WSDL должны рассматриваться как забота о техническом обслуживании.

Добавление или удаление значения перечисления является (должно быть!) Триггером для серьезного обновления интерфейса. Если перечисление является выходным значением, то вам обязательно нужно определить новую версию WSDL через новый URI, чтобы текущие клиенты не нарушали установленный контракт («что если они получат одно из этих новых, неожиданных значений в ответ») ? ") Если перечисление является входным значением, вы можете рассматривать это как незначительное обновление (« поскольку текущим клиентам не нужно знать об этом новом значении »), но тогда единственный способ для этих клиентов получить выгоду от добавление этой новой опции / функциональности (вы добавили это новое значение перечисления по какой-то причине, верно?) означало бы попросить их переключиться позже или раньше на новую версию интерфейса.

И это не имеет никакого отношения к функциональному значению enum, я думаю.

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

7
задан jaco0646 15 November 2018 в 13:47
поделиться

5 ответов

что-то это может сработать для вас:

определить таблицы:

CREATE TABLE #Apartments
(
     ApartmentID    int          not null primary key identity(1,1)
    ,ApartmentName  varchar(500) not null
    ,Status         char(1)      not null default ('A') 
    --....
)

CREATE TABLE #AttributeTypes
(
    AttributeType         smallint     not null primary key
    ,AttributeDescription varchar(500) not null
)

CREATE TABLE #Attributes  --boolean attributes, if row exists apartment has this attribute 
(
     ApartmentID     int not null --FK to Apartments.ApartmentID    
    ,AttributeID     int not null primary key identity(1,1)
    ,AttributeType   smallint  not null --fk to AttributeTypes
)

вставить образец данных:

SET NO COUNT ON
INSERT INTO #Apartments VALUES ('one','A')
INSERT INTO #Apartments VALUES ('two','A')
INSERT INTO #Apartments VALUES ('three','I')
INSERT INTO #Apartments VALUES ('four','I')

INSERT INTO #AttributeTypes VALUES (1,'dishwasher')
INSERT INTO #AttributeTypes VALUES (2,'deck')
INSERT INTO #AttributeTypes VALUES (3,'pool')
INSERT INTO #AttributeTypes VALUES (4,'pets allowed')
INSERT INTO #AttributeTypes VALUES (5,'washer/dryer')
INSERT INTO #AttributeTypes VALUES (6,'Pets Alowed')
INSERT INTO #AttributeTypes VALUES (7,'No Pets')

INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (1,1)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (1,2)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (1,3)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (1,4)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (1,5)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (1,6)

INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (2,1)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (2,2)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (2,3)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (2,4)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (2,7)

INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (3,1)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (3,2)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (3,3)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (3,4)

INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (4,1)
INSERT INTO #Attributes (ApartmentID, AttributeType) VALUES (4,2)
SET NOCOUNT OFF

образец поискового запроса:

;WITH GetMatchingAttributes AS
(
SELECT
    ApartmentID,COUNT(AttributeID) AS CountOfMatches
    FROM #Attributes
    WHERE AttributeType IN (1,2,3)  --<<change dynamically or split a CSV string and join in
    GROUP BY ApartmentID
    HAVING COUNT(AttributeID)=3--<<change dynamically or split a CSV string and use COUNT(*) from resulting table
)
SELECT
    a.*
    FROM #Apartments                      a
        INNER JOIN GetMatchingAttributes m ON a.ApartmentID=m.ApartmentID
    WHERE a.Status='A'
    ORDER BY m.CountOfMatches DESC

ВЫВОД:

ApartmentID ApartmentName 
----------- --------------
1           one           
2           two           

(2 row(s) affected)

В поисковом запросе выше я просто включил строку CSV с идентификаторами атрибутов искать. На самом деле, вы можете создать хранимую процедуру поиска, в которой вы передадите параметр CSV, содержащий идентификаторы для поиска. Вы можете посмотреть этот ответ , чтобы узнать о разбиении без цикла этих строк CSV в таблицу, к которой вы можете присоединиться. Это приведет к тому, что не потребуется использовать какой-либо динамический SQL.

ИЗМЕНИТЬ на основе многих комментариев:

Если вы добавите несколько столбцов в таблицу #AttributeTypes, вы сможете динамически построить страницу поиска. Вот несколько предложений:

  • Статус: "Активный" I "nactive
  • ListOrder: можно использовать это для сортировки по построению экрана
  • ColumnNumber: может помочь организовать поля в одной строке экрана
  • AttributeGroupID: для группировки полей, см. Ниже
  • и т. Д.

Вы можете установить все поля флажками или добавить еще одну таблицу с именем #AttributesGroups, сгруппировать некоторые вместе и использовать переключатели. Например, поскольку «Домашние животные разрешены» и «Нет домашних животных» являются исключительными, добавьте строку в таблицу «Домашние животные» #AttributesGroups. Приложение сгруппирует атрибуты в интерфейсе. Атрибуты в группах будут работать так же, как обычные несгруппированные атрибуты, просто соберите выбранные идентификаторы и передайте их процедуре поиска. Однако для каждой группы вам необходимо, чтобы приложение включало переключатель «без предпочтений» и по умолчанию. У этой опции не будет идентификатора атрибута, и она не передается, поскольку вы не хотите рассматривать атрибут.

В моем примере я показываю пример «суператрибут», который находится в #Apartments таблица "Статус". Вы должны учитывать только основные атрибуты этой таблицы. Если вы начнете использовать их, вы можете изменить CTE на FROM #Apartments с фильтрацией по этим полям, а затем присоединиться к #Attributes. Однако вы столкнетесь с проблемами условий динамического поиска, поэтому прочтите эту статью Эрланда Соммарскога .

ИЗМЕНИТЬ последние комментарии:

вот код для получения списка исключенных атрибутов:

;WITH GetMatchingAttributes AS
(
SELECT
    ApartmentID,COUNT(AttributeID) AS CountOfMatches
    FROM #Attributes
    WHERE AttributeType IN (1,2,3)  --<<change dynamically or split an include CSV string and join in
    GROUP BY ApartmentID
    HAVING COUNT(AttributeID)=3--<<change dynamically or split a CSV string and use COUNT(*) from resulting include table
)
, SomeRemoved AS
(
SELECT
    m.ApartmentID
    FROM GetMatchingAttributes      m
        LEFT OUTER JOIN #Attributes a ON m.ApartmentID=a.ApartmentID 
            AND a.AttributeType IN (5,6)   --<<change dynamically or split an exclude CSV string and join in
    WHERE a.ApartmentID IS NULL
)
SELECT
    a.*
    FROM #Apartments           a
        INNER JOIN SomeRemoved m ON a.ApartmentID=m.ApartmentID
    WHERE a.Status='A'

Я не думаю, что пошел бы по этому пути. Я бы пошел с подходом, изложенным в моем предыдущем РЕДАКТИРОВАНИИ выше. Когда необходимо включить / исключить атрибут, я бы просто добавил атрибут для каждого: «Разрешены домашние животные» и «Нет домашних животных».

Я обновил образцы данных из исходного сообщения, чтобы показать это.

Выполните исходный запрос с помощью:

  • (.., .., 6, .. ), чтобы найти квартиры, в которых разрешено проживание с домашними животными
  • (.., .., 7, ..), чтобы найти квартиры, в которых не разрешено проживание с домашними животными
  • (.., .., ..), если нет предпочтений.

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

9
ответ дан 6 December 2019 в 23:10
поделиться

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

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

2
ответ дан 6 December 2019 в 23:10
поделиться

Я никогда не тестировал это, но что, если бы вы создали поля varchar (256), которые хранят все ваши флаги в виде одной длинной строки из нулей и единиц.

Например, ,

  • AllowsPets = 1
  • HasParking = 0
  • HasDeck = 1
  • ModernKitchen = 1

будет:

  • PropertyFlags = 1011

и если вы искали что-то, что AllowsPets и HasDeck, то поисковый запрос будет выглядеть примерно так:

WHERE PropertyFlags LIKE '1_1_' (подчеркивание представляет собой один подстановочный знак в подобном предложении)

это решит ваши проблемы с добавлением дополнительных столбцов в поиск в будущее, но я не уверен, как это повлияет на производительность.

Кто-нибудь пробовал что-нибудь подобное?

0
ответ дан 6 December 2019 в 23:10
поделиться

Создайте таблицу, в которой хранятся атрибуты или столбцы поиска на основе квартиры. Определенно не добавляйте больше столбцов битовых полей ... кошмарное обслуживание и кошмарное кодирование. И определенно не просьба не генерировать операторы where динамически и не использовать exec.

0
ответ дан 6 December 2019 в 23:10
поделиться

Я прошел по этому пути несколько раз, пытаясь сохранить маркеры состояния!

Когда я только начинал (в 2000 году?), Я попробовал подход к позициям персонажа (ваш №2) и обнаружил, что он быстро стал довольно громоздким, поскольку я снова и снова задавал одни и те же вопросы: «какая позиция занимает 'Позволяет домашним животным'» еще раз?" или, что еще хуже, "как долго эта строка сейчас? / на какой позиции я нахожусь?" Можете ли вы обойти эту проблему - разработать объекты для управления вещами за вас? Ну да, в некоторой степени. Но я действительно не оценил, сколько дополнительных усилий это стоило по сравнению с тем, что идентификаторы полей управлялись для меня базой данных.

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

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

** ТАК ... ** Я закончил тем, что добавил поля в таблицу. Я понимаю теоретические причины, по которым Джон, «Неизвестный» и «Новый в городе» предпочитают другие подходы, и я бы согласился с одним или обоими в какой-то момент. Но опыт - довольно суровый учитель ...

Еще пара вещей

Во-первых, я не согласен с тем, что добавление дополнительных битовых полей - это кошмар обслуживания - по крайней мере, по сравнению с символьно-битовым подходом (ваш №2). То есть наличие отдельного поля для каждого атрибута гарантирует, что нет необходимости в «управлении», чтобы выяснить, какой слот принадлежит какому атрибуту.

Во-вторых, наличие 300 полей на самом деле не проблема - любая достойная база данных может это сделать. без проблем.

В-третьих, ваша реальная проблема и источник боли на самом деле связаны с динамической генерацией ваших запросов. Если вы похожи на меня, то этот вопрос действительно сводится к тому, «Действительно ли мне нужна эта массивная, уродливая и неизящная цепочка операторов IF для построения запроса?»

Ответ, к сожалению, - да. Все три предлагаемых вами подхода по-прежнему сводятся к цепочке операторов IF.

В подходе битового поля базы данных вы получите серию операторов IF, в которых все ваши столбцы должны быть добавлены следующим образом:

string SQL = "Select X,Y,Z Where ";

if (AllowsPets == 0)
  SQL += "(AllowsPets = 0) AND ";
else if (AllowsPets == )
  SQL += "(AllowsPets = 1) AND ";  // Else AllowsPets not in query
.
.
.
SQL = SQL.Substring(SQL.Length - 4);  // Get rid of trailing 'AND' / alternatively append '(1=1)'

В символе - позиционный подход , вы сделаете то же самое, но ваши «Добавления» добавят «0», «1» или «_» в ваш SQL. Вы также, конечно, столкнетесь с проблемами обслуживания, решая, какая из них обсуждалась выше (перечисления помогают, но не решают проблему полностью).

Как упоминалось выше, подход Attribute-Value на самом деле худший. Вы' мне придется либо создать неприятную цепочку подзапросов (что, несомненно, вызовет переполнение стека, содержащее 300 предложений), либо вам понадобится IF-THEN, например:

// Kill any previously stored selections.
SQLObject.Execute("Delete From SelectedApts Where SessionKey=X");
// Start with your first *known* attr/value and fill a table with the results.
.
.
Logic to pick first known attr/value pair
.
.
SQLObject.Execute("Insert Into SelectedApts Select X as SessionKey, AptID From AttrValue Where AllowsPets=1");

// Now you have the widest set that meets your criteria. Time to whittle it down.
if (HasParking == 1)
  SQLObject.Execute("Delete From SelectedApts Where AptID not in (Select AptID From AttrValue Where AllowsChildren=1));
if (AllowsChildren == 0)
  SQLObject.Execute("Delete From SelectedApts Where AptID not in (Select AptID From AttrValue Where AllowsChildren=0));
.
.
.
// Perform 2-300 more queries to keep whittling down your set to the actual match.

Теперь вы можете оптимизировать это немного, поэтому вы выполняете меньше запросов (PIVOT, наборы подзапросов или использование оператора UNION), но факт в том, что это становится ОЧЕНЬ дорого по сравнению с одним запросом, который вы можете использовать (но должны построить) с использованием других подходов.

Таким образом, это болезненная проблема, какой бы подход вы ни выбрали - на самом деле нет магии, которая поможет вам ее избежать. Но, побывав там раньше, абсолютно рекомендую подход №1.

Обновление: Если вы действительно сосредоточены на поиске совпадений по прямым критериям («Все квартиры, у которых есть A, B и C») и вам не нужны другие запросы (например, «...

0
ответ дан 6 December 2019 в 23:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: