Ru-Board.club
← Вернуться в раздел «Прикладное программирование»

» Excel VBA (часть 3)

Автор: Quieteroks
Дата сообщения: 19.09.2011 09:25
SAS888
Сейчас выложу. Только данные "затру" частично. Все же секретность...
http://rghost.ru/22170771
http://rghost.ru/download/22170771/216775fb18b1ca264b87db7d519714f46de25264/%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80.xlsx

Искать дубликаты необходимо по Фамилии, Имени, Отчеству и дате рождения. Т.е. 5,6,7,8 поле.
Автор: asbo
Дата сообщения: 19.09.2011 09:56

Цитата:
Цикл не хочет проверять все строки. Выполняется около 10000 раз.

После удаления нумерация строк меняется, а в программе - сквозная. Надо идти с конца.

А еще лучше - в два этапа. Вначале ставить метки на удаление, а потом уже по этим меткам удалять-переносить. И странно - подразумевается, что будет не больше одного повтора. Вообще-то, эта задача под Access - не надо разбивать на листы и запросом можно обойтись. Да и странно - 30000. В 2003 было ограничение 65, а потом уже и весь лимон.
Автор: Quieteroks
Дата сообщения: 19.09.2011 10:12

Цитата:
После удаления нумерация строк меняется, а в программе - сквозная. Надо идти с конца.


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


Цитата:
И странно - подразумевается, что будет не больше одного повтора.


Почему не должно быть более одного повтора? Может Вы что то просмотрели в коде?


Цитата:
В 2003 было ограничение 65, а потом уже и весь лимон.


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


Цитата:
Вообще-то, эта задача под Access - не надо разбивать на листы и запросом можно обойтись.


Может быть и проще Access использовать... Но итог все равно нужно будет переносить в Excel. Поэтому решили не заморачиваться.
Автор: aidomars
Дата сообщения: 19.09.2011 10:30
Quieteroks
А не пробовали посмотреть в точке останова цикла значения переменных? На какой строке останавливается и т.д.?
Автор: SAS888
Дата сообщения: 19.09.2011 10:37
Для решения данной задачи, работать с ячейками (строками) рабочего листа непосредственно - это очень медленно. С использованием массивов, все гораздо быстрее. Например, так:

Код: Sub Main()
Dim i As Long, j As Long, bi As Long, ci As Long, temp As String
Dim x As New Collection, a(), b(), c()
Application.ScreenUpdating = False: a = ActiveSheet.UsedRange.Value
bi = 0: ci = 0: ReDim b(1 To UBound(a, 1), 1 To UBound(a, 2)): c = b
For i = 1 To UBound(a, 1)
temp = a(i, 5) & a(i, 6) & a(i, 7) & a(i, 8)
On Error Resume Next: x.Add temp, temp
If Err = 0 Then
bi = bi + 1
For j = 1 To UBound(a, 2): b(bi, j) = a(i, j): Next
Else
ci = ci + 1
For j = 1 To UBound(a, 2): c(ci, j) = a(i, j): Next
On Error GoTo 0
End If
Next
With ThisWorkbook.ActiveSheet
.UsedRange.Value = c: Workbooks.Add xlWBATWorksheet
.Range(.[A1], .Cells(1, UBound(a, 2))).Copy
[A1].PasteSpecial Paste:=xlPasteColumnWidths
End With
ActiveSheet.Name = "Уникальные": [A1].Resize(UBound(a, 1), UBound(a, 2)).Value = b
[A1].Select: Set x = Nothing: Application.CutCopyMode = False: Application.ScreenUpdating = True
End Sub
Автор: domo22
Дата сообщения: 19.09.2011 10:42
Подскажите пжлста, как в определении, что приведено ниже, сослаться не на столбец "А", а на текущий, т.е. столбец, где стоит курсор.

Dim Столбец As Range: Set Столбец = Range([A1], Range("A" & Rows.Count).End(xlUp))
Автор: asbo
Дата сообщения: 19.09.2011 10:45

Цитата:
По логике, удаляя строку, предыдущая должна занять ее место. Мы ее не скрываем ведь. Поэтому и не увеличиваем показатель номера строки для проверки.

Да. Я просмотрел, что i имеет отношение к другому листу.


Цитата:
Цитата:И странно - подразумевается, что будет не больше одного повтора.
Почему не должно быть более одного повтора? Может Вы что то просмотрели в коде?

