Страницы: 1 2 След.
RSS
Макросом вместо формул: сделать выборку по ID на максимальную для него дату.
 
Знатоки VBA обращаюсь к Вам за помощью! С листов Работа, Счётчик и Корректор данные по ID заносятся в лист База на наиболее позднюю дату, чтобы отслеживать последние данные. При протягивании формул на 500 строк объём файла сильно увеличивается и это без данных. Может подскажите как можно сделать выборку по ID на максимальную для него дату. Заранее благодарен
 
Пока такой алгоритм вижу - т.к. последняя дата всегда последняя по порядку (ключевое), то можно завести 3 словаря, в каждый (из массива, полученного из диапазона листа) взять номера, каждому номеру в Item позицию в массиве.  
Последняя запомненная позиция будет на последнюю дату.  
Если так не годится - можно в процессе анализировать дату и запоминать максимальную, но это будет воровать скорость.  
Затем по каждому ID из базы 3 раза из влекаем из словаря позицию, из массива данные.  
Макрос будет где-то на 20/30 строк, работать должен быстро.  
Думаю можно повесить на кнопку, хотя можно запускать и по событию активации базы - всё стриаем, заполняем новыми данными.
 
Спасибо за мысль. Но вся проблема как раз в том что дата заполняется по появлению значения в столбце С и не факт что для какого то ID она может быть недавно - может и всего 1 раз быть при начальном заполнении. Т.е. данные должны браться именно последние для конкретного ID, для этого и была введена в автомате дата занесения последних изменений.
 
Т.е.- последняя дата всегда последняя по порядку (ключевое)- получается только для ID внесенного последним.
 
У каждого ID много дат - и последняя по списку является последней по дате. Т.е. Вы ведь не будете ставить сегодняшнюю дату в середину списка?  
В итоге в словаре будут все ID, и у каждого одна дата - последняя из обнаруженных.  
Но можно даты при занесении анализировать - я выше уже сказал.
 
Да теперь я Вас понял и это верно. Может реализацию - "можно запускать и по событию активации базы - всё стриаем, заполняем новыми данными" - на примере подскажите? И еще раз спасибо за участие и помощь.
 
Я сейчас домой - часа через 2-3 может гляну.
 
С нетерпением буду ждать!
 
Попробуйте.  
Немного экран подрагивает - но это не устранить.  
Интересно, как будет на большом количестве данных работать?  
Если будут вопросы по коду - спрашивайте.
 
На счёт количества строк - код около 70...  
Но если пренебречь читаемостью - то 23 :)  
 
 
Private Sub Worksheet_Activate()  
   Dim d1 As Object, d2 As Object, d3 As Object, a1(), a2(), a3(), b(), i&, il&, t&  
   Application.ScreenUpdating = False  
   Set d1 = CreateObject("Scripting.Dictionary"): d1.CompareMode = vbTextCompare  
   Set d2 = CreateObject("Scripting.Dictionary"): d2.CompareMode = vbTextCompare  
   Set d3 = CreateObject("Scripting.Dictionary"): d3.CompareMode = vbTextCompare  
   a1 = Sheets("Работа").[b3].CurrentRegion.Value: a2 = Sheets("Счётчик").[b3].CurrentRegion.Value: a3 = Sheets("Корректор").[b3].CurrentRegion.Value
   With Sheets("База"): b = Range(.[H3], .Range("B" & .Rows.Count).End(xlUp)).Value: End With
   todic a1, d1: todic a2, d2: todic a3, d3: ReDim c(1 To UBound(b), 1 To 11)  
   For i = 1 To UBound(b)  
       If d1.exists(b(i, 2)) Then t = d1.Item(b(i, 2)): c(i, 7) = a1(t, 3): c(i, 8) = a1(t, 4): c(i, 9) = a1(t, 5): c(i, 10) = a1(t, 6): c(i, 11) = a1(t, 7)  
       If d2.exists(b(i, 2)) Then t = d2.Item(b(i, 2)): c(i, 1) = a2(t, 3): c(i, 2) = a2(t, 5): c(i, 3) = a2(t, 6)  
       If d3.exists(b(i, 2)) Then t = d3.Item(b(i, 2)): c(i, 4) = a3(t, 3): c(i, 5) = a3(t, 5): c(i, 6) = a3(t, 6)  
   Next  
   With Sheets("База")  
       il = .UsedRange.Row + UsedRange.Rows.Count - 1: .Range("I3:S" & il).Clear  
       With .[I3].Resize(i - 1, 11): .Columns(11).NumberFormat = "@": .Value = c: .Borders.Weight = xlThin: End With
   End With  
   Application.ScreenUpdating = True  
