Разделы
Публикации
Популярные
Новые
Главная » Оптимизация производительности transact

1 ... 13 14 15 16 17 18 19 ... 55

с товарами, чтооы получить частное - клиентов, которые купили каждьп! элемент каталога. И, как в случае обычного алгебраического умиожспия, мы можем умножить таблицу-делитель на таблицу-частное (с использованием CROSS JOIN), чтобы получить подмножество таблицы-делп.мого.

Это лучше исследовать на при,\1ере. Ниже приведен пример запроса, выполняющего реляционное деление. В нем используются таблицы customers, orders и items, впервые иредставлеиные в главе 1. Если у вас все еще есть эти таблицы (они должны были быть созданы в базе данных GG TS), вам остается только добавить три записи в таблицу orders, прежде че.м продолжить:

IMSERT orders

VALUES(105.19991111.3.1001.123.45; INSERT orders

VALUES(106.19991127.3.1002,678.90) INSERT orders

VALUES(107.19990101.1.1003.86753.09;

Обратитесь к главе 1, если вам необходимы полные определения таб;гпц и остачь-ные данные. После того как таблицы и данные окажутся на своих местах, следующий запрос осуществит реляционное деление таблиц customers п orders tables, чтобы получить частное - клиентов, которые заказали по крайней мере по од-но.му экземпляру каждого товара;

SELECT c.LastName.с.FirstName FROM customers с WHERE NOT EXISTS (SELECT * FROM Items i

WHERE NOT EXISTS (SELECT *

FROM items t JOIN orders 0 ON (t. itemiNumber-o, ItemNumoer) WHERE t.ItemNumber=i.ItemNumber AND 0.CustomerNumber=c.CustomerNumber))

LastName FirstName

Doe John

Citizen John

Это может выглядеть немного непонятным, но на са.мом деле не все гак плохо, как может показаться с первого взгляда. Давайте рассмотрим запрос шаг за шаго.м. Такие запросы обычно лучше всего исследовать наоборот, так что начнем с самого вложенного подзапроса. Он связан и с таблицей items, и с таблицей customers. Количество раз, которое он запускается, равно количеству записей в таблице items, умноженному на количество записей в таблице customers. Запрос к items проходит таблицу items, используя подзапрос, чтобы найти товары, которые: а) имеют такой же номер, как и текущая запись в таблице items, и б) имеются в заказах, сделанных текущим клиентом в таблице customers. Каждая запись, соответствующая этим критериям, не включается (с похющью NOT EXISTS). Таким образом, остаются только те записи, которые есть в таблице items, ио отсутствуют в таблице orders. Другими словами, это товары, которые клиент ешс не заказывал. Внешний запрос - SELECT по таблице customers - затем исключает клиентов, для которых запрос по items возвращает записи - то есть всех клиентов с не заказанными товарами.



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

SELECT с.LastName. с.FirstName FROM customers с JOIN

(SELECT CustomerNumber. COUNT(DISTINCT ItemNumber) AS NumOfltems FROM orders

GROUP BY CustomerNumber) о ON (с.CustomerNumber=o.CustomerNumber) WHERE o.NumOfItems=(SELECT COUNT(*) FROM items)

LastName FirstName

Doe John

Citizen John

При этом подходе таблица customers соединяется с ггроизводной таблицей, которая возвращает номер каждого покупателя и количество заказанных им различных товаров. Это количество затем сравнивается с помощью подзапроса по таблице items с общим количеством товаров в каталоге. Клиенты, которые заказывали такое же количество товаров, какое и.меется в таблице Items, включаются в список. Вот другой вариант того же запроса;

SELECT с.LastName. с.FirstName FROM customers с

WHERE CustomerNumber IN (SELECT CustomerNumber EROM orders GROUP BY CustomerNumber HAVING COUNT(DISTINCT ItemNumber)= (SELECT COUNT(*) FROM items))

LastName FirstName

Doe John

Citizen John ,