подразумевается != не должно быть
"Цикл обрабатывает две строки", " 'Проверка двух строк", ну и, в конце концов,
Rows(j+1).Delete
Rows(j).Delete

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


Цитата:
Может быть и проще Access использовать...

Не проще, а правильнее. Видно же на листе, как Ексель исковеркал строковые данные, представленные, как числа и даты...

Автор: aidomars
Дата сообщения: 19.09.2011 10:46
domo22
Selection.Column не подойдет?
Автор: asbo
Дата сообщения: 19.09.2011 10:50
domo22
Set Столбец = Selection.EntireRow
Автор: Quieteroks
Дата сообщения: 19.09.2011 10:51

Цитата:
А не пробовали посмотреть в точке останова цикла значения переменных? На какой строке останавливается и т.д.?


Добавлял дополнительную переменную для просмотра колличества интераций. Все листы обрабатывались немного больше 10000 раз. (10375, 10297, 10286)

SAS888
Весьма странный результат выполнения скрипта.
Он осатвил одну строчку из двух. Нужны обе.
Жаль без комментариев. Буду разбираться.
Можно Вас попросить пару комментариев к коду?

Добавлено:

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


Согласен, если задача стоит более обширная, нежели у нас. В таблице не должно быть три записи подряд. Поэтому и проверяем только две. Одна строка из нашей базы, вторая из РОФОМС.

Добавлено:

Цитата:
P.S. Подразумевается, что количество используемых столбцов не менее 8. Иначе Ваше сравнение не будет иметь смысла.


Что Вы имеете ввиду под фразой не менее 8? Структура таблицы в примере была. Проверку строк необходимо только по четырем столбцам, обусловленно тем, что остальные значения могут значительно отличяться друг от друга. Скрипт Ваш хорошо справился, хотя немного не тот результат, но думаю это можно подправить.
Автор: SAS888
Дата сообщения: 19.09.2011 11:00
Quieteroks

Цитата:
...Он осатвил одну строчку из двух. Нужны обе.

Возможно, что я Вас не совсем правильно понял. Укажите конкретно: что, после выполнения макроса, должно остаться в исходном файле, и что должен содержать сформированный файл. Давайте сначала получим требуемый результат, потом я напишу подробные комментарии.

Добавлено:

Цитата:
Что Вы имеете ввиду под фразой не менее 8?

Если исходная таблица будет заполнена менее чем на 8 столбцов, то макрос выдаст ошибку. Варианта два: либо проверять, либо соглашаться с тем, что столбцов заведомо больше (я имею ввиду заполненных). Так, или иначе, я исходил из приведенного Вами примера.
Автор: Quieteroks
Дата сообщения: 19.09.2011 11:15
SAS888

Возможно.
- Макрос обрабатывает таблицу в 1000000 или разбитую на 30000 строк по листам. Есть ли ограничения в вашем макросе?
- В таблице большинство строк уникальные. Необходимо выделить строки имеющие дубликаты и перенести в создаваемую книгу. Обе строки, для дальнейшей проверки данных вручную. Причем желательно сохранить форматирование таблицы. Желтые строки это наши даные, остальные РОФОМС.


Цитата:
Если исходная таблица будет заполнена менее чем на 8 столбцов, то макрос выдаст ошибку.


Данной ошибки возникнуть не должно, таблицу вы видели, столбцов около 71. Единственное 9 столбец может иногда пустовать.
Автор: SAS888
Дата сообщения: 19.09.2011 11:28
Quieteroks

Цитата:
Есть ли ограничения в вашем макросе?
Нет.

Цитата:
Необходимо выделить строки имеющие дубликаты и перенести в создаваемую книгу. Обе строки, для дальнейшей проверки данных вручную. Причем желательно сохранить форматирование таблицы.
А вот здесь, по-подробнее:
1. Если мы обнаружили дубликаты (по контролируемым столбцам), то их нужно переносить в другую книгу не зависимо от того, совпадают ли данные в других столбцах? Так?
2. После того, как мы перенесли эти данные в другую книгу, удалять ли их из исходной? Обе (все) совпадающие строки удалять?
3. Что значит "выделить"? Если выделять цветом, то, во-первых, как их потом переносить? Во-вторых, это уже работа со свойствами ячеек рабочего листа, что есть не рационально и очень медленно.

Цитата:
...желательно сохранить форматирование таблицы
Это сделаем.

Цитата:
Желтые строки это наши даные, остальные РОФОМС.
Правильно ли я понимаю, что один файл (новый или исходный?) должен содержать только эти две строки, а другой файл - все, кроме этих?

