среда, 27 марта 2013 г.

Макросы в pgAdmin. Количество записей в таблице и размер в мегабайтах.


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


  1. Макрос для получения количества строк в таблице, чье название выделено в окне Query Tool.

    SELECT COUNT(*) FROM $SELECTION$

  2. Макрос, позволяющий определить размер объекта, в название которого входит выделенный в Query Tool текст. Среди таких объектов могут быть таблицы, индексы, последовательности (sequence).

    SELECT pg_class.relkind AS "Тип", pg_namespace.nspname AS "Схема", pg_class.relname AS "Имя", 
    pg_class.relpages::FLOAT*8192/1024/1024 AS "Размер (Мб)", 
    pg_class.reltuples AS "Записей" FROM pg_class
    LEFT JOIN pg_namespace ON pg_class.relnamespace=pg_namespace.oid 
    WHERE 
    NOT pg_class.relname LIKE 'pg_%' 
    AND NOT (pg_namespace.nspname ILIKE 'pg_%' OR pg_namespace.nspname ILIKE 'information_%') 
    AND pg_class.relname ILIKE ('%' || TRIM('$SELECTION$') ||  '%')  ORDER BY pg_class.relpages DESC;


Благодаря этим макросам можно оперативно оценить объем тех или иных объектов в базе.

вторник, 26 марта 2013 г.

Макросы в pgAdmin. Список таблиц и поля таблицы.


Я работаю программистом и мне часто приходится иметь дело с базами данных. Основная часть моих обязанностей на текущей работе -это разработка схемы БД для Postgresql и создание функций на языке pgSQL. Для разработки кода функций используется программа pgAdmin 3, поставляемая вместе с PostgreSQL. Программа бесплатна, но не всегда удобна. Особенно это касается задач, связанных с созданием структуры СУБД. Для этих целей лучше всего подходит PostgreSQL Maestro, чей дружелюбный интерфейс позволяет упростить и ускорить построение структуры базы.
Однако за долгое время работы с этими программами я заметил, что pgAdmin гораздо стабильней работает, чем PostgreSQL Maestro. Последняя в случае недоступности сервера БД может просто  завершиться с ошибкой, не давая возможности сохранить набранный текст. Другой проблемой этой программы является отображение не в той кодировке сообщений от сервера БД. Из-за этого часто невозможно узнать, в чем причина ошибки того или иного запроса. Поэтому для написания и отладки функций я использую родную программу pgAdmin.
Однако и при написании кода возникает потребность узнать точное наименование таблицы ее структуру. Раньше приходилось постоянно лазить дереву с таблицами и искать нужные названия полей. Это замедляло написание коды. Однако недавно я открыл для себя
замечательную функцию этой программы - макросы.

Макрос - это команда SQL, которая может запускаться по нажатию определенного сочетания клавиш в окне Query Tool.Макросы предназначены для выполнения каких-либо типовых операций.
Чтобы создать макрос, необходимо нажать на команду "Макрос" в главном меню Query Tool. 


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



Макросу нужно задать имя, а затем нажать кнопку "Сохранить".
 Текст макроса представляет собой обычный запрос, за исключением специального ключевого слова $SELECTION$. В это слово подставляется выделенный при редактировании в Query Tool текст. 
Получается, чтобы узнать какую-либо информацию о таблице, вовсе не обязательно писать полный запрос к этой таблице, достаточно написать ее имя, выделить данное имя и запустить макрос, запрашивающий нужную информацию.
А теперь приведу примеры полезных макросов, которые я использую в своей работе.

  1.  Макрос, получающий список таблиц для схемы public базы данных PostgreSQL.


    SELECT * FROM information_schema.TABLES
    WHERE 
    table_type='BASE TABLE' AND table_schema='public'
    ORDER BY table_name


  2.  Макрос, получающий список полей в таблице, чье название выделено (очень не хватало этой возможности, когда я перешел с MS SQL Server 2008, в Microsoft Management Studio она была по-умолчанию).

    SELECT quote_ident(nspname) || '.' || quote_ident(relname) AS table_name, 
           quote_ident(attname) AS field_name, 
           format_type(atttypid,atttypmod) AS field_type, 
           case when attnotnull then ' NOT NULL' else '' end AS null_constraint,
           case when atthasdef then 'DEFAULT ' || 
                                    ( SELECT pg_get_expr(adbin, attrelid) 
                                        FROM pg_attrdef 
                                       WHERE adrelid = attrelid AND adnum = attnum )::text else ''
           end AS dafault_value,
           case when nullif(confrelid, 0) IS NOT NULL
                then confrelid::regclass::text || '( ' || 
                     array_to_string( ARRAY( SELECT quote_ident( fa.attname ) 
                                               FROM pg_attribute AS fa 
                                              WHERE fa.attnum = ANY ( confkey ) 
                                                AND fa.attrelid = confrelid
                                              ORDER BY fa.attnum 
                                            ), ','
                                     ) || ' )'
                else '' end AS references_to
      FROM pg_attribute 
           LEFT OUTER JOIN pg_constraint ON conrelid = attrelid 
                                        AND attnum = conkey[1] 
                                        AND array_upper( conkey, 1 ) = 1,
           pg_class, 
           pg_namespace
     WHERE pg_class.oid = attrelid
       AND pg_namespace.oid = relnamespace
       AND relname = btrim( '$SELECTION$' )
       AND attnum > 0
       AND NOT attisdropped
     ORDER BY attrelid, attnum;
 
