Объект Bookmark – это просто закладка. На практике это – самый удобный способ навигации по документам, созданных при помощи шаблонов (например, отчетов). Принципиальное отличие его от объектов Selection и Range заключается в том, что все выделения и диапазоны теряются при закрытии документа (объекты Range вообще существуют только во время работы создавшей их процедуры, а закладки сохраняются вместе с документом. Если документ создан на основе шаблона, то все закладки, которые были определены в шаблоне, будут определены и в созданном на основе этого шаблона документе.

Создать закладку (меню Вставка -Закладка ) намного проще, чем считать количество символов для объекта Range от начала документа/абзаца/предложения, или выполнять операции Move() (MoveDown(), MoveRight(), MoveNext()) для объекта Selection.

Функциональность объекта Bookmark невелика. Свойств и методов у этого объекта намного меньше, чем у объектов Selection и Range. Однако обычно никто и пытается использовать объект Bookmark для работы с текстом напрямую. Из объекта Bookmark очень просто получить объект Selection (при помощи метода Select()) или объект Range (при помощи свойства Range()) – и дальше можно пользоваться уже свойствами и методами этих объектов, например:

ThisDocument.Bookmarks("Bookmark1").Select

MsgBox Selection.Text

Создавать объекты Bookmark программным способом необязательно, но если есть необходимость, то можно использовать метод Add() коллекции Bookmark:

ThisDocument.Bookmarks.Add Name:="temp", Range:= Selection.Range

У этого метода – всего лишь два параметра, оба которых используются в примере.

Некоторые важные свойства объекта Bookmark

Empty – если это свойство возвращает True, то это значит, что закладка указывает на точку вставки, а не та текст;

Name – имя закладки. Очень удобно, что найти нужную закладку в коллекции закладок можно не только при помощи индекса (номера) закладки, но и по ее имени.

Range – возвращает объект Range на месте этой закладки.

Start, End, StoryType – аналогично таким же свойствам у объекта Selection.

Методов у объекта Bookmark всего три – Copy(), Delete() и Select(). Copy() – создает закладку на основе существующей, Delete() – удаляет ее, а Select() – выделяет то, на что ссылается закладка.

Объект Word.Range, программная работа с диапазоном в документе, свойства и методы объекта Range, преимущества по сравнению с объектом Selection

Как уже говорилось выше, чаще всего разработчиками для определения места ввода текста и навигации по документу используется объект Selection. Для этих же целей можно использовать и объект Range. Главное отличие между объектами Range и Selection заключается в том, что объект Selection может определить и пользователь (выделив текст мышью), а объект Range можно определить только программно, и он не зависит от текущего положения указателя или действий пользователя.

Формальное определение объекта Range выглядит так: это программный объект, который представляет непрерывный участок текста в документе. Этот объект не зависит от объекта Selection – вы можете работать с объектом Range, не изменяя текущего выделения. Он может не включать в себя ни одного символа (представлять курсор ввода текста).

Объектов Range в каждый момент времени может быть сколько угодно, а объектов Selection – только один.

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

Прежде чем мы приступим к рассмотрению конкретных примеров, создадим в базе данных пару необходимых таблиц.

Подготовка таблиц

Представим, что у нас в базе данных хранится информация об обоях, представленная в двух таблицах. Это таблица Oboi (обои) с полями id (уникальный идентификатор), type (тип обоев - бумажные, виниловые и др.), color (цвет), struct (структура) и price (цена). И таблица Ostatki (остатки) с полями id_oboi (ссылка на уникальный идентификатор в таблице Oboi) и count (количество рулонов на складе).

Заполним таблицы данными. В таблицу с обоями добавим 9 записей:

Бумажные

Мультиколор

Тисненые

Бумажные двухслойные

Виниловые

Тисненые

Флизелиновые

Тисненые

Бумажные двухслойные

Бумажные

Мультиколор

Виниловые

Коричневые

Флизелиновые

Тисненые

Тканевые

В таблицу с остатками - также девять записей:

Приступим к описанию порядка использования distinct в SQL.

Место distinct в предложении Select

Аргумент distinct следует помещать сразу после ключевого слова Select в запросах. Он применяется сразу ко всем столбцам, указанным в предложении Select, потому что будет исключать из итогового результата запроса абсолютно идентичные строки. Таким образом, достаточно один раз указать при написании запроса SQL «select distinct». Исключение составляет использование distinct внутри агрегатных функций, что рассмотрим чуть позднее.

Следует помнить, что большинство СУБД и не распознает ваш запрос вида:

SELECT distinct Ostatki.Count, distinct Oboi.*

INNER JOIN Ostatki ON Oboi.id = Ostatki.id_oboi

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

Применение distinct в стандартных запросах

Очевидно, что при грамотном построении структуры таблиц и их заполнении, внутри одной таблицы исключены ситуации, когда встречаются абсолютно идентичные строки. Поэтому выполнение запроса «Select distinct *» с выборкой из одной таблицы практически нецелесообразно.

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

Как видим, в таблице присутствуют дублирующиеся строки. Если же мы добавим в предложение Select distinct:

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

Применение distinct внутри агрегатных функций

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

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

то получим всего 119, ведь обои под артикулами 3 и 7 находятся на складе в одинаковом количестве. Однако очевидно, что этот ответ неверен.

Чаще всего в SQL distinct применяется с функцией Count. Так, без труда мы можем узнать, сколько уникальных видов обоев у нас вообще есть:

SELECT count(distinct Oboi.type)

И получить результат 5 - бумажные обычные и двухслойные, виниловые, тканевые и флизелиновые. Наверняка все видели рекламу типа: «Только у нас более 20 видов различных обоев!», под которой подразумевается, что в данном магазине не пара десятков рулонов всего, а обои самых разнообразных современных типов.

Интересно, что в одном запросе можно указывать несколько функций Count как с атрибутом distinct, так и без него. То есть это единственная ситуация, когда distinct в Select"е может присутствовать несколько раз.

Когда следует отказаться от применения аргумента

От применения аргумента SQL distinct следует отказаться в одном из двух случаев:

  1. Вы выполняете выборку из таблиц и уверены в уникальности значений в каждой. В таком случае применение аргумента нецелесообразно, ведь это дополнительная нагрузка на сервер или клиента (в зависимости от вида СУБД).
  2. Вы боитесь потерять нужные данные. Поясним.

Допустим, начальник просит вас вывести список обоев, которые у вас есть, с указанием всего двух столбцов - тип и цвет. По привычке вы указываете аргумент distinct:

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

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

Альтернатива distinct

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

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

SELECT

Основы запросов SELECT в Oracle SQL, базовый синтаксис запросов, список столбцов, псевдонимы и литералы, оператор q", ключевое слово DISTINCT

Основное средство для получения информации из базы данных - это запросы, то есть команда SELECT . Базовые возможности этой команды будут рассмотрены в этом разделе, другие, более сложные возможности - в следующих разделах. В каждом разделе будут указываться отличия по сравнению с аналогичными возможностями в Microsoft SQL Server .

Поскольку во всех главных базах данных поддерживается стандарт ANSI SQL , то базовые возможности и в Oracle , и в других базах данных (Microsoft SQL Server , IBM DB 2, Informix , Sybase , Microsoft Access ) будут схожи. Отличия - в частностях, которые стандарт SQL не определяет, но которые довольно существенны, и которые могут привести к сложностям для пользователей, которые привыкли создавать запросы к другим базам данных.

Базовый синтаксис команды SELECT в Oracle выглядит следующим образом:

SELECT [ DISTINCT ] список_столбцов FROM источник WHERE фильтр ORDER BY выражение_сортировки

Приведем пример такого запроса:

SELECT employee_id, first_name, last_name, hire_date FROM hr.employees WHERE hire_date < "01.01.2000" ORDER BY last_name

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

SELECT * FROM hr.employees WHERE hire_date < "01.01.2000" ORDER BY last_name

Выводимое имя столбца можно поменять при помощи псевдонима (alias ). Для этого достаточно просто написать имя пседвонима сразу после названия столбца или использовать ключевое слово AS (оба синтаксиса полностью равнозначны):

SELECT last_name Фамилия FROM hr.employees

SELECT last_name AS Фамилия FROM hr.employees

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

SELECT last_name AS Фамилия, salary*12 AS "Зарплата за год" FROM hr.employees

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

Отметим и некоторые особенности, связанные со списком столбцов в Oracle . Если в SQL Server "проблемные" имена столбцов (с пробелами и зарезервированными словами) можно было помещать в квадратные скобки (а обычно и в двойные кавычки - в зависимости от настроек сеанса), то в Oracle и тот, и другой вариант вызовет ошибку.

В список столбцов в Oracle можно включать литералы (literals ). Литералы - это любое строковое значение, дата или число, которое помещается в список столбцов и при этом не является ни названием столбца, ни псевдонимом. Литералы будут повторяться для каждого возвращаемого столбца. Обычно они используются для создания пояснений в возвращаемом наборе результатов:

SELECT last_name AS Фамилия, "Зарплата за год: " , salary*12 FROM hr.employees

В этом примере "Зарплата за год: " - это литерал, который будет выводиться для каждой записи.

Строковые литералы и литералы с датой должны заключаться в одинарные кавычки. Литералы в виде числовых значений в кавычки не помещаются.

Отметим, что литералы часто сливаются в выводимых результатах с возвращаемыми из базы данных значениями при помощи оператора конкатенации (||). Про операторы Oracle будет рассказываться в разделах 2.2 , 2.3 , .

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

select department _ name , q "[ It " s assigned manager ID : ]" , manager _ id from departments

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