End Sub  
Sub todic(a, d): Dim i&  
   With d: For i = 3 To UBound(a): .Item(a(i, 2)) = i: Next: End With  
End Sub
 
Огромное спасибо! Пока разбираюсь, отпишу если можно завтра у нас разница 2 часа и  у Вас ночь уже будет пока я к чему то дойду.
 
У меня тоже 2 часа.    
У Вас в какую сторону? :)
 
Я про Вас и говорю. У меня 22.30. На первый взгляд все отлично и первый и второй вариант. Мерцания не заметил (может просто торопился ответить). Но для более глубокого изучения нужно время (мне очень далеко до Вас). И это еще не конечный вариант в моих планах. Надеюсь не откажите в дальнейшей помощи (конечно по-максимуму буду стараться сам). Еще раз огромное спасибо!
 
{quote}{login=Hugo}{date=01.02.2012 07:29}{thema=}{post}У каждого ID много дат - и последняя по списку является последней по дате. Т.е. Вы ведь не будете ставить сегодняшнюю дату в середину списка?  
В итоге в словаре будут все ID, и у каждого одна дата - последняя из обнаруженных.  
Но можно даты при занесении анализировать - я выше уже сказал.{/post}{/quote}  
 
Если я понял из Вашего кода (или не понял) анализируется последняя запись для данного ID, так может вообще отказаться от макроса ввода даты по изменению ячейки. Я это делал для того что бы как то формулой распознавать какую запись  для данного ID выбирать?
 
Ну в общем да - дата тут роли не играет, я беру просто последнюю запись.  
Но если даты убрать вообще - тогда нужно чуть диапазоны в коде скорректировать.
 
На счёт двух вариантов - это полностью одинаковый код, просто второй чуть поплотнее записан.  
А время у нас одинаковое. Может мы в одной стране живём?
 
Да, наверное в Украине? А как без дат будет для поста 01.02.2012, 23:37, если еще не спите?  
Ненужные даты в столбцах В Работа, Счётчик, Корректор
 
Нет, не в Украине.  
Если не будет столбца дат - в данном случае вроде всё работает, Вам повезло :)  
Я думал - придётся все массивы корректировать, все цифры двигать...
 
Если я правильно понял то сроку:  
With Sheets("База"): b = Range(.[H3], .Range("B" & .Rows.Count).End(xlUp)).Value: End With
Надо:  
With Sheets("База"): b = Range(.[H3], .Range("C" & .Rows.Count).End(xlUp)).Value: End With
Или в первом варианте:b = Range(.[H3], .Range("С" & .Rows.Count).End(xlUp)).Value
Так?  
 
А массивы даты вроде не затрагивают, только данные.  
Отдаленно принцип понятен (но как вариант разбираться а не самому написать такое) но как выбирается последняя запись так и не понял. В формулах специально ввел даты , что бы для каждого ID можно было выбрать последнюю запись!!! Огромное спасибо!
 
Да, с b я что-то перестраховался - нам ведь только ID нужны.  
Можно так:  
 
   With Sheets("База"): b = Range(.[C3], .Range("C" & .Rows.Count).End(xlUp)).Value: End With
 
И тогда всюду b(i, 2) меняем на b(i, 1).  
 