Цитата:
Единственное 9 столбец может иногда пустовать.
Это не важно. Важно, чтобы данные начинались с 1-го столбца и последний заполненный столбец (любой строки) должен быть как минимум 8-ым.
Автор: domo22
Дата сообщения: 19.09.2011 11:30
aidomars
Dim Столбец As Range: Set Столбец = Selection.Column
выдает ошибку "object required"

asbo
Dim Столбец As Range: Set Столбец = Selection.EntireRow
ошибки не выдает, но просто ничего не делает.

Может я что-то или не туда ввожу...

А, понятно, надо
Dim Столбец As Range: Set Столбец = Selection.EntireColumn
вот так работает. Спасибо asbo.
Автор: Quieteroks
Дата сообщения: 19.09.2011 11:40
SAS888

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


Цитата:
Правильно ли я понимаю, что один файл (новый или исходный?) должен содержать только эти две строки, а другой файл - все, кроме этих?


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


Цитата:
Это не важно. Важно, чтобы данные начинались с 1-го столбца и последний заполненный столбец (любой строки) должен быть как минимум 8-ым.


Да. Начинается с первой и заканчивается на 71 столбце. Примерно, в смысле не помню номер последнего столбца.
Автор: asbo
Дата сообщения: 19.09.2011 11:49
domo22

Цитата:
asbo
Dim Столбец As Range: Set Столбец = Selection.EntireRow
ошибки не выдает, но просто ничего не делает.
Может я что-то или не туда ввожу...
А, понятно, надо
Dim Столбец As Range: Set Столбец = Selection.EntireColumn
вот так работает. Спасибо asbo.

Извиняюсь... Мне тут со всех сторон голову строками заморочили. Конечно же Column :)
Автор: SAS888
Дата сообщения: 19.09.2011 12:46
Quieteroks
Посмотрите здесь. Если все так, как Вы хотели - напишу комментарии.
P.S. Проверьте на большом файле.
Автор: Quieteroks
Дата сообщения: 19.09.2011 13:14
SAS888
Ошибку
Run-time error '7':
Out of memory
Выкинул. Дебагер показывает строку:
: a = ActiveSheet.UsedRange.Value

Тестирование на 500 000 строках не прошел.
Но на 30 000 строках все сработало на отлично.
Правда не сохранилось форматирование.
Результат вполне устраивает.
Автор: SAS888
Дата сообщения: 19.09.2011 16:26
Quieteroks

Цитата:
Тестирование на 500 000 строках не прошел.
Но на 30 000 строках все сработало на отлично.
Результат вполне устраивает.
Именно поэтому я и советовал протестировать на большом файле.

Цитата:
Правда не сохранилось форматирование.
Что конкретно? Формат ячеек? Шрифт? Или что еще?
Автор: AndVGri
Дата сообщения: 20.09.2011 03:32
Quieteroks
По мотивам SAS888
Хотя тех изменений... Данные столбцов критериев отбираются каждый в свой массив (у Excel видимо так и осталось с версий 2003 ограничение на размер данных, которые можно получить в массив)
[more]
Option Explicit

Private Keys As Collection

Private Function FirstEntry(ByVal Key As String, ByVal Index As Long) As Long
On Error GoTo errHandle
Keys.Add Index, Key
FirstEntry = -1
Exit Function
errHandle:
FirstEntry = Keys.Item(Key)
End Function