Таким образом, когда, в случае разработки, я забываю точное название таблицы, я выполняю команду для первого макроса. И у меня перед глазами появляется весь список таблиц схемы public. А когда мне хочется узнать список полей данной таблицы, то я просто выделяю ее название и запускаю второй макрос. Теперь разработка PgSql-функций для меня значительно ускорилась :)


воскресенье, 10 февраля 2013 г.

Активация расширений Google Chrome по нажатию клавиш

Все больше открываю для себя замечательных возможностей браузера Google Chrome. Мне как программисту, освоившему метод слепой печати, не всегда нравится серфить в Интернете, используя только мышь. При этом, я часто сохраняю полезные и интересные заметки из Интернета с помощью сервиса www.evernote.com. В моем браузере установлен плагин Evernote Web Clipper, который позволяет выделенный на веб-странице текст или всю веб страницу отправить в мой блокнот для последующего ознакомления. Для запуска этого плагина я всегда использовал контекстное меню и не знал, что каждому установленном плагину в Chrome для запуска можно назначить функциональную клавишу. Это делается следующим образом:


  1. Кликаем на пункт "Настройки" главного меню браузера
  2. Слева будет один из трех пунктов - "Расширения". Кликаем по нему.
  3. Пролистав список установленных расширений внизу справа Вы увидите пункт "Настроить команды"
  4. Появится вот такое окошечко, где можно задать комбинации клавиш для активизации плагина (в данном случае комбинация Ctrl+E запускает плагин Evernote Web Clipper)

Этот способ позволил мне ускорить сохранение заметок в Evernote, и сделал еще более продуктивным процесс получения знаний посредством Интернет-серфинга.

пятница, 5 октября 2012 г.

Динамический PIVOT в Microsoft SQL Server


Необходимо из исходной таблицы получить сводную таблицу (см. рис.).  В качестве столбцов в новой таблице  будут годы, в строках названия клиентов, а в ячейках – суммы по полю [Count], соответствующие данному клиенту и году. Сложность заключается в том, что неизвестно количество лет, следовательно,  количество столбцов в новой таблице должно вычисляться динамически.


После некоторых размышлений и исследования возможностей Microsoft SQL Server  2008 R2 был разработан следующий код:

  1. --Создаем тестовую таблицу
  2. CREATE TABLE Table1
  3. (
  4. [Customer] VARCHAR(10),
  5. [MONTH] INT,
  6. [YEAR] INT,
  7. [COUNT] INT
  8. )
  9. GO
  10. --вставляем данные в таблицу
  11. INSERT INTO Table1([Customer],[MONTH],[YEAR],[COUNT])
  12. VALUES ('A',9,2011,1),
  13. ('A',9,2011,8),
  14. ('A',9,2012,1),
  15. ('B',9,2011,3),
  16. ('B',10,2012,2),
  17. ('B',10,2012,2)
  18. GO
  19. --создаем переменную для хранения строки с заголовками столбцов
  20. DECLARE @columns VARCHAR(8000)
  21. SELECT @columns = COALESCE(@columns + ',[' + CAST([YEAR] AS VARCHAR) + ']', '[' + CAST([YEAR] AS VARCHAR)+ ']')
  22. FROM Table1
  23. GROUP BY [YEAR]
  24. DECLARE @query NVARCHAR(4000)
  25. --динамически конструируем текст запроса
  26. SET @query = 'select * from (select
  27. [customer],[Year],[Count] from Table1
  28. )AS SourceTable
  29. Pivot(sum([count]) for [Year] IN (' + @columns + ')) AS PVT;'
  30. --выполнение запроса с помощью хранимой процедуры
  31. EXECUTE SP_EXECUTESQL @query




