SQL: выборочные подзапросы - PullRequest
3 голосов
/ 18 июня 2009

У меня SQL-запрос (MSSQLSERVER), где я добавляю столбцы в набор результатов с помощью подвыборов:

SELECT P.name, 
(select count(*) from cars C where C.type = 'sports') AS sportscars,
(select count(*) from cars C where C.type = 'family') AS familycars,
(select count(*) from cars C where C.type = 'business') AS businesscars
FROM people P
WHERE P.id = 1;

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

В приведенном выше примере каждая запись в таблице "people" также имеет три дополнительных столбца: "wantSportscar", "wantFamilycar" и "wantBusinesscar". Теперь я хочу сделать выборку для каждого дополнительного столбца только в том случае, если соответствующему полю «хочет .....» в таблице сотрудников установлено значение «истина». Другими словами, я хочу сделать первый подвыбор, только если для P.wantsSportscar задано значение true для этого конкретного человека. Второй и третий подпункты должны работать аналогичным образом.

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

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

Если человек А хочет спортивный автомобиль и семейный автомобиль, результат будет включать столбцы «имя», «спортивные автомобили» и «семейные автомобили».

Если человек B хочет бизнес-кар, результат будет включать столбцы «имя» и «бизнес-кар».

Я пытался использовать различные комбинации с операторами IF, CASE и EXISTS, но до сих пор не смог получить синтаксически правильное решение. Кто-нибудь знает, возможно ли это вообще? Обратите внимание, что запрос будет сохранен в хранимой процедуре.

Ответы [ 6 ]

5 голосов
/ 18 июня 2009

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

Невозможно изменить макет набора результатов в одном запросе.

Вместо этого вы можете оформить запрос следующим образом:

SELECT  P.name, 
        CASE WHEN wantssport = 1 THEN (select count(*) from cars C where C.type = 'sports') ELSE NULL END AS sportscars,
        CASE WHEN wantsfamily = 1 THEN (select count(*) from cars C where C.type = 'family') ELSE NULL END AS familycars,
        CASE WHEN wantsbusiness = 1 THEN (select count(*) from cars C where C.type = 'business') ELSE NULL END AS businesscars
FROM    people P
WHERE   P.id = 1

, который выберет NULL в соответствующем столбце, если человек этого не хочет, и проанализирует эти NULL на стороне клиента.

Обратите внимание, что реляционная модель отвечает на запросы в терминах relations.

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

Реляционная модель всегда отвечает на этот конкретный вопрос четвертичным отношением.

Он не пропускает ни одного из членов отношения: вместо этого он просто устанавливает их на NULL, что является способом SQL показать, что член отношения не определен, не применим или не имеет смысла.

0 голосов
/ 22 октября 2009

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

Динамический SQL - ваш ответ.

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

Поэтому продолжайте с осторожностью:

declare @sqlToExecute nvarchar(max);
declare @includeSportsCars bit;
declare @includeFamilyCars bit;
declare @includeBusinessCars bit;

set @includeBusinessCars = 1
set @includeFamilyCars = 1
set @includeSportsCars  = 1

set @sqlToExecute = 'SELECT P.name '

if @includeSportsCars = 1 
    set @sqlToExecute = @sqlToExecute + '(select count(*) from cars C where C.type = ''sports'') AS sportscars, ';
if @includeFamilyCars = 1
    set @sqlToExecute = @sqlToExecute + '(select count(*) from cars C where C.type = ''family'') AS familycars, ';
if @includeBusinessCars = 1
    set @sqlToExecute = @sqlToExecute + '(select count(*) from cars C where C.type = ''business'') AS businesscars '

set @sqlToExecute = @sqlToExecute + ' FROM people P WHERE P.id = 1;';

exec(@sqlToExecute)
0 голосов
/ 18 июня 2009

Есть три основных принципа, которым вы хотите научиться, чтобы упростить эту работу. Первый - нормализация данных, второй - GROUP BY, а третий - PIVOT.

Во-первых, нормализация данных. Ваш дизайн таблицы людей не в первой нормальной форме. Столбцы «wantports», «wantfamily», «wantbusiness» действительно являются повторяющейся группой, хотя они могут не выглядеть как одна. Если вы сможете изменить дизайн таблицы, вам будет полезно создать третью таблицу, назовем ее «peoplewant» с двумя ключевыми столбцами: personid и cartype. Я могу подробно рассказать о том, почему этот дизайн будет более гибким и мощным, если хотите, но сейчас я пропущу это.

На GROUP BY. Это позволяет вам получить результат, который суммирует каждую группу в одной строке результата.

SELECT 
    p.name, 
    c.type, 
    c.count(*) as carcount
FROM people p, 
   INNER JOIN peoplewant pw ON p.id = pw.personid 
   INNER JOIN cars c on pw.cartype = c.type
WHERE
   p.id = 1
GROUP BY 
   p.name,
   c.type

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

Наконец, PIVOT. Инструмент PIVOT в вашей СУБД позволяет вам превратить этот результат в форму, в которой для человека есть только одна строка, и для каждого из типов карт, требуемых для этого человека, есть отдельный столбец. Я сам не использовал PIVOT, поэтому позволю кому-нибудь еще отредактировать этот ответ, чтобы привести пример с использованием PIVOT.

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

0 голосов
/ 18 июня 2009

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

Вы не сможете сделать это простым SQL. Я предлагаю вам сделать этот столбец NULL или ZERO.

Если вы хотите динамически расширять запрос при добавлении новых автомобилей, то PIVOTing может вам в чем-то помочь.

0 голосов
/ 18 июня 2009

Вы можете делать то, что хотите, сначала выбрав значения в виде отдельных строк во временной таблице, а затем сделав PIVOT для этой таблицы (превратив строки в столбцы).

0 голосов
/ 18 июня 2009

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

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

...