А последняя запись выбирается так - в словарь заносятся все записи. и к каждой её позиция в массиве.  
Но т.к. записи пишутся только уникальные - то каждая следующая затирает предыдущую, в итоге остаётся последняя просмотренная с её позицией.  
Если перебирать массив снизу вверх - запомним первую.  
Но если будут даты - можно их анализировать, и тогда не важно, в каком порядке и где эти даты - запомним что захотим.  
 
Может быть можно выиграть пару миллисекунд, если перебирать диапазоны снизу вверх. и запоминать только первую попавшуюся пару ID - номер.  
Так не нужно будет переписывать номера в словаре.  
Но всё равно нужно просмотреть весь диапазон, и каждый ID проверить на наличие в словаре - код будет чуть сложнее, добавится одно условие. Но может чуть быстрее сработает - можете в качестве тренировки сравнить позже на большом количестве данных, что быстрее.  
Для этого нужно изменить Sub todic. Если не справитесь - поможем :)
 
Да для осмысления нужна свежая голова. Спасибо. Завтра все еще раз проанализирую и попробую.
 
{quote}{login=Hugo}{date=02.02.2012 01:52}{thema=}{post}  
 
А последняя запись выбирается так - в словарь заносятся все записи. и к каждой её позиция в массиве.  
Но т.к. записи пишутся только уникальные - то каждая следующая затирает предыдущую, в итоге остаётся последняя просмотренная с её позицией.  
Если перебирать массив снизу вверх - запомним первую.  
Но если будут даты - можно их анализировать, и тогда не важно, в каком порядке и где эти даты - запомним что захотим.  
 
Может быть можно выиграть пару миллисекунд, если перебирать диапазоны снизу вверх. и запоминать только первую попавшуюся пару ID - номер.  
Так не нужно будет переписывать номера в словаре.  
Но всё равно нужно просмотреть весь диапазон, и каждый ID проверить на наличие в словаре - код будет чуть сложнее, добавится одно условие. Но может чуть быстрее сработает - можете в качестве тренировки сравнить позже на большом количестве данных, что быстрее.  
Для этого нужно изменить Sub todic.    
 
Понял где но как не понял. А это(перебор снизу вверх)действительно интересно, тем более что за пару лет в листе Работа будут нужны только нижние данные а ведь  по нему и будут основные изменения. И даты для анализа можно брать из столбца Р База(или Е Работа).  
Вот уж воистину - предела совершенствованию-нет!
 
"Понял где но как не понял." Вот так:  
 
Sub todic(a, d)  
   Dim i&  
   With d  
       For i = UBound(a) To 3 Step -1  
       If Not .exists(a(i, 2)) Then .Item(a(i, 2)) = i  
       Next  
   End With  
End Sub  
 
 
Т.е перебираем снизу вверх и проверяем, есть ли уже в словаре.  
Если нет - заносим в словарь ID и его индекс в массиве.  
Но всё равно нужно перебрать всё до конца (начала :)).  
Но - если точно знаете, что первых пару тысяч смотреть не стоит - замените 3 на 2000. Но думаю на скорость это сильно не повлияет.  
А если ненужных строк много больше - то тогда уже в сами массивы данных нет смысла включать этот мусор. Тогда стоит переделать формирование массивов a1/2/3 - а тут перебирать их целиком.  
 
