T-SQL находит точно такие же значения в ссылочной таблице - PullRequest
2 голосов
/ 16 августа 2011

Предположим, у меня есть 3 таблицы в базе данных Sql Serer 2008:

CREATE TABLE [dbo].[Properties](
    [PropertyId] [int] NOT NULL,
    [PropertyName] [nvarchar](50) NOT NULL
)

CREATE TABLE [dbo].[Entities](
    [EntityId] [int] NOT NULL,
    [EntityName] [nvarchar](50) NOT NULL
)    

CREATE TABLE [dbo].[PropertyValues](
    [EntityId] [int] NOT NULL,
    [PropertyId] [int] NOT NULL,
    [PropertyValue] [int] NOT NULL
)
  1. Таблица Свойства содержит возможный набор свойств, значения которых можно настроить для бизнес-объектов.
  2. Таблица Entities содержит бизнес-объекты, которые настраиваются из приложения.
  3. Таблица 3 содержит выбранные значения свойств для бизнес-объектов. Каждый бизнес-объект может содержать свой собственный набор свойств (то есть «Property1» может быть настроен для первого объекта, но не настроен для второго).

Моя задача - найти бизнес-объекты, которые точно соответствуют заданному объекту (объекты, которые имеют точно такой же набор свойств с точно такими же значениями). Производительность имеет решающее значение.

Есть предложения?

[ADDED] Например, в таблице сущностей есть запись с EntityId = 1. В таблице PropertyValues ​​есть 3 строки, которые связаны с этой записью:

 EntityId  PropertyId  PropertyValue
 1         4           Val4
 1         5           Val5
 1         6           Val6

Требуется найти другие записи в таблице Entity, которые имеют 3 связанные строки в таблице PropertyValues, и эти строки содержат те же данные, что и строки для EntityId = 1 (кроме столбца EntityId)

[ADDED] Пожалуйста, смотрите мой новый вопрос: Лучший подход к хранению данных, атрибуты которых могут варьироваться

[BOUNTY1] Спасибо за все. Ответы были очень полезны. Моя задача немного сложна (но это усложнение может быть полезно в целях производительности). Пожалуйста, смотрите детали ниже:

  • Добавлена ​​новая таблица с именем EntityTypes
  • В таблицы сущностей и свойств добавлен столбец EntityTypeId
  • Теперь существует несколько типов сущностей. У каждого объекта есть свой набор свойств.

    Можно ли повысить производительность, используя эту информацию?

[BOUNTY2] Есть второе осложнение:

  • В таблицу свойств добавлен столбец IsDeleted
  • Таблица PropertyValues ​​может иметь значения для свойств, которые уже удалены из базы данных. Объекты с такими свойствами считаются недействительными.
  • Некоторые сущности не имеют значений для каждого свойства набора EntityType. Эти объекты также считаются недействительными.

Вопрос: как мне написать скрипт, который будет выбирать все сущности и дополнительный столбец IsValid для них.

Ответы [ 5 ]

5 голосов
/ 16 августа 2011
;with cteSource as
(
  select PropertyId,
         PropertyValue
  from PropertyValues
  where EntityId = @EntityID
)
select PV.EntityId
from PropertyValues as PV
  inner join cteSource as S  
    on PV.PropertyId = S.PropertyId and
       PV.PropertyValue = S.PropertyValue and
       PV.EntityId <> @EntityID
group by PV.EntityId
having count(*) = (select count(*)
                   from cteSource) and
       count(*) = (select count(*)
                   from PropertyValues as PV1
                   where PV1.EntityId = PV.EntityId)

Для вашего добавления вы можете добавить это, где пункт:

where -- exlude entities with deleted properties
      PV.EntityID not in (select PV2.EntityID
                          from Properties as P
                            inner join PropertyValues as PV2
                              on P.PropertyID = PV2.PropertyID
                          where P.IsDeleted = 1)
      -- exclude entities with missing EntityType                     
  and PV.EntityID not in (select E.EntityID
                          from Entities as E
                          where E.EntityType is null) 

Edit:

Если вы хотите протестировать запрос с некоторыми примерами данных, вы можете сделать это здесь: http://data.stackexchange.com/stackoverflow/q/110243/matching-properties

1 голос
/ 24 августа 2011