Отметим некоторые особенности применения оператора q ":

  • можно использовать как строчную букву q , так и заглавную (Q) ;
  • после q должна идти обычная одинарная кавычка. Следующая одинарная кавчка определяет завершение области действия оператора q ;
  • сразу после одинарной кавычкой должен идти символ, выбранный пользователем для применения в качестве кавычек. Этот символ может быть любым, за исключением пробела, перевода строки или табуляции (в том числе и одинарная кавычка). Если пользователь выбрал символ (, {, [ или <, то закрываться кавычки должны соответствующим символом), }, ] и >. В других случаях закрытие кавычек производится при помощи того же символа, который использовался для их открытия.

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

SELECT salary FROM hr . employees

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

Чтобы вернуть только уникальные значения (или наборы значений, если возвращается несколько столбцов), в запросе можно использовать ключевое слово DISTINCT :

SELECT DISTINCT salary FROM hr.employees

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

Отличительной особенностью Oracle является то, что вместо DISTINCT можно использовать ключевое слово UNIQUE . SQL Server такого не позволяет.

(По материалам статьи Neil Boyle на swynk.com "Speed up SELECT DISTINCT queries")

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

select DISTINCT
au_fname,
au_lname
from authors

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

select DISTINCT
au_fname,
au_lname
from authors a join titleAuthor t
on t.au_id = a.au_id

