Простой, но сложный SQL-запрос

#sql #sql-server

#sql #sql-сервер

Вопрос:

У меня есть очень простая таблица MS SQL со следующими данными (с именем столбца и типом данных):

 TableId     PersonName              Attribute           AttributeValue
(int)       (nvarchar 50)           (nvarchar 50)       (bit)
----------- ----------------------- ------------------- --------------
1           A                       IsHuman             1
2           A                       CanSpeak            1
3           A                       CanSee              1
4           A                       CanWalk             0
5           B                       IsHuman             1
6           B                       CanSpeak            1
7           B                       CanSee              0
8           B                       CanWalk             0
9           C                       IsHuman             0
10          C                       CanSpeak            1
11          C                       CanSee              1
12          C                       CanWalk             0
  

Теперь в результате мне нужно уникальное имя пользователя, которое имеет как атрибут isHuman, так и CanSpeak с AttributeValue = 1.

Ожидаемый результат должен быть (не должен включать C, поскольку этот имеет isHuman = 0)

 PersonName
------------
A
B
  

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

Комментарии:

1. @suresh вы опубликовали свой ответ здесь, и я также отформатировал этот вопрос, и он выглядит нормально, тогда почему вы изменили все это на эту ссылку

Ответ №1:

 SELECT PersonName 
  FROM MyTable 
 WHERE AttributeName = 'IsHuman' 
       AND AttributeValue = 1
INTERSECT
SELECT PersonName 
  FROM MyTable 
 WHERE AttributeName = 'CanSpeak' 
       AND AttributeValue = 1;
  

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

Ответ №2:

 SELECT PersonName 
FROM MyTable 
WHERE (AttributeName = 'IsHuman' AND AttributeValue = 1) OR 
      (AttributeName = 'CanSpeak' AND AttributeValue = 1) 
GROUP BY PersonName
HAVING COUNT(*) > 1
  

или

 SELECT PersonName 
FROM MyTable 
WHERE AttributeValue = 1 AND AttributeName IN ('IsHuman', 'CanSpeak') 
GROUP BY PersonName
HAVING COUNT(*) > 1
  

Комментарии:

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

2. Привет, Дилан Смит, спасибо за быстрый ответ. Можем ли мы добавить больше в предложение where, например «CanSee», «CanWalk» и т. Д. В соответствии с выбором пользователя. Это может варьироваться в зависимости от выбора.

3. @sureshjat: похоже, вам нужно взглянуть на реляционный оператор деления , широко известный как «поставщик, который поставляет все детали» , в частности деление с остатком .

4. Конечно, просто добавьте его в предложение IN и соответствующим образом увеличьте число в предложении HAVING

5. Оба решения будут работать неправильно, если (например) будут две записи с attributeName = ‘isHuman’ и AttributeValue = 1

Ответ №3:

 SELECT PersonName FROM MyTable 
WHERE PersonName IN 
(SELECT T1.PersonName FROM MyTable T1 WHERE T1.Attribute = 'IsHuman' and T1.AttributeValue='1')
AND (Attribute = 'CanSpeak' AND AttributeValue='1')
  

Комментарии:

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

2. Я пропустил закрывающие скобки вашего оператора IN и прочитал это как то, что предложение CanSpeak было дополнено предложением isHuman. Я удалю свой комментарий и поддержу этот ответ, но вы могли бы подумать о переформатировании инструкции, чтобы было очевидно, что AND НЕ является частью инструкции IN.

Ответ №4:

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

 SELECT t.PersonName FROM table t 
INNER JOIN table t2 ON t.PersonName=t2.PersonName AND t3.Attribute = 'IsHuman' AND t2.AttributeValue = 1 
INNER JOIN table t3 ON t2.PersonName=t3.PersonName AND t3.Attribute = 'CanSpeak' AND t3.AttributeValue = 1 
  

или

 SELECT t.PersonName FROM table t 
INNER JOIN table t2 ON t.PersonName=t2.PersonName
INNER JOIN table t3 ON  t2.PersonName=t3.PersonName
WHERE t2.Attribute = 'IsHuman' AND t2.AttributeValue = 1 AND t3.Attribute = 'CanSpeak' AND t3.AttributeValue = 1 
  

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

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

Ответ №5:

 select personname from yourtablename where personname in ('a','b') group by personname
  

Комментарии:

1. Это дает ожидаемый результат … 🙂

2. Привет, Дилан Смит, можем ли мы добавить больше в предложение where, например «CanSee», «CanWalk» и т. Д. В соответствии с выбором пользователя. Он может меняться в зависимости от выбора.

Ответ №6:

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

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

Правильный ответ (в том смысле, что это повысит вероятность того, что я найму вас, а также уменьшит важность того, что оптимизатор RDMS понимает это, и его можно распространить на другие, произвольно сложные случаи):

 SELECT t1.PersonName 
  FROM MyTable t1, MyTable t2
 WHERE t2.AttributeName = 'CanSpeak' 
       AND t2.AttributeValue = 1
       AND t1.AttributeName = 'IsHuman' 
       AND t1.AttributeValue = 1
       AND t1.PersonName = t2.PersonName;
  

Комментарии:

1. Это утверждение содержит ошибку, использует антишаблон и работает хуже, чем предлагаемые решения. Вы должны уволить любого, кто предлагает этот <vbg> (не мой downvote кстати)

2. Может быть расширен путем добавления нового табличного выражения для каждой строки критериев поиска, что непрактично. Хотя я согласен, что его можно распространить на сколь угодно сложные случаи, достичь этого нелегко. «Правильный» способ сделать это — использовать отношения изображений. В SQL лучшее, что мы можем получить, — это реляционное разделение. Помните, что в SQL единицей работы является строка (а не таблица): способ расширения в этом случае — добавить строки критериев в таблицу делителей и использовать для каждой из них один и тот же запрос. Ваше предложение по сути является динамическим SQL.

3. Кстати, оператор переименования отношения, который вы называете «псевдонимом» (а стандарты SQL называют «именем корреляции»), не является частью какой-либо известной мне реляционной алгебры. Конечно, это полезно, но недостаточно важно для алгебры. Алгебра Дарвена включает переименование атрибута, но не переименование отношения. Действительно, Дарвен особенно критически относится к тому факту, что SQL заставляет без необходимости применять имя корреляции к таблице. Цитата : страница 9, TEETH_GNASHER .

4. @Lieven — ваш комментарий содержит неуместность, неопровержимое утверждение и апелляцию к авторитету.

5. @onedaywhen — если бы критериям поиска было разрешено меняться, тогда да, вы не могли бы использовать этот метод. Предполагая, что критерии были исключительно объединением случаев, тогда разделение, как вы упоминаете в своих комментариях к OP, было бы правильным решением, но реализует ли какой-либо SQL разделение? Я отказался от того, чтобы называть это «именем корреляции», потому что многие документы RDBMS называют это «псевдонимом», но «переименовать» просто вводит в заблуждение: это представление в таблице, и я называю его, а не переименовываю. Среди многих, многих недостатков SQL является то, что иногда он излишне требует имен корреляции, но это не один из тех случаев.