MyTetra Share
Делитесь знаниями!
Что мы знаем про каунтеры
Время создания: 16.03.2019 23:43
Раздел: !Закладки - VBA - Access

Что мы знаем про каунтеры (aka счетчики)

Опубликовано: 30 апр 04
Рейтинг:

Добавить в закладки и поделиться


Оценить и прокомменитировать
Распечатать статью

Автор: Владимир Саныч, участники форума по Аксессу
Прислал: Владимир Саныч

Вопросы



Q1: Как создать свой счетчик (чтобы поле было не типа счетчик)?

Q2: Как заставить счетчик начать выдавать значения начиная с некоторой заданной величины?

Q3: Может ли поле счетчика содержать повторяющиеся значения?

Q4: В таблице есть счетчик, но его значения идут не подряд, несколько чисел в середине отсутствуют. Как перезаполнить поле, чтобы дырок не было? (Другой вариант вопроса. Счетчик показывает, что последняя запись в моей таблице имеет номер N, а реально записей меньше. Почему счетчик неправильно считает количество записей в таблице? Что это - баг или фича?)

Q5: Как сымитировать счетчик в отчете?

Q6: Как сымитировать счетчик в запросе на добавление?

Q7: Как сымитировать счетчик в обычном запросе либо ленточной форме?

Q8: Как получить значение счетчика только что добавленной записи?

Q9: Как создать одним запросом таблицу со счетчиком?

Вопросы с ответами



Q1: Как создать свой счетчик (чтобы поле было не типа счетчик)?

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

Вариант 1

Nz(DMax(...),0)+1


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

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

Вариант 2

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

Function Cou() As Long

Dim ws As Workspace, db As Database
Dim rsCounter As Recordset

On Error GoTo errCou
Set ws = DBEngine(0)
Set db = CurrentDb
1
ws.BeginTrans
2
Set rsCounter = db.OpenRecordset("select * from tabCounter")
3
rsCounter.AddNew
Cou = rsCounter!nCounter
'Close without update!'
rsCounter.Close
4
ws.CommitTrans
5
Exit Function

errCou:
Select Case Erl
    Case 3
        rsCounter.Close
        Set rsCounter = Nothing
        ws.Rollback
        DBEngine.Idle DB_FREELOCKS
        Resume 1
    Case 2, 4
        ws.Rollback
        DBEngine.Idle DB_FREELOCKS
        Resume 1
    Case Else
        Resume Next
End Select

End Function


Подвариант: все-таки делать rsCounter.Update, тогда по виду этой таблицы будет сразу ясно, какое значение было выдано последним. Правда, в этом случае файл станет расти.

Еще подвариант: держать в этой таблице одну запись, в которой находится очередное значение счетчика, и вместо AddNew делать Edit и rsCounter!nCounter=rsCounter!nCounter+1, а потом соответственно Update.

Вариант 3 (от Гетца)

Держим в отдельной таблице очередное значение счетчика и каждый раз увеличиваем его на 1. Таблица блокируется на момент чтения и увеличения счетчика, а все, кто в нее будут в это время стучаться, спокойно ждут (см. обработчик ошибок adhGetNextAutoNumber_Err) освобождения таблицы.

Function adhGetNextAutoNumber(ByVal strTableName As String) As Long

    On Error GoTo adhGetNextAutoNumber_Err

    Dim wrk As dao.Workspace
    Dim db As dao.Database
    Dim rstAutoNum As dao.Recordset
    Dim lngW As Long
    Dim lngX As Long
    Dim intRetryCount As Integer
    
    Randomize
    DoCmd.Hourglass True
    intRetryCount = 0

    Set wrk = dao.DBEngine.Workspaces(0)
    Set db = wrk.OpenDatabase(adhCurrentDBPath() & adhcAutoNumDb, False)
    Set rstAutoNum = db.OpenRecordset(strTableName & "_ID", dbOpenTable, dbDenyRead)

    rstAutoNum.MoveFirst
    rstAutoNum.Edit
    rstAutoNum!NextAutoNumber = rstAutoNum!NextAutoNumber + 1
    rstAutoNum.Update
 
    adhGetNextAutoNumber = lngNextAutoNum

adhGetNextAutoNumber_Exit:
    DoCmd.Hourglass False
    On Error Resume Next
    rstAutoNum.Close
    Set rstAutoNum = Nothing
    db.Close
    Set db = Nothing
    wrk.Close
    Set wrk = Nothing
    Exit Function

adhGetNextAutoNumber_Err:
    Select Case Err.Number
        Case adhcErrRI, adhcLockErrCantUpdate2, adhcLockErrTableInUse
            intRetryCount = intRetryCount + 1
            If intRetryCount > adhcLockRetries Then
                adhGetNextAutoNumber = -1
                Resume adhGetNextAutoNumber_Exit
            Else
                dao.DBEngine.Idle
                lngW = intRetryCount ^ 2 * _
                  Int((adhcLockUBound - adhcLockLBound + 1) * Rnd() + adhcLockLBound)
                For lngW = 1 To lngW
                    DoEvents
                Next lngW
                Resume
            End If
        Case Else
            MsgBox "Error " & Err.Number & ": " & Err.Description, _
             vbOKOnly + vbCritical, "adhGetNextAutoNumber"
            adhGetNextAutoNumber = -1
            Resume adhGetNextAutoNumber_Exit
    End Select