В этом варианте подзапрос используется для формирования списка клиентов, для которых общее количество различных заказанных товаров равно количеству товаров в таблице Items - то есть тех, кто заказал по крайней мере одну единицу каждого товара. Мы разумно применяем GROUP BY, чтобы объединить но.мера клиентов для исключения дубликатов и для того, чтобы можно было использовать агрегат COUNT() в предложении HAVING. Заметьте, что хотя подзапрос задействует GROUP BY, он не вычисляет никакие агрегатные значения. Это разрешено и с точки зрения ANSI, и, очевидно, с точки зрения Transact-SQL. Основное предназначение GROUP BY - возможность применить агрегат COUNT() для фильтрации записей, возвращенных подзапросом. HAVING разрешает прямые ссылки на агрегатные функции; WHERE - нет. Вот подход, задействующий простое соединение для решения задачи:

SELECT с.LastName. с.FirstName

FROM customers с JOIN orders о ON (с.CustomerNumber=o.CustomerNumber) JOIN items 1 ON (o,ItemNumber-i.ItemNumber) GROUP BY c.LastName. c.FirstName

HAVING COUNT(DISTINCT o,ItemNumber)=(SELECT COUNT(*) FROM items)



iastNd.-ne F;rstNane

Сое Johr;

Cil-.zen John

При гаком подходе таблицы customers и orders соединяются с применением их столбцов CustomerNumber, затем результируюигее множество урезается, чтобы остались только те клиенты, для которых суммарное количество различных товаров равняется количеству записей в таблице items. И снова мы получае.м клиентов, которые заказали по Kpaiineii мере по одно.му экземпляру каждого товара из таблицы items.

Агрегатные функции

Агрегатные функцтп! cyьvпIpyют данные в столбце в едтн1ствеиное значение. Они могут сум.мировать все данные столбца, а могут подводить групповые итоги для этих данных. Агрегаты, которые су.ммируют па основании группируемых столбцов, также известны как векторные агрегаты.

SQL Server в настоящее вре.мя поддерживает восемь агрегатных фуикцн11; COUNTO, SUI4(), МАХ(), STDDEVO (среднеквадратичное отклопеиие),

STDDEVPO (среднеквадратичное отклонение совокупности), VAR() (дисперсия) и VARP() (дисперсия совокупности). Все из них, кроме COUNT(), автоматически игнорируют значения NULL. Если COUNT() передать название конкретного столбца, то она также будет игнорировать NULL-значения. Вот пример;

CREATE TABLE #testnun (cl int null) INSERT #testnull DEFAULT VALUES INSERT #testnun DEFAULT VALUES SELECT COUNT(*). COUNT(cl) FROM Itestnui)

Warning: Null value eliminated f-om aggregate.

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

SELECT COUNT(OISTINCT title i0) AS TotalTitles

FROM sales

TotalTitles

SELECT stor id. title id, SUM(qty) AS lotaiSold FROM sales

GROUP BY ston id. title id ORDER BY ston 1d. titlejd

(результаты сокращены)

ston id title id TotaiSoid

6380 BU1032 5



6380

PS2091

7066

РС8888

7066

PS2091

7067

PS2091

В первом примере используется ключевое слово DISTINCT, чтобы получить количество уникальных значений titlejd в таблице. Поскольку записи в таблице sales представляют собой индивидуальные покупки, одинаковые значения titlejd будут существовать по определению. Если включить ключевое слово DISTINCT, они будут проигнорированы при подсчете количества значений в столбце. Обратите внимание на то, что агрегаты с DISTINCT нельзя прн.менять при использовании операторов CUBE или ROLLUP.

Во втором примере вычисляется векторный агрегат с использованием столбцов storjd и titlejd. Другими словами, значение SUM() в каждой строке результирующего множества отражает сумму для конкретной комбинации storjd/titlejd. Так как в агрегате не были указаны ни ALL, пи DISTINCT, при агрегировании рассматриваются все записи в каждой группе.

Благодаря подзапроса.м, агрегатные функции могут применяться почти везде в операторе SELECT и могут также задействоваться в операторах INSERT, UPDATE и DELETE. Вот пример, показывающий использование агрегатных функций в предложении WHERE оператора SELECT, для ограничения возвращаемых записей:

SELECT t.title FROM titles t

WHERE (SELECT C0lJNT(s.titie 1d) FROM sales s WHERE s.t1tle idt,t1tlejd)>] title

Is Anger the Enemy?

The Busy Executives Database Guide

The Gourmet Microwave