Есть ещё 2 варианта алгоритма:  
1. Собирать все позиции по всем ID в ОДИН словарь - каждому значению в  Item поместить массив из трёх значений, куда и собирать эти позиции.  
Этим исключим в конце поиск в трёх словарях, но зато при занесении в словарь нужен поиск нет_ли_уже_в_словаре, по результату извлечение/создание массива для позиций.  
Далее как в существующем коде - просмотр базы, находим позиции данных в трёх массивах, копирование данных.  
2. Тоже один словарь, но сразу создаём общий массив, куда соберём упорядоченные данные из трёх листов (в высоту как сумма всех исходных, на случай, что во всех массивах разные ID, шириной как база).  
Далее при просмотре исходных массивов сразу копируем данные в этот созданный общий массив, а индексы позиций данных в этом массиве по ID храним в словаре.  
Далее создаём пустой массив уже для выгрузки в базу.  
При анализе базы перекладываем отобранные данные из общего массива в этот массив, его выгружаем.  
Эти оба варианта более динамичны - может быть любое количество обрабатываемых листов, даже не известное заранее (с выгрузкой только чуть ещё алгоритм продумать).  
Но по скорости думаю будут помедленнее - больше действий с данными.  
 
 
Ещё замечание - тут во всех вариантах могут быть "ошибки", если ID будут заведены с лишними пробелами в начале/конце.  
Это легко устранить, добавив всюду Trim(), где идёт проверка значения ID. Но код станет чуть медленнее.  
Если данные не вводятся вручную - то думаю эта предосторожность лишняя.  
 
Ещё - можно этот код поместить в отдельный модуль, и запускать его при активации листа База и при изменении в Базе в столбце C.  
Тогда данные будут обновляться сразу при занесении/удалении ID в базе, а не только при перемещении по листам.  
Но срабатывание при активации нужно оставить - на случай изменения в источниках без изменения в базе.
 
Hugo Вы знаете я восхищен вашей способностью мыслить я едва осмыслил так сказать первые шаги а Вы уже сгенерировали совсем новое направление и оно еще интереснее предыдущего. Огромное спасибо за помощь. Заинтересовал 2 вариант и - Ещё - можно этот код поместить в отдельный модуль, и запускать его при активации листа База и при изменении в Базе в столбце C -.  
Да интересный у нас диалог. Вы предлагаете а я как бы оцениваю. Но Ваши идеи действительно настолько интересны в отношении алгоритмов, что я снимаю "шляпу". Мне казалось, что было формулами единственно правильное решение а вот нет. Приходится разбираться с книгами как это работает и пока только восхищаться - увы.
 
Подходы могут быть разные, вот для примера организация связи с данными таблиц листов: Работа, Счётчик, Корректор по заданным условиям на первом листе книги (если будете пробовать, то добавьте его). К сожалению ваши таблицы на этих листах организованы не совсем правильно, чтобы обеспечить полную связь для обновления, но можно ведь и доработать  
 
Private Function WorkReference(ByVal OnSheet As Excel.Worksheet, ByVal FirstColId As Long, ByVal LastColId As Long) As String  
   Dim LRow As Long  
   LRow = OnSheet.Cells(OnSheet.Rows.Count, FirstColId).End(xlUp).Row  
   WorkReference = " [" & OnSheet.Name & "$" & OnSheet.Range(OnSheet.Cells(3, FirstColId), OnSheet.Cells(LRow, LastColId)).Address(False, False, xlA1) & "] "
End Function  
 
Public Sub test()  
   Dim sWork As String, sCounter As String, sCorrector As String  
   Dim sSql As String, sConn As String, QTable As Excel.QueryTable  
   Dim pSheet As Excel.Worksheet, pList As Excel.ListObject  
     
   sWork = WorkReference(ThisWorkbook.Worksheets("Работа"), 3, 8)  
   sCounter = WorkReference(ThisWorkbook.Worksheets("Счётчик"), 3, 7)  
   sCorrector = WorkReference(ThisWorkbook.Worksheets("Корректор"), 3, 7)  
     
   sSql = "Select r1.*, t3.*, p3.* From (" & sWork & "As r1 Inner Join "  
   sSql = sSql & "(Select t1.* From" & sCounter & "As t1 Where t1.ID In (Select t2.ID From" & sCounter  
   sSql = sSql & "As t2 Group By t2.ID Having Max(t2.[Поверка])=t1.[Поверка])) As t3 On r1.ID=t3.ID) "
   sSql = sSql & "Inner Join (Select p1.* From" & sCorrector & "As p1 Where p1.ID In (Select p2.ID From" & sCorrector  
   sSql = sSql & "As p2 Group By p2.ID Having Max(p2.[Поверка])=p1.[Поверка])) As p3 On r1.ID=p3.ID"
     
   sConn = "OLEDB;Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & ThisWorkbook.FullName  
   sConn = sConn & ";Extended Properties=""Excel 8.0;HDR=YES"";"  
     
   Set pSheet = ThisWorkbook.Worksheets(1)  
   Set QTable = pSheet.QueryTables.Add(sConn, pSheet.Range("A1"), sSql)  
   QTable.Refresh  
     
   Set pList = pSheet.ListObjects.Add(xlSrcRange, QTable.ResultRange, , xlYes)  