Public Sub test()
Dim LastCol As Long, i As Long, RowCount As Long
Dim heads() As String, id As Long
Dim key5, key6, key7, key8
Dim Status() As Long, wksSheet As Worksheet
Set Keys = New Collection
Set wksSheet = ActiveSheet
LastCol = wksSheet.UsedRange.Columns.Count + 1
Rows(1).Insert Shift:=xlDown
ReDim heads(1 To 1, 1 To LastCol)
For i = 1 To LastCol
heads(1, i) = "Cols" & CStr(i)
Next i
wksSheet.Range(wksSheet.Cells(1, 1), wksSheet.Cells(1, LastCol)).Value = heads
RowCount = wksSheet.UsedRange.Rows.Count
key5 = wksSheet.Range(wksSheet.Cells(2, 5), wksSheet.Cells(RowCount, 5)).Value
key6 = wksSheet.Range(wksSheet.Cells(2, 6), wksSheet.Cells(RowCount, 6)).Value
key7 = wksSheet.Range(wksSheet.Cells(2, 7), wksSheet.Cells(RowCount, 7)).Value
key8 = wksSheet.Range(wksSheet.Cells(2, 8), wksSheet.Cells(RowCount, 8)).Value
RowCount = RowCount
ReDim Status(1 To RowCount - 1, 1 To 1)
For i = 1 To RowCount - 1
id = FirstEntry(CStr(key5(i, 1)) & CStr(key6(i, 1)) & CStr(key7(i, 1)) & CStr(key8(i, 1)), i)
If id > 0 Then
Status(id, 1) = 1: Status(i, 1) = 1
Else
Status(i, 1) = 0
End If
Next i
If Keys.Count < (RowCount - 1) Then 'если есть хотя бы один дубль
wksSheet.Range(wksSheet.Cells(2, LastCol), wksSheet.Cells(RowCount, LastCol)).Value = Status
wksSheet.UsedRange.AutoFilter LastCol, "1"
wksSheet.Range(wksSheet.Cells(2, 1), wksSheet.Cells(RowCount, LastCol - 1)).Copy

ActiveWorkbook.Worksheets.Add
ActiveSheet.PasteSpecial XlPasteType.xlPasteColumnWidths
ActiveSheet.Paste

wksSheet.Range(wksSheet.Cells(2, 1), wksSheet.Cells(RowCount, LastCol - 1)).EntireRow.Delete Shift:=XlDeleteShiftDirection.xlShiftUp
wksSheet.UsedRange.AutoFilter
wksSheet.Columns(LastCol).Delete
wksSheet.Rows(1).Delete Shift:=XlDeleteShiftDirection.xlShiftUp
End If
End Sub
[/more]
Автор: Quieteroks
Дата сообщения: 20.09.2011 07:59
SAS888



Цитата:
Цитата:Правда не сохранилось форматирование.
Что конкретно? Формат ячеек? Шрифт? Или что еще?


Конкретно формат ячеек. Строка из одной базы помечены желой заливкой. После копирования сторок в новую книгу, обработой Вашего макроса, строки обе белые. Хотя в исходном одна белая, вторая желтая.

AndVGri

Судя по всему да. Поэту мы так же писали макрос, который нам бы поделил книгу на 30 000 строк, что бы макрос поиска дубликатов справился. Но он и в этом случае не справился. Хотя когда задача была обратной, поиск уникальных, он отлично себя показал.
Автор: AndVGri
Дата сообщения: 20.09.2011 09:04
Quieteroks
Тестил на 1000000 случайных, вроде не вылетал на 2010
Автор: Quieteroks
Дата сообщения: 20.09.2011 10:05
AndVGri

Макрос не вылетает.
Результаты выполения двух макросов:
Обработка - 500 000 строк
Первы макрос (SAS888) - 1386 пар
Второй макрос (AndVGri) - 1512 пар из них 20 первых строк лишних...
Автор: SAS888
Дата сообщения: 20.09.2011 10:23
Попробуйте [more=этот]
Код: Sub Main()
Dim i As Long, j As Long, k As Long, bi As Long, ci As Long, temp As String
Dim x As New Collection, y As New Collection, a(), b(), c()
Dim r As Long, r1 As Long, r2 As Long, col As Long, blok As Integer
Dim ws As Worksheet, ws1 As Worksheet, ws2 As Worksheet
Application.ScreenUpdating = False: Application.DisplayAlerts = False
On Error Resume Next: Sheets("Уникальные").Delete: On Error GoTo 0
Set ws = ActiveSheet: Sheets.Add.Name = "Уникальные": Set ws1 = ActiveSheet
Workbooks.Add xlWBATWorksheet: ActiveSheet.Name = "Повторы": Set ws2 = ActiveSheet
col = ws.UsedRange.Columns.Count: r = ws.UsedRange.Row + ws.UsedRange.Rows.Count - 1
ws.Range(ws.[A1], ws.Cells(1, col)).EntireColumn.Copy
ws1.[A1].PasteSpecial Paste:=xlPasteColumnWidths
ws2.[A1].PasteSpecial Paste:=xlPasteColumnWidths
ws.Activate
a = Range("E1:H" & r).Value
For i = 1 To UBound(a, 1)
temp = a(i, 1) & a(i, 2) & a(i, 3) & a(i, 4)
On Error Resume Next: x.Add temp, temp
If Err <> 0 Then
y.Add temp, temp: On Error GoTo 0
End If
Next
blok = Application.RoundUp(r / 30000, 0): r1 = 1: r2 = 30000
For k = 1 To blok
If r2 > r Then r2 = r
a = Range(Cells(r1, 1), Cells(r2, col)).Value
bi = 0: ci = 0: ReDim b(1 To UBound(a, 1), 1 To UBound(a, 2)): c = b
For i = 1 To UBound(a, 1)
temp = a(i, 5) & a(i, 6) & a(i, 7) & a(i, 8)
On Error Resume Next: y.Add temp, temp
If Err = 0 Then
bi = bi + 1
For j = 1 To UBound(a, 2): b(bi, j) = a(i, j): Next
Else
ci = ci + 1
For j = 1 To UBound(a, 2): c(ci, j) = a(i, j): Next
On Error GoTo 0
End If
Next
If bi > 0 Then ws1.Cells(ws1.UsedRange.Rows.Count + 1, 1).Resize(bi, col).Value = b
If ci > 0 Then ws2.Cells(ws2.UsedRange.Rows.Count + 1, 1).Resize(ci, col).Value = c
r1 = r2 + 1: r2 = r2 + 30000
Next
[A1].Select: Set x = Nothing: Set y = Nothing: ws1.Activate
Application.CutCopyMode = False: Application.ScreenUpdating = True
End Sub
Автор: steemi
Дата сообщения: 20.09.2011 10:39
Скажите пажалуйста есть ли в VB функция которая определяет длину массива, т.е. количество в нем символов?
Автор: Quieteroks
Дата сообщения: 20.09.2011 10:44
SAS888