Ha агрегат можно ссылаться в списке SELECT напрямую, с помощью ссылки на столбец (как было показано в предыдупп^х прн.мерах), или косвенно, с помощью подзапроса. Вот пример обоих типов ссылок: ,

SELECT stor id. COUNT (DISTINCT titlejd) AS titles sold.

100*CAST(COUNT(DISTINCT t1tle id) AS float) / (SELECT COUNT(*) EROM titles) AS percent of tota ! FROM sales GROUP BY stor id

stor id titles sold TotalSold

6380 2 11.1111111Ш11Ш

7066 2 11.111111111111111

7067 4 22,222222222222221 7131 6 33.333333333333336 7896 3 16.666666666666668 8042 5 27 777777777777779

Здесь COUNT(DISTINCT titlejd) представляет собой нря.мую ссылку, тогда как SELECT COUNT(*) - косвенную. Как и в нескольких других примерах, первый агрегат возвращает количество уникальных названий (titles) в таблице sales. Второй агрегат заключен в несвязагщый подзапрос. Он возвращает полное количество названий в таблице titles, чтобы запрос .мог вычислить процент книг, проданных



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

Агрегаты можно также задействовать в предложении HAVING запроса. Когда запрос имеет предложение HAVING, скорее всего он содержит агрегаты. Вот пример:

SELECT stor id. COUNT(DISTINCT titlejd) AS titles sold,

100*CAST(COUNT(OISTINCT title id) AS float) / (SELECT COUNT(*) FROM titles) AS percent of Jotal FROM sales GROUP BY stor id

HAVING COUNT(DISTINCT titlejd) > 2

stor id titles sold TotalSold

7067 4 22.222222222222221

7131 6 33,333333333333336

7896 3 16,666666666666668

8042 5 27.777777777777779

Это просто перефразировка предыдущего запроса, .мы добавили к нему предложение HAVING. HAVING фильтрует результирующее множество, так же как WHERE фильтрует сам оператор SELECT. Агрегат1Л обычно используются в предложении HAVING, так как их значения еще не вычислены и не доступны после обработки WHERE.

GROUP BY и HAVING

Предложения GROUP BY и HAVING тесно связаны с агрегатными функциями. GROUP BY разбивает таблтщу на группы, и каждая группа имеет свои агрегатные значения. Как я сказал ранее, HAVING офаничивает группы, возвращаемые GROUP BY. Любые столбцы, за исключением bit, text, ntext и image, могут при.меняться в предложении GROUP BY. Чтобы получить группы внутри групп, просто укажите более одного столбца. Вот простой пример, использующий GROUP BY:

SELECT st,stor name, t.type, SUM(s.qty) AS TotalSold FROM sales s JOIN titles t ON (s,title id=t,titlejd) JOIN stores St ON (s,storJd=st.stor id) GROUP BY st.stor name, t.type ORDER BY st.stor name, t,type

stor name

type

lotalSold

Barnums

popular comp

Barnums

psychology

Bookbeat

business

Bookbeat

mod cook

Bookbeat

popular comp

Bookbeat

UNDECIDED

Doc-U-Mat:

Quality Laundry and Books

mod cook

Doc-U-Mat;

Quality Laundry and Books

psychology

Eric the Read Books

business



Eric the Read Books

psychology

Fricative Bookshop

business

Fricative Bookshop

mod cook

News & Brews

psycho!ogy

News & Brews

trad cook

GROUP BY ALL генерирует все возможные группы -

даже те, которые не coot-

ветствуют критерию запроса. Агрегатные значения для групп, не соответствующих условия.м поиска запроса, возвращаются как NULL:

SELECT st.stor name. t.type. SUM(s.qty) AS TotalSold

FROM sales s JOIN titles t ON (s.title id=t.titlejd)

JOIN stores St ON (s.storJd=st.storjd)

WHERE t.type=business

GROUP BY ALL st.stor name. t.type

ORDER BY $t.stor name. t.type

stor name

type

TotalSold

Barnums

popular comp

NULL

Barnums

psychology

NUtt

Bookbeat

business

Bookbeat

mod cook

NULt

Bookbeat

popular comp

NULL

Bookbeat

UNDECIDED

NULL

Doc-U-Mat: Quality Laundry and Books

mod cook

NULL