End Sub  
По идее, если перекомпоновать исходные таблицы листов в вид, удобный для работы с базами данных, дописать в SQL связь с листом База, определиться с требуемыми полями и их названиями, возвращаемыми SQL, то получим обновляемую таблицу.
 
Я извиняюсь за свое невежество а можно подробнее про - если перекомпоновать исходные таблицы листов в вид, удобный для работы с базами данных, дописать в SQL связь с листом База, определиться с требуемыми полями и их названиями, возвращаемыми SQL, то получим обновляемую таблицу -. Очень хочется глянуть как это. И насовсем понял как добавить условие: -заданным условиям на первом листе книги (если будете пробовать, то добавьте его)-. Спасибо за помощь и желание помочь.
 
Да всё, в принципе, просто.  
Формат таблиц на листах: Первая строка заголовки, дальше данные одного типа в стобце (или пустые ячейки). Это позоволить в запросе избавиться от ограничения ссылки на диапазон данных листа [База$A3:G56], то есть - ссылка будет выглядеть как [База$], что позволяет дописывать данные в таблицу и при обновлении получать результат с учётом добавлений.
Требования по листу. В книге первый лист должен быть пустым, иначе макрос затрёт данные на первом листе.  
Подводя итоги. Необходимо будет сделать 4 листа таблиц исходных данных: База, Работа, Счётчик, Корректор. Лист-таблица База нужна для выборки данных по полям: Объект и Район.  
В понедельник постараюсь выложить пример
 
Может быть действительно лучше сделать на SQL?  
Правда вот такой запрос мне кажется совершенно неуправляемым :)  
 
Select r1.*, t3.*, p3.* From ( [Работа$C3:H7] As r1 Inner Join (Select t1.* From [Счётчик$C3:G12] As t1 Where t1.ID In (Select t2.ID From [Счётчик$C3:G12] As t2 Group By t2.ID Having Max(t2.[Поверка])=t1.[Поверка])) As t3 On r1.ID=t3.ID) Inner Join (Select p1.* From [Корректор$C3:G9] As p1 Where p1.ID In (Select p2.ID From [Корректор$C3:G9] As p2 Group By p2.ID Having Max(p2.[Поверка])=p1.[Поверка])) As p3 On r1.ID=p3.ID
 
Но тут нет отбора значений, заведённых на лист "База" - просто выводятсе все последние записи по всем существующим.  
И к тому же по всем, которые есть на всех листах. Т.е. если на одном удалить один ID - его в сводном уже не будет.  
Вероятно, это можно изменить - но я такие сложности не освоил,я только простые запросы писал...  
 
 
По своему коду.  
Мне самому второй вариант на больших объёмах не нравится.  
Вот смотрите - сейчас есть три листа допустим по 5 записей на каждом, и вполне может быть, что на всех трёх заведены разные ID. Следовательно, нужно объявить массив на общее количество строк, чтоб точно всё уместилось, ну и в ширину тоже как сумма ширин этих листов (исходя из задачи).  
Получается массив примерно 15х11.  
Ну а если 3 листа по 50000 ID? 150000х11. А если листов не 3?  
Вряд ли конечно все ID будут разными, но где тот минимум строк, который нужен?  
Другое дело, если заранее известно число уникальных ID. Ну или сперва их можно отдельным проходом по всем данным подсчитать (что тоже дополнительный перебор исходных массивов и дополнительная работа со словарём).  
Тогда можно объявить массив конкретного размера и его заполнить, потом уже из него отобрать только то, что нужно для выгрузки в Базу.  
Ну или сразу выгрузить весь - будет как на SQL, но имхо более подконтрольно :)  
 