Отработал на хорошо. Создал базу уникальных в этой же книге перед исходным листом. Исходный остался нетронутым. Создал новую книгу с Повторами и результат выполнения 1496 пар и одна первая пустая строка. И так же первые 20 строк уникальных... Т.е. больше результатов получил макроса AndVGri.

Кстати сейчас заметил. Результат первого варианта макроса так же вбил теже 20 уникальных строк...

SAS888
AndVGri

Расскажите как строится логика работы макроса и если можно подробные комментарии к коду. Чтоб дальнейшие похожие задачи можно было решать самостоятельно.
Спасибо за помощь.
Автор: asbo
Дата сообщения: 20.09.2011 10:45
steemi

Цитата:
Скажите пажалуйста есть ли в VB функция которая определяет длину массива, т.е. количество в нем символов?

Имелось ввиду - есть строковый массив и надо получить сумму длин строк всех его элементов?
Размерность массива - Lbound и UBound(arrayname[, dimension])
Автор: steemi
Дата сообщения: 20.09.2011 10:51
немного не правильно задал вопрос, массив числово, и сколько в нем элементов,
так элементы могут быть разные
Автор: asbo
Дата сообщения: 20.09.2011 11:07
Lbound и UBound(arrayname[, dimension]) - индекс первого и последнего элемента указанной (или первой по умолчанию) размерности. F1

Добавлено:
Число элементов: (UBound - Lbound + 1)
Автор: SAS888
Дата сообщения: 20.09.2011 11:20
Quieteroks

Цитата:
...результат выполнения 1496 пар...
Во-первых, повторяться могут не только пары, но и тройки, четверки и т.д. Во-вторых, Вы так и не сказали, 1496 пар (или чего там) - это правильный результат, или нет?

Цитата:
...и одна первая пустая строка...
Каждый полученный блок макрос вставляет под уже использованные строки. Поэтому, при вставке 1-го блока (когда лист пуст) появляется пустая строка. Если она сильно мешает - удалите ее.

Цитата:
И так же первые 20 строк уникальных...
Не совсем понятно.... Т.е. в листе "Повторы" присутствуют уникальные строки? Такого быть не должно. Посмотрите, может быть повторы находятся не в соседних строках, а в разных местах листа. Если, все-таки, уникальные строки присутствуют - прикрепите проблемный файл, в котором это происходит. Естественно, не весь, а несколько строк.

Цитата:
Т.е. больше результатов получил макроса AndVGri.
Т.е. мой макрос пропускает какие-то повторы? Или 1512 - 20 = 1492 и есть правильный результат?

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

P.S. Вы не сравнивали время выполнения предложенных Вам вариантов? Мне самому интересно.

Страницы: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127

Предыдущая тема: VS 2010


Форум Ru-Board.club — поднят 15-09-2016 числа. Цель - сохранить наследие старого Ru-Board, истории становления российского интернета. Сделано для людей.