Doc-U-Mat: Quality Laundry and Books

psychology

NULL

Eric the Read Books

business

Eric the Read Books

psychology

NULL

Fricative Bookshop

Business

Fricative Bookshop

mod cook

NULL

News & Brews

psychology

NULL

News & Brews

trad cook

NULL

GROUP BY ALL нельзя использовать совместно с операторами ROLLUP и CUBE и удаленны.ми таблицами. Он также перекрывается HAVING, как вы, наверное, и ожидали, так же как и обычный GROUP BY, HAVING фильтрует результаты GROUP BY.

Обратите внимание на применение ORDER BY в предыдущем примере. Вы больше не можете предполагать, что группы, полученные с помощью GROUP BY, будут отсортированы в соответствии с некоторым порядком. Это поведение отличается от поведения SQL Server версии 6.5 и более ранних, так что будьте осторожны. Если вам необходим конкретный порядок, используйте ORDER BY, чтобы гарантировать его. Хотя GROUP BY и HAVING обычно применяются совместно с агрегатами, их наличие не требуется. Если задействовать GROUP BY без агрегатов, из данных будут удалены дублирующиеся значения. Того же самого эффекта можно добиться, если указать DISTINCT перед групгшруе.мыми столбцами в списке SELECT, и фактически SQL Server обрабатывает запросы с GROUP BY без агрегатов так же, как обычные запросы с DISTINCT. Это означает, что для этих двух запросов будет сгенерирован одинаковый план выполнения:

SELECT s.title id

FROM sales s

GROUP BY s.titlejd

SELECT DISTINCT s.title id



FROM sales s

(Чтобы просмотреть план выполпепия в Query Analyzer, нажмите сочетание клавиш Qrl+K или выберите Show Execution Plan из меню Query перед выполнением запроса.)

Как мы обнаружили в разделе, посвящепно.м реляционно.му делению, иред-ложение GROUP BY без агрегатных функции! используется для .моделировантиг запросов SELECT DISTINCT. Если включить в запрос предложение GROUP BY, даже без агрегатов. .\южио отфтпьтровать результирующее множество, используя агрегат напрямую. В отличие от предложения WHERE, в предложении HAVING можно применять агрегаты без включения их в подзапрос. Один из ири.меров реляционного деления, приведепиыр! выше, использует этот факт для фильтрации загшсей возвраще1П1ЫХ подзапросом с по.люшью агрегата в предложении HAVING.

Сводные таблицы

Довольно часто возникает необходи.мость преобразовать вертикально расположенные данные в горизонтальные таблицы, которые мож1ю применять в отчетах и пользовательском интерфейсе. Эти таблицы нзвеспш! как сводные, или перекрестные (crosstabulations, cros.s-tabs), и они представляют собой основной элемент любого приложения OLAP (ОпИпе Analytical Processing, оперативная аналитическая обработка), EIS (Executive Information System, информационная система руководителя) или DSS (Decision Support System, система поддержки принятия решений),

SQL Server включает широкий набор инструментов для гюддержки OLAP, описание которых выходит за рамки это11 книги. Установите OLAP Services с компакт-диска SQL Server и почитайте документацию продукта для получения более подробной информации. Однако задача преобразования вертикальных данных хорошо соответствует теме этой книги и довольно просто реализуется на Transact-SQL. Предположи.м, что у нас есть таблица с квартальны.ми обьема.ми продаж:

CREATE TABLE Icrosstab iyr int, qtr int, sales money)

INSERT #crosstab VALUES (1999, 1, 44)

INSERl #crosstab VALUES (1999, 2, 50)

INSERT #crosstab VALUES (1999, 3, 52)

INSERT #crosstab VALUES (1999, 4, 49)

INSERT #crosstab VALUES (2000, 1, 50)

INSERT #crosstab VALUES (2000, 2, 51)

INSERT Icrosstab VALUES (2000, 3. 48)

INSERT #crosstab VALUES (2000. 4. 45)

INSERT #crosstab VALUES (2001. 1. 46)

INSERT #crosstab VALUES (2001. 2. 53)

INSERT #crosstab VALUES (2001. 3, 54)

INSERT #crosstab VALUES (2001, 4, 47)

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

SELECT

уг AS Year,