Моя задача - найти бизнес-объекты, которые точно соответствуют заданному объекту (объекты, которые имеют точно такой же набор свойств с точно такими же значениями).

, если "заданный объект"Например, t описывается как #PropertyValues, поэтому запрос будет выглядеть следующим образом:

create table #PropertyValues(
    [PropertyId] [int] NOT NULL,
    [PropertyValue] [int] NOT NULL
)

insert #PropertyValues
select
    3, 3 -- e.g.

declare
    @cnt int

select @cnt = count(*) from #PropertyValues

select 
    EntityId
from 
    PropertyValues pv
    left join  #PropertyValues t on t.PropertyId = pv.PropertyId and t.PropertyValue = pv.PropertyValue
group by
    EntityId
having 
    count(t.PropertyId) = @cnt
    and count(pv.PropertyId) = @cnt

drop table #PropertyValues

Но если производительность настолько критична, вы можете создать специальное индексированное поле для объектов таблицы, например, EntityIndex varchar (8000), который будетзаполняется триггером в таблице PropertyValues ​​как convert (char (10), PropertyId) + convert (char (10), PropertyValue) (для всех свойств объекта, отсортированных!).Так что по этому полю можно будет очень быстро искать.

1 голос
/ 23 августа 2011

Один из способов взглянуть на это - если у меня есть все карты базовых мячей, которые у вас есть, тогда у нас не будет той же самой бейсбольной карты, которую у меня может быть больше. Но если у вас также есть все бейсбольные карточки, которые есть у меня, то у нас точно такие же бейсбольные карточки. Это немного сложнее, так как мы ищем командой. По команде можно посчитать количество совпадений, мой счет и ваш счет и сравнить эти 3 счета, но это 3 соединения. Это решение - 2 соединения, и я думаю, что это будет быстрее, чем вариант 3 соединений.

Для меня бонусные вопросы не имели смысла. В качестве изменения таблицы, но это имя таблицы не соответствует ни одной из таблиц. Нужно полное описание таблицы для этих бонусных вопросов.

Ниже представлен вариант 2 соединения:

    select [m1].[IDa] as [EntityId1], [m1].[IDb] as [EntityId2]
    from
    (   select [PV1].[EntityId] as [IDa], [PV2].[EntityId] as [IDb]
        from [PropertyValue] as [PV1] 
        left outer join [PropertyValue] as [PV2] 
            on  [PV2].[EntityId] <> [PV1].[EntityId]
            and [PV2].[PropertyId] = [PV1].[PropertyId]
            and [PV2].[PropertyValue] = [PV1].[PropertyValue] 
        group by [PV1].[EntityId], [PV2].[EntityId]
        having count(*) = count([PV2].[EntityId])
    ) as [m1]
    join 
    (   select [PV1].[EntityId] as [IDa], [PV2].[EntityId] as [IDb]
        from [PropertyValue] as [PV1] 
        right outer join [PropertyValue] as [PV2] 
            on  [PV2].[EntityId] <> [PV1].[EntityId]
            and [PV2].[PropertyId] = [PV1].[PropertyId]
            and [PV2].[PropertyValue] = [PV1].[PropertyValue] 
        group by [PV1].[EntityId], [PV2].[EntityId]
        having count(*) = count([PV1].[EntityId]))
    ) as [m2]
    on [m1].[IDa] = [m2].[IDa] and [m1].[IDb] = [m2].[IDb] 

Ниже приведена опция, основанная на количестве присоединений:

    select [m1].[IDa] as [EntityId1], [m1].[IDb] as [EntityId2]
    from
    (   select [PV1].[EntityId] as [IDa], [PV2].[EntityId] as [IDb], COUNT(*) as [count]
        from [PropertyValue] as [PV1] 
        join [PropertyValue] as [PV2] 
            on  [PV2].[EntityId] <> [PV1].[EntityId]
            and [PV2].[PropertyId] = [PV1].[PropertyId]
            and [PV2].[PropertyValue] = [PV1].[PropertyValue] 
        group by [PV1].[EntityId], [PV2].[EntityId]
    )   as [m1]
    join 
    (   select [PV1].[EntityId] as [IDa], COUNT(*) as [count]
        from [PropertyValue] as [PV1] 
        group by [PV1].[EntityId]
        having count(*) = count([PV1].[sID]))
    )   as [m2]
    on [m1].[IDa] = [m2].[IDa] and [m1].[count] = [m2].[count]
    join 
    (   select [PV2].[EntityId] as [IDb], COUNT(*) as [count]
        from [PropertyValue] as [PV2] 
        group by [PV2].[EntityId]
    )   as [m3]
    on [m1].[IDb] = [m3].[IDb] and [m1].[count] = [m3].[count]