Здесь нам нужно видеть только уникальные имена авторов, которые написали книги. Запрос будет работать, как требуется, но мы можем повысить его эффективность, если перепишем его так:

select au_fname, au_lname
from authors a
where exists (
select *
from titleAuthor t
where t.au_id = a.au_id
)

Причина более быстрой отработки запроса в том, что предложение EXISTS возвратит имя сразу же, когда найдена первая книга, и никакие другие книги этого автора далее не будут сканироваться (мы уже получили имя автора, и это всё, что нам нужно). С другой стороны, запрос DISTINCT возвращает одну копию имени автора для, каждой его книги и продолжил бы работу, пока не обработал бы весь список и не отсёк дубликаты согласно предложению DISTINCT. Вы можете исследовать план выполнения каждого из представленных запросов, чтобы увидеть причину повышения эффективности последнего примера.
Повышение эффективности зависит от соотношения количества попаданий строк в LEFT и RIGHT (или INNER и OUTER) таблиц. Следующий запрос будет работать в любой SQL Server базе данных. Пробуйте вставить эти два запроса в Query Analyser и сравнить их планы выполнения, а именно, как соотносятся I/O этой пары запросов для различных базах данных. Второй запрос обычно будет более эффективным, хотя фактическая эффективность может меняться.

select DISTINCT o.name
from sysobjects o
join sysindexes i
on o.id = i.id
where o.type = "U"

select o.name
from sysobjects o
where o.type = "U"
and exists (
select 1
from sysindexes i
where o.id = i.id
)