SUM(CASE qtr WHEN 1 THEN sales ELSE NULL END) AS 01, SUM(CASE qtr WHEN 2 THEN sales ELSE NULL END) AS 02, SUM(CASE qtr WHEN 3 THEN sales ELSE NULL END) AS 03,



Year

Total

1999 2000 2001

44.0000 50.0000 46.0000

50.0000 51.0000 53.0000

52.0000 48.0000 54.0000

49.0000 45.0000 47.0000

195.0000 194.0000 200.0000

Обратите внимание на го, что нет необходимости суммировать столбцы Qn, чтобы получить сумму за год. Запрос уже группирует данные по столбцу уг; все, что необходимо сделать, чтобы получить сумму за год, - добавить простой агрегат. В этом случае нет необходимости применять подзапрос, производную таблицу или другую экзотическую конструкцию, если только в таблице sales не существует записей, не попадающих в 1-4-й кварталы, чего и не должно быть. За счет наличия столбца qtr создать запрос оказалось очень просто, даже слишком просто. На практике данные очень редко содержат столбец, отражающий квартал, чаще всего временные измерения создаются, исходя из дат члетюв последовательности. Вот пример, иллюстрирующий это, в нем используется таблица Orders базы данных Northwind. Столбец OrderDate каждого заказа преобра-.зуется в соответствующую временную границу:

SELECT

OATEPARTCyy.OrderDate) AS Year.

COUNTCCASE OATEPARTCqq.OrderDate) WHEN 1 THEN 1 ELSE NULL END) AS Ql. COUNTCCASE OATEPARTCqq.OrderDate) WHEN 2 iHEN 1 ELSE NULL END) AS Q2. COUNTCCASE DATEPARTCqq.OrderDate) WHEN 3 THEN 1 ELSE NULL END) AS 03, COUNTtCASE OATEPARTCqq.OrderDate) WHEN 4 THEN 1 ELSE NULL END) AS Q4. COUNT(*) AS TotalNumberOfSales FROM Orders

GROUP BY OATEPARTCyy.OrderDate) ORDER BY 1

Year

Total.NumberOfSales

1996

1997

199B

Этот запрос возвращает количество заказов за каждый квартал и за каждый год. Здесь используется функция DATEPART() для выделения элементов дат. Запрос проходит таблицу Orders, и функция CASE вычисляет для каждого значения OrderDate квартал, в который оно попадает, и возвращает 1 - если заказ соответствует данному кварталу, или NULL, если нет.

CUBE и ROLLUP

Операторы CUBE и ROLLUP предложения GROUP BY добавляют в результирующее множество итоговые записи. CUBE создает много.мертгый куб, из.мерения которого определяются столбцами, указанными в предложении GROUP BY. Этот куб - развертка данных таблицы, представляющая собой все возможные ко.мбинации измерений.

SUMCCASE qtr WHEN 4 THEN sales ELSE NUl L END) AS Q4. SUM(sales) AS Total FROM #crosstab GROUP BY yr



Оиерато]) ROLLUP, наоборот, осуществляет иера1)хическое сум.\н1рованне данных. Итоговые записи добавляются в результирующее хиюжество на основании иерархии группируе.мых столбцов, слева направо. Вот пример, использующий оператор ROLLUP для генерации про.межуточиых и су.\ьмарных итоговых записей:

SELECT CASE GROUPING(st.stor ndnie) wHEN С THEN st.stor nanie ELSE ALL END AS Store. CASE GROUPING(t,type) UHEN 0 THEN t.type ELSE ALL TYPES END AS Type, SUK(s,qty) AS TotalSold

FRCM sales s JOIN titles t ON (s,tit:e id=t,title id) JOIN stores st ON (s,stor id=st.sto.r id) GROUP BY 5t.stor name. t.type WITH ROLLUP

Store

Type

TotalSold

Barnums

popular coinp

Ba.nums

psychology

Barлums

ALL TYPES

Bookbeat

business

Bookbeat

mod cook

Bookbeat

popular comp

Bookbeat

UNDECIDED

Bookbeat

ALL TYPES

Doc-U-Mat: Quality Laundry and Books

mod cook

Doc-U-Mat: Quality Laundry and Books

psychology

Doc-U-Mat: Quality Laundry and Books

ALL TYPES