Данный код создает таблицу Table1, соответствующую исходной таблице на рисунке. Затем 
переменной @COLUMNS присваивается строка с  годами через запятую, которые считываются запросом, с применением функции COALESCE. После этого можно сконструировать запрос, создающий сводную таблицу. Данный запрос в виде строки сохраняется в переменной @query и выполняется посредством хранимой процедуры sp_executesql. Результат выполнения запроса:


среда, 19 сентября 2012 г.

Как сохранить видеоролик с Youtube во время просмотра


При просмотре видеоролика на Youtube часто хочется его сохранить на компьютер не уходя с текущей страницы. При этом не возникает желания заходить на веб-сайты, типа http://ru.savefrom.net, которые предоставляют возможность загрузки роликов. Способ, который я опишу, подходит для пользователей браузера Mozilla Firefox.

  • Шаг 1. Для начала вам понадобится зайти на страницу https://addons.mozilla.org/en-US/firefox/addon/greasemonkey и нажать кнопку "Add to Firefox". Расширение Greasemonkey, которое установится в ваш бразуре, позволяет дополнять различные сайты пользовательскими JavaScript-скриптами и выполнять эти скрипты в Firefox. Это дает возможность расширять функции сайтов, на которые настроены дополнительные скрипты.
  • Шаг 2. Далее необходимо зайти на страницу http://userscripts.org/scripts/show/25105 и нажать кнопку "Install". После этого, разрешите установку дополнительного скрипта.
  • Шаг 3. Проверка работы установленного дополнения. Зайдите на Youtube и убедитесь, что при просмотре видеоролика появилась кнопка "Скачать".

воскресенье, 9 сентября 2012 г.

Тестовые SQL-задачи от работодателя

Как-то раз мне пришлось решать тестовые задачи от одного работодателя. Компания занимается поддержкой и разработкой веб-сервисов на основе технологий Microsoft. Я претендовал на должность системного аналитика и разработчика баз данных, работающего с Microsoft SQL Server.  Данный тест мною был успешно пройден, но я не устроил кадровиков компании по желаемой зарплате. В итоге, предпочтение отдали другому, менее притязательному кандидату. По-моему, зарплата в 60000 руб по Москве является небольшой для человека, владеющими подобными технологиями. Таково уж мое мнение. Итак, следующие задачи:


Вопрос 1
Дана таблица:

CREATE TABLE dbo.call 
  ( 
     subscriber_name VARCHAR(64) NOT NULL, 
     event_date      DATETIME NOT NULL, 
     event_cnt       INT NOT NULL 
  ) 

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

Результат:
subscriber_name
min_date
max_event_cnt
max_date
min_event_cnt
Subscriber1
20091012
15
20061012
10
Subscriber2
20080301
20
20090513
8



Решение задачи:

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

with ish as (select subscriber_name,max(event_cnt) as max_event_cnt,min(event_cnt) as min_event_cnt from call
group by subscriber_name)
select
subscriber_name,
(select min(event_date) from call where subscriber_name=ish.subscriber_name and event_cnt=ish.max_event_cnt) as min_date,
max_event_cnt,
(select max(event_date) from call where subscriber_name=ish.subscriber_name and event_cnt=ish.min_event_cnt) as max_date,
min_event_cnt
from ish;


Вопрос 2 
Как бы вы оптимизировали следующий запрос (показан полный скрипт таблицы; приведите обоснование своего выбора)?

CREATE TABLE dbo.call 
  ( 
     id              INT IDENTITY PRIMARY KEY CLUSTERED, 
     subscriber_name VARCHAR(64) NOT NULL, 
     event_date      DATETIME NOT NULL, 
     subtype         VARCHAR(32) NOT NULL, 
     type            VARCHAR(128) NOT NULL, 
     event_cnt       INT NOT NULL 
  ) 

SELECT * 
FROM   dbo.call 
WHERE  subscriber_name = @a 
       AND event_date > @b 
       AND subtype = @c 


Решение задачи:

Необходимо создать некластерный и отфильтрованный индекс.
Скорей всего поле subtype, участвующее в запросе, имеет ограниченный набор значений.  По каждому из этих значений было бы необходимо создать отфильтрованный индекс, пример:


create nonclustered index call_idx_subtype_val1 on dbo.call(subscriber_name,event_date,subtype) where subtype='val1';
create nonclustered index call_idx_subtype_val1 on dbo.call(subscriber_name,event_date,subtype) where subtype='val2';

...

--Пример запроса, использующего индекс

select *
from dbo.call
where subscriber_name = 'Ivanov' and event_date >
convert(datetime, '2014-08-22 00:00:00.000',  121)
 and subtype = 'val1';