End Function


Q2: Как заставить счетчик начать выдавать значения начиная с некоторой заданной величины?

A1: Добавить в таблицу со счетчиком при помощи инсерта запись, в которой полю счетчика дается значение на 1 меньше, чем надо. Потом удалить эту запись. Способ работает только при условии, что этот счетчик этого или большего значения еще не выдавал. (Если таким образом дать счетчику отрицательное значение, то он начнет выдавать отрицательные значения, несмотря на то что уже выдавал значения, большие их. Играя на этом, можно добиться, чтобы счетчик выдавал любые значения, в т.ч. и те, которые уже были.)

A2: Сжать базу, в которой сидит таблица со счетчиком. Счетчик будет выдавать значения начиная с наибольшего из существующих +1. В некоторых версиях Аксесса это работает только при условии, что таблица со счетчиком пуста (и тогда счетчик начнет выдавать значения с 1).

A3: Начиная с Аксесса 2000, можно запустить запрос наподобие такого:

alter table Таблица1 alter column ПолеСчетчик counter(1,1)


Q3: Может ли поле счетчика содержать повторяющиеся значения?

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

Q4: В таблице есть счетчик, но его значения идут не подряд, несколько чисел в середине отсутствуют. Как перезаполнить поле, чтобы дырок не было? (Другой вариант вопроса. Счетчик показывает, что последняя запись в моей таблице имеет номер N, а реально записей меньше. Почему счетчик неправильно считает количество записей в таблице? Что это - баг или фича?)

A: Это нормальная ситуация. Если возникла необходимость, чтобы значения счетчика шли подряд, значит база была спроектирована неверно. Поле счетчика должно служить только для однозначной идентификации записей (и, возможно, порядка их занесения), юзер не должен видеть его значений, а если и увидит, то не должен возражать против тех значений, которые есть. Счетчик не служит для подсчета записей.

Q5: Как сымитировать счетчик в отчете?

A: Заводим текстбокс и задаем ему свойства:

ControlSource = "=1"
RunningSum = Over All


Q6: Как сымитировать счетчик в запросе на добавление?

A: Пишем функцию примерно такого вида:

Function MyFun(varDummy As Variant, Optional iStartValue As Variant) As Long
Static n As Long
If IsMissing(iStartValue) Then
    MyFun = n
    n = n + 1
Else
    n = iStartValue
    MyFun = True
End If
End Function


В запросе обращаемся к ней дважды:

в части WHERE - с параметрами (чтоугодно,N), где N равно нужному начальному значению счетчика;

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

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

Q7: Как сымитировать счетчик в обычном запросе либо ленточной форме?

A1:

SELECT (Select Sum(1) From t AS p Where p.f<=p1.f), p1.f
FROM t AS p1
ORDER BY p1.f;


A2:

SELECT DCount("f", "t","f<=" & CStr(f)), f
FROM t
ORDER BY f;


Примечание 1. Поле f обязано быть уникальным.

Примечание 2. Способ 1 быстрее работает, но является необновляемым.

Примечание 3. Аналогичный вопрос в другом разделе FAQ: http://www.sql.ru/faq/faq_topic.aspx?fid=126

A3: В новых версиях Аксесса у формы есть свойство CurrentRecord.

Q8: Как получить значение счетчика только что добавленной записи?

A1: Если запись добавляется через рекордсет, то так:

rs.AddNew
переменная = rs!полесчетчика
...
rs.Update

'или

Set rs = ...OpenRecordset("...where 1=0") 'обязательно пустой рекордсет
rs.AddNew 'ровно один раз; при добавлении двух и более записей ничего не получится
...
rs.Update
rs.MoveFirst
переменная = rs!полесчетчика

'или

rs.AddNew
...
rs.Update
rs.Bookmark = rs.LastModified
переменная = rs!полесчетчика


A2: Более широкий круг применимости у такого способа:

Dim rs As ADODB.Recordset 
Set rs = New ADODB.Recordset
CurrentProject.Connection.Execute "INSERT ..."
rs.Open "SELECT @@identity as cou", CurrentProject.Connection
переменная = rs!cou
rs.Close


Однако и этот способ имеет ограничения, а именно:

работает либо через ADO, либо через DAO в Jet 4 и позже, и только с базами формата Аксесса 2000 и позже;

возвращает значение только из записи, добавленной программно, но не через юзер-интерфейс.

A3: Для adp годится такая модификация того же способа:

SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY]


Q9: Как создать одним запросом таблицу со счетчиком?

A: create table xx (id counter)

Так же в этом разделе:
 
MyTetra Share v.0.53
Яндекс индекс цитирования