1 голос
/ 16 августа 2011

My task is to find business objects which are exactly same as given object (ones which have exactly same set of properties with exactly same values). Performance is critical.

Подход может варьироваться в зависимости от среднего числа свойств, которые обычно имеют объекты, от нескольких до десятков.

Предполагая, что объекты имеют различное количество свойств:

Я бы начал с составного неуникального индекса на диаде (PropertyValues.PropertyId, PropertyValues.PropertyValue) для select-performance.

Затем, учитывая идентификатор сущности, я бы выбрал в курсоре его свойства - пары свойств-значений.

[EDIT: Не уверен, является ли (entityid, propertyid) уникальным в вашей системе или вы разрешаете несколько экземпляров одного и того же идентификатора свойства для сущности, например, FavoriteColors:

              entityid  propertyid property value
                1            17     blue
                1            17     dark blue
                1            17     sky blue
                1            17     ultramarine

Вам также потребуется либо неуникальный индекс для монады (PropertyValues.entityid), либо составной индекс для (PropertyValues.entityid, PropertyValues.propertyid); составной индекс будет уникальным, если вы хотите, чтобы один и тот же идентификатор свойства не связывался с сущностью более одного раза.

Если свойство может встречаться несколько раз, вероятно, в вашей таблице свойств должен быть флаг CanBeMultivalued. Если вы хотите предотвратить это, у вас должен быть уникальный индекс триады (entityid, propertyid, propertyvalue):

        entityid  propertyid property value
                1            17     blue
                1            17     blue

Если эта триада проиндексирована, вам не понадобится индекс (entityid) или составной индекс (entityid, propertyid) в таблице PropertyValues.

* * Тысяча двадцать-одина [/ EDIT] * * тысяча двадцать-дв

Затем я бы создал временную таблицу для хранения соответствующих идентификаторов сущностей.

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

      insert into temp
      select entityid from PropertyValues
      where propertyid = mycursor.propertyid and propertyvalue = mycursor.propertyvalue

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

Поскольку вы знаете, сколько свойств имеет данный объект, вы можете сделать следующее, чтобы выбрать только те объекты, которые имеют все общие свойства с данным объектом:

       select entityid from temp
       group by entityid having count(entityid) =  {the number of properties in the given object}

ДОПОЛНЕНИЕ:

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

Кроме того, после первой итерации цикла вы можете исследовать возможность того, что внутреннее соединение между временной таблицей и таблицей PropertyValues ​​может обеспечить некоторое повышение производительности:

        select entityid from propertvalues
        >> inner join temp on temp.entityid = propertyvalues.entityid <<
        where propertyid = mycursor.propertyid and propertyvalue = mycursor.propertyvalue

И вы также можете попробовать удалить entityids из temp после первой итерации:

        delete from temp
        where not exists
        (
         select entityid from propertyvalues
         inner join temp on temp.entityid = propertyvalues.entityid
         where propertyid = mycursor.propertyid and propertyvalue = mycursor.propertyvalue
        )

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

Конечно, если бы temp был пустым в любое время после того, как первая пара свойства-значения данного объекта использовалась для поиска совпадений, вы бы знали, что для вашего данного объекта нет совпадений, потому что вы нашли свойство-значение что никакая другая сущность не обладает, и вы можете выйти из цикла и вернуть нулевой набор.

0 голосов
/ 16 августа 2011

Я думаю, что это просто самообъединение:

select P2.EntityID,E.EntityName
from PropertyValues P1
inner join PropertyValues P2
on   P1.PropertyID    = P2.PropertyID
and  P1.PropertyValue = P2.PropertyValue
inner join Entity E
on P2.EntityID = E.EntityID
where P1.EntityId = 1
and   P2.EntityId <> 1
group by P2.EntityID, E.EntityName
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...