Главная » Статьи » Excel » Формулы рабочего листа |
Вы читаете вторую статью моего цикла "В помощь аналитику". С первой статьёй можно ознакомиться здесь. Напомню, что в первой части мы:
Цели этого этапа
Файл для скачиванияH2A-P2.ZIPОбзор рабочих листовЛист DATAТут хранится ВСЯ информация, которая потом в агрегированном виде появляется в отчёте. Предполагается, что в реальном мире данный отчёт будет залинкован на таблицу SQL или Access. В сравнении с файлом из первой части цикла у нас произошло одно изменение - добавился столбец MTDRE. Изучая отчёт, я вдруг осознал, что для секции MTD (Month to date) мне надо сравнивать факт за месяц с роликом (RE) за месяц, а ролика в таблице нет, так как на его месте лежит уже факт. Поэтому пришлось этот самый RE для конкретного месяца добавить и ввести дополнительное измерение (колонка MTDRE), чтобы иметь возможность разделять эти данные. Таким образом, если в строке лежит RE за текущий месяц, то MTDRE=1. Кстати говоря, мой отчёт за АПРЕЛЬ, так как PnL готовится ежемесячно и надо было выбрать какой-то месяц. На листе созданы именованные диапазоны для каждой колонки с данными. Эти диапазоны удобно создавать через функцию ФОРМУЛЫ -> Создать из выделенного. Если вы в будущем будете линковать лист DATA на источник внешних данных (Access, SQL), то позаботьтесь об автоматическом переопределении данных диапазонов через VBA макрос. ![]() Для информации: объём наших данных - около 9400 строк, 10 столбцов, в отдельном файле XLSX занимает 450 Кб. Лист REFВспомогательный лист, на котором хранится таблица-справочник для замены языковых элементов отчёта на нужный вариант, а также объявлено 2 параметра через именованные диапазоны: selLang (текущий язык отчёта) и selCurMonth (текущий месяц отчёта - выше мы говорили про Апрель). Лист SELECTДанный лист играет важную роль - на нём производятся ВСЕ расчёты и хранятся ВСЕ данные, которые могут потребоваться на листе PnL. Он поделен на несколько сегментов:
Лист PnLЭто сам отчёт P&L. На этом листе НЕ производится никаких расчётов, связанных с данными. Тут сосредоточено всё, что касается визуализации данных. Это очень правильное решение - разделять расчёты и визуализацию этих расчётов. Для сложных отчётов она сильно облегчает вам задачу, позволяя сосредоточиться либо на расчётах, либо на том, как должны выглядеть ваши данные. На листе есть кнопка вызова формы, при помощи которой пользователи настраивают внешний вид отчёта. Изменения в способе извлечения данныхК сожалению, мне пришлось отказаться от разрекламированной мною в первой части функции СУММЕСЛИМН (SUMIFS), так как данная функция не может обеспечить удобный отбор значений критериев по условию ИЛИ. ![]() Критерий в виде именованного диапазона valGEO принимает значения "G1", "G2", "G3", "G4", "G5", либо "*", то есть либо одна территория, либо все вместе. Я же хотел дать пользователям возможность выбирать набор территорий по своему вкусу. Я видал в интернете варианты решения вместо valGEO писать {"G1";"G3";"G5"}, но у меня не получилось это заставить работать в Excel 2013. Если кто-то знает как это должно работать, то непременно мне сообщите! В виду вышеописанной причины я был вынужден отказаться от СУММЕСЛИМН (SUMIFS) в пользу СУММПРОИЗВ (SUMPRODUCT). Проблема в том, что формула это хоть и более гибкая, но она ужасно медленная, так как обрабатывает гораздо больше данных. К тому же список параметров расширился в простейшем случае с 5 до 11. Мне даже пришлось параметры формулы разнести по отдельным строкам, чтобы не запутаться в них. Кстати я рекомендую брать этот способ на вооружение - получается очень наглядно, разбираться стало куда проще. ![]() Конструкция ![]() В некоторых ячейках (смотрите, например, С12) формула ещё сложнее из-за того, что TYPE (в данном случае DEDUC) имеет свои типы ("D1"), да ещё должен и учесть типы клиентов ("S1"), поэтому используется сложный SUBTYPE "D1S1", а формула приобретает вид: ![]() С сожалением приходится признать, что пересчёт листа SELECT занимает на моём ноутбуке (Intel Core i3-4000M 2.4GHz) около 9 секунд (спасибо СУММПРОИЗВ, которых на листе 1764 штуки !), поэтому, если вам не критично, чтобы пользователь мог выбирать произвольные наборы регионов, то я рекомендую вернуться к более шустрой формуле СУММЕСЛИМН. Тем не менее при работе с отчётом я ревностно слежу, чтобы лист SELECT лишний раз ни дай Бог не пересчитался, поэтому он пересчитывается ТОЛЬКО тогда, когда пользователь действительно меняет набор регионов/бизнесов. Остальные формулы, встречающиеся на этом листе, я надеюсь, не будут сложны для понимания, а если нет, то, пожалуйста, регистрируйтесь на сайте и пишите комментарии. Кстати, я в любом случае хотел бы увидеть ваши комментарии :) Детали визуализацииСразу оговорюсь, что я никого не пытался поразить видом отчёта в дизайнерском смысле, в этом плане он сделан на скорую руку и скорее всего несовершенен. Подобрать шрифты, размеры и цвета вы можете сами. Меня интересовал гораздо больше технологический аспект визуализации. Им и займёмся. В первую очередь хочу обратить ваше внимание, что отчёт очень тщательно структурирован, стандартизирован и формализован. Он чётко поделён на большие блоки, сегменты и отдельные типовые колонки. Опишем их:
Далее предлагаю ознакомление с функционалом начать с пользовательской формы: ![]() За отображение блоков отвечает раздел формы Block, отображение секций - Section, отображение столбцов - Columns. Все эти элементы управления типа checkbox (галочка) привязаны через именованные диапазоны к следующим ячейкам (лист SELECT, ячейка I170): ![]() В свою очередь эти именованные диапазоны участвуют на листе PnL в ячейках-индикаторах видимости столбцов. Это строки 1:4. Строка 2 отвечает за индикаторы показа блоков, строка 3 - секций, строка 4 - отдельных типов колонок. Строка 1 вычисляет логическое И по строкам 2:4 и именно на это значение макрос смотрит при принятии решения по скрытию или отображению данного столбца. Если столбец по факту скрыт, а значение индикатора 1, то макрос его сделает видимым, если столбец является видимым, а индикатор равен 0, то такой столбец макрос скроет. Всё просто. ![]() Назначение остальных элементов управления пользовательской формы предельно ясно. Можно выбирать язык отчёта, территории, типы бизнеса и режим показа типов клиентов. При изменении регионов и типов бизнеса после нажатия кнопки Config & Hide Excel будет думать около 10 секунд, предварительно выведя на экран уведомление, что надо слегка подождать. Исходный код пользовательской формы приведен в листинге и снабжен комментариями: ' Обработчик нажатия кнопки Config & Hide на форме Private Sub btnHide_Click() ' Меняем стиль курсора Application.Cursor = xlWait ' Выгружаем текущую форму, чтобы отобразить форму ожидания Unload Me ' Выводим и отрисовываем форму ожидания frmWaiting.Show vbModeless ' Если не отрисовать, то из-за высокой загрузки CPU она не успеет отрисоваться в фоне frmWaiting.Repaint ' Возвращаем автоматический пересчёт формул ' После этого формулы начнут пересчитываться Application.Calculation = xlCalculationAutomatic ' Выделяем текущий регион (равносильно нажатию Ctrl+A) [A1].CurrentRegion.Select With Selection ' Перебираем в выделении столбцы For i = 1 To .Columns.Count ' Если столбец скрыт, но должен быть виден, то отменяем скрытие If .Columns(i).Hidden And .Cells(1, i) = 1 Then .Columns(i).Hidden = False End If ' Если столбец виден, но должен быть скрыт, то скрываем If Not .Columns(i).Hidden And .Cells(1, i) = 0 Then .Columns(i).Hidden = True End If Next ' Аналогично перебираем в выделении строки For i = 1 To .Rows.Count If .Rows(i).Hidden And .Cells(i, 1) = 1 Then .Rows(i).Hidden = False End If If Not .Rows(i).Hidden And .Cells(i, 1) = 0 Then .Rows(i).Hidden = True End If Next End With ' Разрешаем Excel разморозить экран Application.ScreenUpdating = True ' Возвращаем нормальный вид курсора Application.Cursor = xlDefault ' Убираем форму ожидания Unload frmWaiting ' Позиционируем текущую ячейку [A1].Select End Sub Private Sub chbB1_Change() If Me.chbB1 Then ' Ячейки valBUS1-3 и valGEO1-5 лучше не беспокоить без особой надобности, ' так как даже, если вы запишите в них значение, которое в них уже есть, ' Excel всё равно захочет пересчитать всё, что от них зависит, поэтому ' прежде чем их изменять, я проверяю, а есть ли смысл их менять. If Range("valBUS1").Item(1) <> "B1" Then Range("valBUS1").Item(1) = "B1" Else If Range("valBUS1").Item(1) <> "FF" Then Range("valBUS1").Item(1) = "FF" End If End Sub Private Sub chbB2_Change() If Me.chbB2 Then If Range("valBUS2").Item(1) <> "B2" Then Range("valBUS2").Item(1) = "B2" Else If Range("valBUS2").Item(1) <> "FF" Then Range("valBUS2").Item(1) = "FF" End If End Sub Private Sub chbB3_Change() If Me.chbB3 Then If Range("valBUS3").Item(1) <> "B3" Then Range("valBUS3").Item(1) = "B3" Else If Range("valBUS3").Item(1) <> "FF" Then Range("valBUS3").Item(1) = "FF" End If End Sub Private Sub chbG1_Change() If Me.chbG1 Then If Range("valGEO1").Item(1) <> "G1" Then Range("valGEO1").Item(1) = "G1" Else If Range("valGEO1").Item(1) <> "FF" Then Range("valGEO1").Item(1) = "FF" End If End Sub Private Sub chbG2_Change() If Me.chbG2 Then If Range("valGEO2").Item(1) <> "G2" Then Range("valGEO2").Item(1) = "G2" Else If Range("valGEO2").Item(1) <> "FF" Then Range("valGEO2").Item(1) = "FF" End If End Sub Private Sub chbG3_Change() If Me.chbG3 Then If Range("valGEO3").Item(1) <> "G3" Then Range("valGEO3").Item(1) = "G3" Else If Range("valGEO3").Item(1) <> "FF" Then Range("valGEO3").Item(1) = "FF" End If End Sub Private Sub chbG4_Change() If Me.chbG4 Then If Range("valGEO4").Item(1) <> "G4" Then Range("valGEO4").Item(1) = "G4" Else If Range("valGEO4").Item(1) <> "FF" Then Range("valGEO4").Item(1) = "FF" End If End Sub Private Sub chbG5_Change() If Me.chbG5 Then If Range("valGEO5").Item(1) <> "G5" Then Range("valGEO5").Item(1) = "G5" Else If Range("valGEO5").Item(1) <> "FF" Then Range("valGEO5").Item(1) = "FF" End If End Sub Private Sub chbS1_Change() If Me.chbS1 Then Range("valSEG1").Item(1) = "S1" Else Range("valSEG1").Item(1) = "FF" End If End Sub Private Sub chbS2_Change() If Me.chbS2 Then Range("valSEG2").Item(1) = "S2" Else Range("valSEG2").Item(1) = "FF" End If End Sub Private Sub frColumns_Click() End Sub Private Sub opBusAll_Click() Me.chbB1.Value = True Me.chbB2.Value = True Me.chbB3.Value = True If Range("valBUS1").Item(1) <> "B1" Then Range("valBUS1").Item(1) = "B1" If Range("valBUS2").Item(1) <> "B2" Then Range("valBUS2").Item(1) = "B2" If Range("valBUS3").Item(1) <> "B3" Then Range("valBUS3").Item(1) = "B3" Me.chbB1.Enabled = False Me.chbB2.Enabled = False Me.chbB3.Enabled = False End Sub Private Sub opBusManual_Click() Me.chbB1.Value = Range("valBUS1").Item(1) = "B1" Me.chbB2.Value = Range("valBUS2").Item(1) = "B2" Me.chbB3.Value = Range("valBUS3").Item(1) = "B3" Me.chbB1.Enabled = True Me.chbB2.Enabled = True Me.chbB3.Enabled = True End Sub Private Sub opEnglish_Click() Range("selLang").Item(1) = "ENG" End Sub Private Sub opGeoAll_Click() Me.chbG1.Value = True Me.chbG2.Value = True Me.chbG3.Value = True Me.chbG4.Value = True Me.chbG5.Value = True Me.chbG1.Enabled = False Me.chbG2.Enabled = False Me.chbG3.Enabled = False Me.chbG4.Enabled = False Me.chbG5.Enabled = False If Range("valGEO1").Item(1) <> "G1" Then Range("valGEO1").Item(1) = "G1" If Range("valGEO2").Item(1) <> "G1" Then Range("valGEO2").Item(1) = "G2" If Range("valGEO3").Item(1) <> "G1" Then Range("valGEO3").Item(1) = "G3" If Range("valGEO4").Item(1) <> "G1" Then Range("valGEO4").Item(1) = "G4" If Range("valGEO5").Item(1) <> "G1" Then Range("valGEO5").Item(1) = "G5" End Sub Private Sub opGeoManual_Click() ' Не для всех будет понятна эта конструкция ' Me.chbG1.Value примет значение True, если range равен указанной константе ' и False, если range не равен Me.chbG1.Value = Range("valGEO1").Item(1) = "G1" Me.chbG2.Value = Range("valGEO2").Item(1) = "G2" Me.chbG3.Value = Range("valGEO3").Item(1) = "G3" Me.chbG4.Value = Range("valGEO4").Item(1) = "G4" Me.chbG5.Value = Range("valGEO5").Item(1) = "G5" Me.chbG1.Enabled = True Me.chbG2.Enabled = True Me.chbG3.Enabled = True Me.chbG4.Enabled = True Me.chbG5.Enabled = True End Sub Private Sub opRussian_Click() Range("selLang").Item(1) = "RUS" End Sub Private Sub opSegAll_Click() Me.chbS1.Value = False Me.chbS2.Value = False Me.chbS1.Enabled = False Me.chbS2.Enabled = False End Sub Private Sub opSegManual_Click() Me.chbS1.Value = Range("valSEG1").Item(1) = "S1" Me.chbS2.Value = Range("valSEG2").Item(1) = "S2" Me.chbS1.Enabled = True Me.chbS2.Enabled = True End Sub Private Sub UserForm_Activate() ' Переводим Excel в режим ручного пересчета Application.Calculation = xlCalculationManual ' Замораживаем экран Application.ScreenUpdating = False ' Если не все регионы выбраны, то включаем Manual переключатель If Not (Range("valGEO1").Item(1) = "G1" And _ Range("valGEO2").Item(1) = "G2" And _ Range("valGEO3").Item(1) = "G3" And _ Range("valGEO4").Item(1) = "G4" And _ Range("valGEO5").Item(1) = "G5") Then Me.opGeoManual.Value = True End If ' Если не все бизнесы выбраны, то включаем Manual переключатель If Not (Range("valBUS1").Item(1) = "B1" And _ Range("valBUS2").Item(1) = "B2" And _ Range("valBUS3").Item(1) = "B3") Then Me.opBusManual.Value = True End If If Not (Range("valSEG1").Item(1) = "FF" And _ Range("valSEG2").Item(1) = "FF") Then Me.opSegManual.Value = True End If If Range("selLang").Item(1) = "RUS" Then Me.opRussian.Value = True Else Me.opEnglish.Value = True End If ' Привязываем элементы управления к соответствующим ячейкам через свойство ControlSource Me.chbACT.ControlSource = Range("showACT").Worksheet.Name & "!" & Range("showACT").Address Me.chbAmount.ControlSource = Range("showAMT").Worksheet.Name & "!" & Range("showAMT").Address Me.chbAmtU.ControlSource = Range("showAMTU").Worksheet.Name & "!" & Range("showAMTU").Address Me.chbBP.ControlSource = Range("showBP").Worksheet.Name & "!" & Range("showBP").Address Me.chbDIFF.ControlSource = Range("showDIFF").Worksheet.Name & "!" & Range("showDIFF").Address Me.chbDIFFpercent.ControlSource = Range("showDIFFp").Worksheet.Name & "!" & Range("showDIFFp").Address Me.chbDIFFpercentU.ControlSource = Range("showDIFFpU").Worksheet.Name & "!" & Range("showDIFFpU").Address Me.chbDIFFU.ControlSource = Range("showDIFFU").Worksheet.Name & "!" & Range("showDIFFU").Address Me.chbFY.ControlSource = Range("showFY").Worksheet.Name & "!" & Range("showFY").Address Me.chbMTD.ControlSource = Range("showMTD").Worksheet.Name & "!" & Range("showMTD").Address Me.chbPY.ControlSource = Range("showPY").Worksheet.Name & "!" & Range("showPY").Address Me.chbRE.ControlSource = Range("showRE").Worksheet.Name & "!" & Range("showRE").Address Me.chbYTD.ControlSource = Range("showYTD").Worksheet.Name & "!" & Range("showYTD").Address Me.chbYTG.ControlSource = Range("showYTG").Worksheet.Name & "!" & Range("showYTG").Address End Sub Выводы
Читайте также: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Просмотров: 15422 | | |
Всего комментариев: 0 | |