Eric the Read Books

business

Eric the Read Books

psychology

Eric the Read Books

ALL TYPES

Fricative Bookshop

business

Fricative Bookshop

mod cook

Fricative Bookshop

ALL TYPES

News & Brews

psychology

News S Brews

trad cook

News & Brews

ALL TYPES

ALL TYPES

Данный запрос имеет несколько примечательных особенностей. Во-первых, обратите внимание на дополнительные записи, которые ROLLUP вставил в результирующее множество. Так как запрос производит группировку на основании столбцов stor name и type, ROLLUP создает итоговые записи сначала для каждой группы stor name (значение ALL TYPES), затем для всего результирующего множества. Функция GROUPINGO служит для создания метки для каждого группи-руе.мого столбца. Обычно сгруппированные столбцы возвращаются как NULL Используя GROUPINGO, запрос может преобразовать NULL к че.му-нибудь более осмысленному. Вот тот же самый запрос, на этот раз с применением CUBE:

SELECT CASE GROUPlNGCst.stor name) WHEN 0 THEN st.stor name ELSE ALL END AS Store. CASE GROUPING(t.type) WHEN 0 THEN t.type ELSE ALL TYPES END AS Type. -SUM(s.qty) AS TotalSold

FROM sales s JOIN titles t ON (s.title id=t.title id) JOIN stores st ON (s.stor id=st.stor id) GROUP BY st.stor name, t.type WITH CUBE

Store Type TotalSold

Barnums popular comp 50

Barnums psychology 75

Barnums ALL TYPES 125



Bookbeat

business

Bookbeat

mod cook

Bookbeat

popular comp

Bookbeat

UNDECIDED

Bookbeat

ALL TYPES

Doc-U-Mat: Ouality Laundry and Books

mod cook

Doc-U-Mat: Ouality Laundry and Books

psycho!ogy

Doc-U-Mat: Ouality Laundry and Books

ALL TYPES

Em с the Read Books

business

Eric the Read Books

psychology

Eric the Read Books

ALL TYPES

Fricative Bookshop

business

Fricative Bookshop

mod cook

Fricative Bookshop

ALL TYPES

News & Brews

psycho!ogy

News & Brews

trad cook

News & Brews

ALL TYPES

ALL TYPES

business

mod cook

popular comp

psycho!ogy

trad cook

UNDECIDED

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

Без детального знания ваших данных практически невозможно узнать, сколько записей вернет CUBE. Однако очень просто вычислить верхний предел количества возможных записей. Он равен взаимному произведению количества уникальных значений +1 для каждого столбца группировки. необходимо для записей итогов ALL, генерируемых для каждого атрибута. В нашем случае мы имеем шесть различных магазинов и шесть различных типов книг в таблице sales. Это означает, что .максимальное количество записей, которые вернет CUBE, - сорок девять ((6 -F 1) x (6 -F 1)). В нашем случае записей м'еньше, так как не каждый магазин продал каждый тип книги. Также обратите внимание, что CUBE не генерирует нулевые промежуточные итоги для типов книг, которые данный магазин не продавал. Эти данные могли бы быть полезны, чтобы мы могли увидеть, что продает магазин, а что - нет. Такой полный куб позволяет получить более однородное результирующее множество, по которому проще строить отчеты и диаграммы. Вот версия последнего запроса, создающая полный куб:

SELECT

CASE GROUPING(st.stor name) WHEN О THEN st.stor name ELSE ALL END AS Store, CASE GROUP ING(S,type) WHEN 0 THEN S,type ELSE ALL TYPES END AS Type, SUM(s,qty) AS TotalSold FROM

(SELECT DISTINCT st,stor 1d, t,type, 0 AS qty FROM stores St CROSS JOIN titles t UNION ALL

SELECT s.stor Td, t,type, s.qty FROM sales s JOIN titles t

ON s.t1t!ejd=t.title id) s JOIN stores St ON (s.stor id=st.storjd) GROUP BY st.stor name, s.type WITH CUBE



1 ... 13 14 15 16 17 18 19 ... 55
© 2004-2024 AVTK.RU. Поддержка сайта: +7 495 7950139 в тональном режиме 271761
Копирование материалов разрешено при условии активной ссылки.
Яндекс.Метрика