SQL: выборочные подзапросы
-
06-07-2019 - |
Вопрос
У меня 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;
Вышеприведенный запрос только из тестовой настройки, что немного глупо, но, как мне кажется, он служит достаточно хорошим примером. На самом деле запрос, над которым я работаю, охватывает несколько сложных таблиц, которые только отвлекают от рассматриваемой проблемы.
В приведенном выше примере каждая запись в таблице "люди" также есть три дополнительных столбца: «WantSportscar», «WantFamilycar». и "хочет бизнес-кар". Теперь я хочу сделать выборку для каждого дополнительного столбца только в том случае, если соответствующий " хочет ..... " поле в таблице сотрудников установлено на "true". Другими словами, я хочу сделать первый подвыбор, только если для P.wantsSportscar задано значение true для этого конкретного человека. Второй и третий подпункты должны работать аналогичным образом.
Таким образом, этот запрос должен работать так, что он показывает имя конкретного человека и количество моделей, доступных для типов автомобилей, которые он хочет иметь. Возможно, стоит отметить, что мой окончательный набор результатов всегда будет содержать только одну запись, а именно запись одного конкретного пользователя.
Важно, чтобы, если человек не интересовался определенным типом автомобилей, столбец для этого типа не был включен в окончательный набор результатов. Пример, чтобы убедиться, что это понятно:
Если человек А хочет спортивный автомобиль и семейный автомобиль, результат будет включать столбцы «имя», «спортивные автомобили». и "семейные автомобили".
Если человек B хочет бизнес-кар, результат будет включать столбцы «имя» и "Businesscar".
Я пытался использовать различные комбинации с операторами IF, CASE и EXISTS, но до сих пор не смог найти синтаксически правильное решение. Кто-нибудь знает, возможно ли это вообще? Обратите внимание, что запрос будет сохранен в хранимой процедуре.
Решение
В вашем случае возможна разметка столбцов 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
на стороне клиента.
Обратите внимание, что реляционная модель отвечает на запросы в виде отношений
.
В вашем случае отношение выглядит следующим образом: «Этот человек удовлетворяет потребности многих спортивных автомобилей, бизнес-автомобилей и семейных автомобилей».
Реляционная модель всегда отвечает на этот конкретный вопрос четвертичным отношением.
Он не пропускает ни одного из членов отношения: вместо этого он просто устанавливает для них значение NULL
, которое является способом SQL
, чтобы показать, что член отношение не определено, не применимо и не имеет смысла.
Другие советы
Я в основном парень из Oracle, но есть большая вероятность, что то же самое применимо. Если я не понял, то, что вы хотите, невозможно на этом уровне - у вас всегда будет статическое количество столбцов. Ваш запрос может контролировать, является ли столбец пустым, но поскольку в самой внешней части запроса вы указали число столбцов X, вы гарантированно получите X столбцов в наборе результатов.
Как я уже сказал, я незнаком с MS SQL Server, но, полагаю, будет какой-то способ выполнения динамического SQL, и в этом случае вам следует изучить это, поскольку это позволит вам создать более гибкий запрос. р>
Вы можете сделать то, что хотите, сначала выбрав значения в виде отдельных строк во временной таблице, а затем выполнив PIVOT для этой таблицы (превратив строки в столбцы).
Важно, что если человек не заинтересованы в определенном типе автомобилей, что столбец для этого типа не будет быть включены в окончательный набор результатов. пример, чтобы убедиться, что это понятно:
Вы не сможете сделать это простым SQL. Я предлагаю вам сделать этот столбец NULL или ZERO.
Если вы хотите динамически расширять запрос при добавлении новых автомобилей, то PIVOTing может вам в чем-то помочь.
Есть три основных принципа, которым вы хотите научиться, чтобы облегчить эту работу. Первый - нормализация данных, второй - GROUP BY, а третий - PIVOT.
Во-первых, нормализация данных. Ваш дизайн таблицы людей не в первой нормальной форме. Столбцы "wantports", "wantfamily", "wantbusiness" quot; действительно повторяющаяся группа, хотя они могут не выглядеть таковыми. Если вы сможете изменить дизайн таблицы, вам будет полезно создать третью таблицу, назовем ее «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 будут появляться нули для людей, которые не хотят тип машины в столбцах результата.
Только что наткнулся на это сообщение через поиск в 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)