Вопрос 3
Из таблицы следующей структуры:
CREATE partition FUNCTION pf_monthly(datetime) AS range RIGHT FOR VALUES ( 
'20120201', '20120301', '20120401', '20120501', '20120601', '20120701', 
'20120801', '20120901', '20121001', '20121101', '20121201') 

go 

CREATE partition scheme ps_monthly AS partition pf_monthly ALL TO ([primary]) 

go 

CREATE TABLE dbo.order_detail 
  ( 
     order_id      INT NOT NULL, 
     product_id    INT NOT NULL, 
     customer_id   INT NOT NULL, 
     purchase_date DATETIME NOT NULL, 
     amount        MONEY NOT NULL 
  ) 
ON ps_monthly(purchase_date) 

go 

CREATE CLUSTERED INDEX ix_purchase_date 
  ON dbo.order_detail(purchase_date) 

go 

Необходимо удалить случайно внесенные данные по клиенту с id 42, за период с мая по июнь (включительно) 2012-го года, что составляет более 80% записей за этот период. В таблице несколько миллиардов записей. Какие есть способы решения данной задачи?


Решение задачи:

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


declare @PartMay  int;
declare @PartJune  int;
--получаем номер секции за май
set @PartMay=(SELECT  $partition.order_detail('20120501'));
--получаем номер секции за июнь
set @PartJune=(SELECT  $partition.order_detail('20120501'));

--создание таблицы с данными по Маю
create table dbo.order_detailMay         (
                               order_id int       not null
                ,              product_id int   not null
                ,              customer_id int               not null
                ,              purchase_date datetime not null           
                ,              amount               money                 not null
);

--создание таблицы с данными по июню
create table dbo.order_detailJune         (
 order_id int      not null
                ,              product_id int   not null
                ,              customer_id int               not null
                ,              purchase_date datetime not null           
                ,              amount               money                 not null
);

--переключаем секции с маем и июнем на отдельные таблицы
alter table dbo.order_detail switch partition @PartMay to dbo.order_detailMay;
alter table dbo.order_detail switch partition @PartJune to dbo.order_detailJune;

--удаление ошибочных данным по клиенту 42
delete from dbo.order_detailMay where customer_id=42;
delete from dbo.order_detailJune where customer_id=42;

--переключим очищенные таблицы в качестве секций основной таблицы
ALTER TABLE dbo.order_detailMay switch TO dbo.order_detail PARTITION @PartMay;
ALTER TABLE dbo.order_detailJune switch TO dbo.order_detail PARTITION @PartJune;



Вопрос 4

Какое отличие(я) между delete from dbo.my_table и truncate table ?

Мои ответы:

1. delete логирует удаление построчно,  truncate – по страницам  данных в таблице, поэтому журнал транзакций у delete больше
2. delete блокирует каждую удаляемую строку, truncate – всю таблицу
3. триггеры срабатывают только на delete
4. truncate нельзя использовать, если на поле таблицы ссылается  внешний ключ
5. truncate нельзя использовать, если таблица участвует в репликации
6. truncate быстрее delete


Вопрос 5

Система успешно работала полгода, затем неожиданно производительность серьезно деградировала. Каковы возможные проблемы, пути решения?

Мои ответы:

1) Возможно проблема с блокировками ресурсов. Из консоли выполняем EXEC
sp_lock, находим блокировки с монопольным доступом (X), далее удаляем
монопольные блокировки с помощью команды kill.

2) Проблемы с аппаратной частью – возможно отказывает жесткий диск. Проверить DBCC CHECKDB

3) Фрагментация индексов.  Для проверки – выполняем

SELECT OBJECT_NAME(OBJECT_ID), index_id,index_type_desc,index_level,
avg_fragmentation_in_percent,avg_page_space_used_in_percent,page_count
FROM sys.dm_db_index_physical_stats
(DB_ID(N'database'), NULL, NULL, NULL, NULL)
ORDER BY avg_fragmentation_in_percent DESC


и проверяем значение avg_fragmentation_in_percent. Если оно малое (от 5 до 30 %), то выполняем ALTER INDEX REORGANIZE, если большее, то выполняем ALTER INDEX REBUILD. Индексы меньше фрагментируются, если их создавать с FILLFACTOR<100>

4) Медленные запросы на больших таблицах. Необходимо определить время выполнения запросов с помощью SQLProfiler. Для них  проанализировать планы выполнения, выявить использование индексов. Возможная ошибка проектирования – использование кластерных индексов на таблицах,  в которых столбцы обновляются очень часто.


Каталог блогов Blogolist