На словаре есть ещё вариант 3.  
Сразу проходом по исходным данным собираем ID в один словарь, и каждому в Item помещаем массив на 11+3 полей, который заполняем из исходных данных.  
Эти +3 поля думаю нужны как индикатор, что с конкретного листа данные уже получены.  
В конце в базу циклом по словарю выгружаем каждому ID базы данные из массива по этому ID.  
Тут минус в этом цикле - если грузить сразу на лист, то на болшьшом объёме процесс будет долгим.  
Можно сперва создать массив, циклом переложить в него, и уже его выгрузить на лист - так будет побыстрее, но тоже много суеты...  
 
В общем, выбирайте - уже аж 5 вариантов, считая Ваши формулы (я бы такие вероятно написать не смог...)  
И будет ещё на SQL :)  
 
"запускать его при активации листа База и при изменении в Базе в столбце C" - это ведь просто.  
Весь код перенесите в стандартный модуль, имя любое.  
Далее  
 
 
Private Sub Worksheet_Change(ByVal Target As Range)  
          If Not Intersect(Target, Range("C:C")) Is Nothing Then  
          Application.EnableEvents = False  
          имя_макроса_из_модуля  
          Application.EnableEvents = True  
         End If  
End Sub  
 
Private Sub Worksheet_Activate()  
имя_макроса_из_модуля˙  
End Sub  
 
Только тогда в коде тут одну точку нужно добавить, у меня выпала (но в коде листа работало и без неё):  
 
    il = .UsedRange.Row + .UsedRange.Rows.Count - 1
 
Уважаемый Hugo я даже не думал что настолько увлекательно разбирать варианты. Думал сделаю чтоб работало и ладно но вы заразили в хорошем смысле. Первоначальная мысль была такой есть определенное количество приборов в которых установлены счетчики и корректоры. Для опознавания приборов был введен уникальный номер. Но счетчик или корректор в приборе может выйти из строя и его просто в данном приборе заменяют. Так вот чтобы знать что сейчас в данном приборе такой-то счетчик и такой-то корректор такие-то работы по обслуживанию(ремонту) проведены и такие-то планируются и было все задумано. Исходя из этого столбцы С-Н заполнены изначально а остальные также но из других листов. Количество ID меняется с расширением или увеличением количества обслуживаемых объектов, счетчики, корректоры- при замене в данном  ID или изменении даты проверки(раз в 3 года), а вот работы помесячно или в случае необходимости.  
Поэтому есть такая мысль для  
- На словаре есть ещё вариант 3.  
Сразу проходом по исходным данным собираем ID в один словарь -  
И при следующих входах только проверять не изменилось ли их количество и если нет то выгрузить данные с листов Счетчик, Корректор и Работа по этим ID.  
- Тогда можно объявить массив конкретного размера и его заполнить, потом уже из него отобрать только то, что нужно для выгрузки в Базу-  
При изменении количества ID например 450-453=3, 3 последних добавить в массив ID и обновить данные по ID с листов Счетчик, Корректор и Работа.  
Вот как то так но опять как это выглядит конкретно-УВЫ.  
А с запуском при активации листа База и переносе в стандартный модуль в принципе   вчера додумал сам(Ваше воспитание). Еще раз благодарю за науку и помощь.
 
И еще ID не может быть на листах разный т.к. берется из динамического диапазона созданного из данных ID столбца С листа База(на листе Работа он подключен к выпадающему списку). Но с разбором вариантов "украшение" приостановлено, хочется лучшего! А вообще планируется данные по листам заносить через форму с вкладками соответствующих листов.  
И еще одно Вы не против если диалог продолжим через неделю, в субботу еду в командировку а с возможностью доступа к интернету есть сомнения.
Страницы: 1 2 След.
Читают тему
Наверх