Вы должны понять особенности объединения двух (или больше) таблиц, чтобы использовать это для эффективного выполнения. Следующие два запроса для базы данных Northwind предназначены для возврата ID заказчика, у которого установлена скидка больше чем 2 процента для любой позиции. На первый взгляд они будут вести себя так же, как в предыдущем примере (форматы запросов схожи), но полученные фактические результаты окажутся другими.

select DISTINCT customerID
from orders o
join od
on o.OrderID = od.OrderID
where discount > 0.02

select customerID from orders o
where exists (
select *
from od
where o.OrderID = od.OrderID
and discount > 0.02
)

Разница эффективности выполнения этих запросов в том, что OrderID, который определяет зависимость между двумя таблицами, не является именем заказчика. Второй запрос возвратит множество имён заказчика - одно для каждой позиции, полученной заказчиком. Пробуйте добавить столбец OrderID в список SELECT, чтобы увидеть это.

Зарезервированные слова DISTINCTROW и DISTINCT в инструкции SELECT Jet SQL позволяют исключить из результирующего множества повторяющиеся строки. Разница между этими словами состоит в том, как при их использовании определяются повторяющиеся записи.

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

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

Замечание

Модификаторы могут использоваться не только в инструкции SELECT, но также и в запросах на добавление записей и на создание таблицы.

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

  1. Создайте новый запрос в базе данных "Борей". Для этого раскройте список запросов окна базы данных и нажмите кнопку Создать (New Query). В списке диалогового окна Новый запрос (New Query) выделите значение Конструктор (Design View) и нажмите кнопку ОК.
  2. Добавьте в запрос таблицы "Клиенты" (Customers), "Заказы" (Orders), "Заказано" (Orders Details) и "Товары" (Products). Access автоматически создает необходимые связи между таблицами.
  3. Перетащите поле "Название" (CompanyName) из списка полей таблицы "Клиенты" в первый столбец бланка запроса. Выделите ячейку Сортировка (Sort) и выберите значение По возрастанию (Ascending).
  4. Перетащите поле "Марка" (ProductName) из списка полей таблицы "Товары" (Products) во второй столбец бланка запроса. Задайте в этом столбце также сортировку по возрастанию.
  5. Нажмите на панели инструментов кнопку Запуск (Run) для выполнения запроса.

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

  1. Выберите команду меню Вид, Режим SQL (View, SQL View) или щелкните по стрелке на кнопке Вид (View) на панели управления и выберите элемент Режим SQL (SQL View). Откроется диалоговое окно, в котором выведена инструкция SQL, соответствующая построенному запросу.
  2. Введите слово DISTINCTROW сразу после слова SELECT. Запрос должен выглядеть гак, как показано на рис. 8.58.
  3. Нажмите кнопку Запуск (Run). Результирующее множество запроса будет включать 1695 записей.
  4. Снова выполните команду Вид, Режим SQL (View, SQL Mode) и замените в инструкции SQL СЛОВО DISTINCTRTOW СЛОВОМ DISTINCT.
  5. Нажмите кнопку Запуск (Run). Результирующее множество запроса содержит те же 1695 строк, что и при использовании ключевого слова DISTINCTROW.

Рис. 8.58.

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

Вместо того чтобы вносить эти модификаторы вручную в режиме SQL, можно просто установить соответствующие свойства запроса. Чтобы открыть диалоговое окно свойств запроса, щелкните правой кнопкой мыши по свободному полю в верхней панели окна Конструктора запроса и выберите из контекстного меню команду Свойства (Properties). Появляется диалоговое окно, представленное на рис. 8.57.

Два свойства в этом окне определяют использование модификаторов DISTINCTROW и DISTINCT: Уникальные значения (Unique Values) и Уникальные записи (Unique Rows). Свойство Уникальные значения соответствует модификатору DISTINCT, a свойство Уникальные записи - модификатору DISTINCTROW. Если вы попробуете установить эти значения, то увидите, что Access не позволит установить значения Да (Yes) для обоих свойств. Если одно из них имеет значение Да (Yes), то для второго автоматически устанавливается значение Нет (No). Если оба свойства имеют значение Нет (No), запрос будет включать в результирующий набор все записи.

Замечание

Свойство Уникальные записи имеет смысл только для запроса, который строится по нескольким таблицам. Если запрос имеет только одну таблицу, значение этого свойства игнорируется.