for flooders
:: Главная :: Решения :: Статьи :: Сайт М. Дроздова :: Файловый архив :: Книга по VFP 9 :: Русский Help Online :: OFF-LINE Форум
   Л и с о в о д ы   в с е х   с т р а н,  о б ъ е д и н я й т е с ь !!!  

Список Форумов  :: Visual Foxpro, Foxpro for DOS
   :: Помощь сайту :: 

Как автомат-ки получать автоинкриментное поле SQL сервера в КА
A1

Сообщений: 96
Дата: 14.02.06 05:25:15ОтветитьЦитировать
Уважаемые помогите рассказав

Использую курсорадаптеры для подсоединения к SQL SErver-ским таблицам. Все просто замечательно, но чую что кое-что можно сделать лучше, а точнее сказать правальнее. Допустим на сервере есть таблица, поле ID которой автоинкриментное, при создании курсорадаптера указваю, что разрещаю изменения и ключом ставлю поле ID
И теперь сам вопрос: в программе, когда пишу TABLEUPDATE, как можно автоматически получать поле ID, сгенерированное на сервере. Сейчас получаю так - делаю рефреш КА после чего перемещаюсь на максимальное значение ID.
Ratings: 0 negative/0 positive

Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
MichaelD

Сообщений: 7578
Дата: 14.02.06 08:33:33ОтветитьЦитировать
A1,

Цитата:
как можно автоматически получать поле ID, сгенерированное на сервере.
см. @@IDENTITY, IDENT_CURRENT('table_name'), SCOPE_IDENTITY() SQL Book online

Цитата:
Сейчас получаю так - делаю рефреш КА после чего перемещаюсь на максимальное значение ID
Здесь проблема в том, что неизвестно чей именно ID ты получишь... О-очень вероятно, что не этим клиентом добавленный при интенсивной работе на добавление...


------------------
С уважением,
Михаил Дроздов, Пермь, Россия
Ratings: 0 negative/0 positive

Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
Влад Колосов

Сообщений: 22439
Откуда: Ростов-на-Дону
Дата: 14.02.06 10:38:55ОтветитьЦитировать
При использовании VIEW надежнее всего отказаться от автоинкремента на сервере.


------------------
Совершенство - это не тогда, когда нельзя
ничего прибавить, а тогда, когда нечего убавить.
Ratings: 0 negative/0 positive

Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
Aleksey Tsingauz [MSFT]
Автор

Сообщений: 407
Дата: 14.02.06 22:27:27ОтветитьЦитировать
A1
Уважаемые помогите рассказав
Использую курсорадаптеры для подсоединения к SQL SErver-ским таблицам. Все просто замечательно, но чую что кое-что можно сделать лучше, а точнее сказать правальнее. Допустим на сервере есть таблица, поле ID которой автоинкриментное, при создании курсорадаптера указваю, что разрещаю изменения и ключом ставлю поле ID
И теперь сам вопрос: в программе, когда пишу TABLEUPDATE, как можно автоматически получать поле ID, сгенерированное на сервере. Сейчас получаю так - делаю рефреш КА после чего перемещаюсь на максимальное значение ID.

Здесь есть пример forum.foxclub.ru.

Примеры я постил на UT:
  
 ******************************  
  SET MULTILOCKS ON  
  SET DELETED ON  
  CLOSE DATABASES ALL   
  CLEAR  
    
  oCA=CREATEOBJECT("CACategoriesODBC")  
 * Use ODBC connection to the Northwind database on the local SQL Server as a data source  
  oCA.DataSource=SQLSTRINGCONNECT("DRIVER=SQL Server;SERVER=(local);Trusted_Connection=Yes; DataBase=Northwind")  
    
    
  IF !oCA.AutoOpen()  
  	AERROR(errs)  
  	DISPLAY MEMORY like errs  
  	return  
  ENDIF  
    
  SELECT (oCA.Alias)  
  LIST  
    
  ? "Add new category"  
  INSERT INTO (oCA.Alias) (Name) VALUES ("New category")  
  ?[TABLEUPDATE(.F.)=],TABLEUPDATE(.F.)  
  LIST  
    
 * delete the new record from the table  
  GO bottom  
  DELETE  
  TABLEUPDATE(.F.)  
    
    
  SQLDISCONNECT(oCA.DataSource)  
  RETURN   
    
    
  DEFINE CLASS CACategoriesODBC AS CursorAdapter  
  	Alias="CACategories"  
    
  	DataSourceType="ODBC"  
  	SelectCmd="select * from categories"  
  	CursorSchema="Id I, Name C(15), Descr M, Pict M"  
    
 	* set update properties  
  	Tables="Categories"  
  	KeyFieldList="Id"  
  	UpdatableFieldList="Name, Descr, Pict"  
  	UpdateNameList="Id Categories.CategoryId, Name Categories.CategoryName,"+;  
  		"Descr Categories.Description, Pict Categories.Picture"  
  	CompareMemo=.F.  
    
 	* Use ConversionFunc property to remove leading and trailing spaces from the Name field before sending   
 	* its value to the server   
  	ConversionFunc="Name AllTrim"  
    
 	* set conflict check type  
  	ConflictCheckType=3  
    
    
  	FUNCTION AutoOpen()  
  		NODEFAULT   
  		RETURN this.CursorFill(.T.) && use schema   
  	ENDFUNC  
    
  	PROCEDURE AfterInsert  
  		LPARAMETERS cFldState, lForce, cInsertCmd, lResult  
  		IF lResult=.T. && get new Id  
  			LOCAL currentArea  
  			currentArea=SELECT()  
    
 			* if insert is successful, update ID field with   
 			* real value using @@IDENTITY variable from the server  
 			* (CategoryId is IDENTITY field)  
  			TRY  
  				IF 1=SQLEXEC(this.DataSource,"SELECT @@IDENTITY as CategoryId","IDRes")  
  					replace id with IDRes.CategoryId IN (This.Alias)  
  					USE IN IDRes  
  				ELSE  
  					AERROR(errs)  
  					DISPLAY MEMORY like errs  
  				ENDIF  
  			FINALLY  
  				SELECT (currentArea)  
  			ENDTRY	  
  		ENDIF  
  	ENDPROC   
  ENDDEFINE  
 ******************************  

  
 ********************************  
 *Here is an example for ADO data source type, the same approach should work for ODBC.  
    
  SET EXCLUSIVE OFF  
  SET MULTILOCKS ON  
  SET DELETED ON  
  CLOSE DATABASES ALL   
    
  CLEAR   
    
  oCA=CREATEOBJECT("CACategoriesAdo2")  
  ?oCA.AutoOpen()  
  WAIT WINDOW "Insert/Update/Delete records in the BROWSE"  
  BROWSE NORMAL  
    
    
  DEFINE CLASS CACategoriesAdo2 AS CursorAdapter  
  	Alias="CACategories"  
    
 	* set data source type, SELECT command and cursor schema  
  	DataSourceType="ADO"  
  	SelectCmd="select CategoryId, CategoryName, Description from categories"  
  	CursorSchema="Id I, Name C(15), Descr M"  
    
    
 	* These properties must be set in order to send updates using ADODB.Command object  
  	InsertCmdDataSourceType="ADO"  
  	UpdateCmdDataSourceType="ADO"  
  	DeleteCmdDataSourceType="ADO"  
    
 	* We will use custom InsertCmd, UpdateCmd and DeleteCmd  
    
 	* Pass ID field as an output parameter to the #CategoryIns procedure,  
 	* It will be automatically updated with new IDENTITY value  
  	InsertCmd="EXEC #CategoryIns ?@CACategories.Id OUTPUT, ?CACategories.Name, ?CACategories.Descr"  
    
  	UpdateCmd="EXEC #CategoryUpd ?CACategories.Id, ?OLDVAL('Name','CACategories'), ?CACategories.Name, ?CACategories.Descr"  
    
  	DeleteCmd="EXEC #CategoryDel ?CACategories.Id, ?OLDVAL('Name','CACategories')"  
    
  	PROCEDURE Init  
  		LOCAL oCon as ADODB.Connection, oRS as ADODB.Recordset, oCom as ADODB.Command  
    
 		* When object is created, create ADO connection object and connect  
 		* to the Northwind database on the local SQL Server  
  		oCon=CREATEOBJECT("ADODB.Connection")  
  		oCon.Open("Provider=SQLOLEDB.1;Integrated Security=SSPI;Initial Catalog=Northwind;Data Source=(local)")  
    
 		* Create stored procedure for Insert, return new value for CategoryId field  
 		* using output parameter @ID	  
  		TEXT TO cSQL NOSHOW  
  			CREATE procedure #CategoryIns @id int OUTPUT, @Name varchar(15),   
  											@Descr text  
  			AS  
  			INSERT into Categories (CategoryName,Description)   
  			VALUES (@Name, @Descr)  
    
  			SELECT @id=SCOPE_IDENTITY( )  
  		ENDTEXT  
    
  		oCon.Execute(cSQL)  
    
 		* Create stored procedure for Update, it'll check for update conflicts  
  		TEXT TO cSQL NOSHOW  
  			CREATE procedure #CategoryUpd @id int, @OldName varchar(15),   
  							@Name varchar(15), @Descr text  
  			AS  
  			SET ANSI_NULLS OFF  
    
  			UPDATE Categories SET CategoryName=@Name,Description=@Descr  
  			WHERE CategoryId=@Id AND CategoryName=@OldName  
    
  			IF @@ROWCOUNT=0 AND @@ERROR=0  
  				RAISERROR (' Update conflict.', 16, 1)  
  		ENDTEXT  
    
  		oCon.Execute(cSQL)  
    
 		* Create stored procedure for Delete, it'll check for update conflicts  
  		TEXT TO cSQL NOSHOW  
  			CREATE procedure #CategoryDel @id int, @Name varchar(15)   
  			AS  
  			SET ANSI_NULLS OFF  
    
  			DELETE Categories  
  			WHERE CategoryId=@Id AND CategoryName=@Name  
    
  			IF @@ROWCOUNT=0 AND @@ERROR=0  
  				RAISERROR (' Update conflict.', 16, 1)  
  		ENDTEXT  
    
  		oCon.Execute(cSQL)  
    
 		* Create Recordset object and configure it for better fetch performance,  
 		* we will not use it for updates  
  		oRs=CREATEOBJECT("ADODB.Recordset")  
  		oRS.ActiveConnection=oCon  
  		oRS.CursorLocation= 2  && adUseServer   
  		oRS.CursorType= 0  && adOpenForwardOnly   
  		oRS.LockType= 1  && adLockReadOnly  
    
  		this.DataSource=oRS			  
    
 		* Create the ADODB.Command object to send updates through  
  		oCom=CREATEOBJECT("ADODB.Command")  
  		oCom.ActiveConnection=oCon  
    
  		this.InsertCmdDataSource=oCom  
  		this.UpdateCmdDataSource=oCom  
  		this.DeleteCmdDataSource=oCom  
  	ENDPROC  
    
  	FUNCTION AutoOpen()  
  		NODEFAULT   
  		RETURN this.CursorFill(.T.,.F.,-1) && use schema   
  	ENDFUNC  
    
    
 	* The following events are used only to print  
 	* commands that are being executed by the object  
  	PROCEDURE BeforeInsert   
  		LPARAMETERS cFldState, lForce, cInsertCmd  
  		?  
  		? PROGRAM()  
  		? "cInsertCmd=",cInsertCmd  
  	ENDPROC   
    
  	PROCEDURE BeforeUpdate   
  		LPARAMETERS cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd  
  		?  
  		? PROGRAM()  
  		? "cUpdateInsertCmd=",cUpdateInsertCmd  
  		? "cDeleteCmd=",cDeleteCmd  
  	ENDPROC   
    
  	PROCEDURE BeforeDelete   
  		LPARAMETERS cFldState, lForce, cDeleteCmd	  
  		?  
  		? PROGRAM()  
  		? "cDeleteCmd=",cDeleteCmd  
  	ENDPROC   
    
  ENDDEFINE  
 ********************************  

  
 **************************************    
  CLOSE DATABASES all    
  SQLDISCONNECT(0)    
  CLEAR    
  SET MULTILOCKS ON    
    
  LOCAL nAutoRefreshConn as Integer    
    
 * Change connection string to connect to any available SQL Server    
  nAutoRefreshConn=SQLSTRINGCONNECT("DRIVER=SQL Server;SERVER=(local);Trusted_Connection=Yes;")    
  IF (nAutoRefreshConn <1)    
   	? "Failed to connect!"    
     AERROR(aerrs)    
     DISPLAY MEMORY LIKE aerrs    
     return    
  ENDIF    
    
  TEXT TO cSQL NOSHOW    
     CREATE TABLE #CAAutoRefreshDemo    
     (    
      f_IDENTITY int NOT NULL IDENTITY PRIMARY KEY,    
      f1 int NOT NULL UNIQUE,    
      f_TIMESTAMP timestamp    
     )    
  ENDTEXT    
    
  IF SQLEXEC(nAutoRefreshConn ,cSQL)!=1    
     ? "Failed to create demo table!"    
     AERROR(aerrs)    
     DISPLAY MEMORY LIKE aerrs    
  ENDIF    
    
  LOCAL oCA as CursorAdapter  
    
  oCA=CREATEOBJECT("CursorAdapter")  
    
  oCA.Alias = "CATest"    
  oCA.BufferModeOverride= 5   
  oCA.DataSource= nAutoRefreshConn    
  oCA.DataSourceType="ODBC"    
  oCA.SelectCmd="select * from #CAAutoRefreshDemo"    
  oCA.CursorSchema="f_IDENTITY I, f1 I, f_TIMESTAMP Q(8)"      
  oCA.UseCursorSchema= .T.  
  oCA.Tables="#CAAutoRefreshDemo"    
  oCA.TimestampFieldList="f_TIMESTAMP"  
  oCA.UpdatableFieldList="f1"    
  oCA.UpdateNameList="f1 #CAAutoRefreshDemo.f1, f_TIMESTAMP #CAAutoRefreshDemo.f_TIMESTAMP, f_IDENTITY #CAAutoRefreshDemo.f_IDENTITY"    
    
  oCA.CursorFill()  
    
  ?"1) Refresh IDENTITY field using [SELECT @@IDENTITY] command"  
  oCA.InsertCmdRefreshFieldList="f_IDENTITY"  
  oCA.InsertCmdRefreshCmd="SELECT @@IDENTITY"  
    
  INSERT INTO CATest (f1) VALUES (1)  
  INSERT INTO CATest (f1) VALUES (2)  
  INSERT INTO CATest (f1) VALUES (3)  
    
  IF !TABLEUPDATE(.T.)  
   	? "TABLEUPDATE is failed!"    
     AERROR(aerrs)    
     DISPLAY MEMORY LIKE aerrs    
  ELSE  
  	LIST   
  ENDIF  
  ?  
  ?"2) Refresh IDENTITY and TIMESTAMP fields using "  
  ?"   [SELECT f_IDENTITY, f_TIMESTAMP FROM #CAAutoRefreshDemo WHERE f_IDENTITY=@@IDENTITY] command"  
  oCA.InsertCmdRefreshFieldList="f_IDENTITY"  
  oCA.RefreshTimestamp= .T.    
  oCA.InsertCmdRefreshCmd="SELECT f_IDENTITY, f_TIMESTAMP FROM #CAAutoRefreshDemo WHERE f_IDENTITY=@@IDENTITY"  
    
  INSERT INTO CATest (f1) VALUES (40)  
  INSERT INTO CATest (f1) VALUES (50)  
  INSERT INTO CATest (f1) VALUES (60)  
    
  IF !TABLEUPDATE(.T.)  
   	? "TABLEUPDATE is failed!"    
     AERROR(aerrs)    
     DISPLAY MEMORY LIKE aerrs    
  ELSE  
  	LIST   
  ENDIF  
    
    
  ?"3) Refresh IDENTITY and TIMESTAMP fields using alternative unique key"  
  oCA.RefreshTimestamp= .F. && just for demo purposes will include f_TIMESTAMP into InsertCmdRefreshFieldList  
  oCA.InsertCmdRefreshFieldList="f_IDENTITY, f_TIMESTAMP "  
  oCA.InsertCmdRefreshKeyFieldList="f1"  
  oCA.InsertCmdRefreshCmd="" && CursorAdapter will generate command automatically   
    
  INSERT INTO CATest (f1) VALUES (700)  
  INSERT INTO CATest (f1) VALUES (800)  
  INSERT INTO CATest (f1) VALUES (900)  
    
  IF !TABLEUPDATE(.T.)  
   	? "TABLEUPDATE is failed!"    
     AERROR(aerrs)    
     DISPLAY MEMORY LIKE aerrs    
  ELSE  
  	LIST   
  ENDIF  
    
    
  TABLEREVERT(.T.)  
  SQLDISCONNECT(0)  
  RETURN   
 ************************************************   


  
 ********************************  
  CLEAR  
    
  LOCAL oConn as ADODB.Connection, oRS as ADODB.Recordset, oCom as ADODB.Command  
    
  oConn=CREATEOBJECT("ADODB.Connection")  
  oConn.Open("Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Data Source=(local)")  
    
  oConn.Execute("create table #test (f1 int IDENTITY)")  
  oConn.Execute("create procedure #test_ins_1 @f1 int OUT as "+;  
  			  " insert into #test DEFAULT VALUES "+;  
  			  " select @f1=SCOPE_IDENTITY()")  
  oConn.Execute("create procedure #test_ins_2 as "+;  
  			  " insert into #test DEFAULT VALUES "+;  
  			  " return SCOPE_IDENTITY()")  
    
  oCom = CREATEOBJECT("Adodb.Command")  
  oCom.ActiveConnection = oConn  
    
  oRS = CREATEOBJECT("Adodb.Recordset")  
  oRS.ActiveConnection = oConn  
    
  LOCAL oCA as CursorAdapter  
    
  oCA=CREATEOBJECT("CursorAdapter")  
  oCA.Alias="test"  
  oCA.DataSourceType="ADO"  
  oCA.DataSource=oRS  
  oCA.SelectCmd="select * from #test"  
  oCA.InsertCmdDataSourceType="ADO"  
  oCA.InsertCmdDataSource=oCom  
  oCA.InsertCmd="EXEC #test_ins_1 ?@test.f1 OUT"  
    
  ?oCA.CursorFill()  
    
  APPEND BLANK  
  APPEND BLANK  
  APPEND BLANK  
    
  oCA.InsertCmd="EXEC ?@test.f1 = #test_ins_2"  
  APPEND BLANK  
  APPEND BLANK  
  APPEND BLANK  
    
  LIST  
    
  ?oCA.CursorRefresh()  
  LIST  
  USE  
 ********************************  
Ratings: 0 negative/0 positive

Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
Владимир Максимов

Сообщений: 13755
Откуда: Москва
Дата: 18.03.06 23:11:15ОтветитьЦитировать
Извиняюсь, что поднял старую тему, но тут на www.sql.ru на нескольких страницах пытаемся убедить товарища FoXXX, что способ поиска новой записи в Remote View как Requery()+Go Bottom - работает только при выполнении определенных условий. Ну, ладно, товарищь так отдыхает...

Но вообще-то, вопрос был поднят более общий. Все по той же теме. Как получить значение поля IDENTITY при работе через CAD. В связи с этим еще раз посмотрел решения предложенные Алексеем. Не ради критики, а для пояснения тем, кто пойдет тем же путем...

Вкратце, их суть сводится к следующему

1) через ODBC - после успешного создания новой записи читается значение @@IDENTITY

2) через ADO - вместо прямой команды вставки формируется хранимая процедура, куда в качестве параметра передаются значения полей. В этой процедуре собственно и выполняется команда INSERT и возвращается значение SCOPE_IDENTITY()

3) AutoRefresh - в таблицу вводится поле или набор полей, по значению которого также можно однозначно идентифицировать запись. Это НЕ ключевые поля. После вставки записи осуществляется поиск новой записи по значению этих вспомогательных полей

4) это дубль способа 2) несколько по другому оформленный.

Недостатки:

1) через ODBC - Работает, если у таблицы, куда происходит вставка нет триггера, в теле которого также есть вставка в таблицу имеющую поле типа IDENTITY. В этом случае получим значение поля из этой второй таблицы

2) и 4) через ADO - Вполне рабочее решение. Единственный недостаток - это избыточность (зачем посылать те поля, в которых ничего не ввели) и проблемы в случае, если в таблице допустимы поля со значением NULL (как отличить те поля в которые ничего не ввели от тех, в которые ввели пустое значение. Здесь подошло бы DEFAULT на поля, но как это настроить в CAD?)

3) AutoRefresh - Далеко не всегда в таблице есть дублирующие поля, которые могут взять на себя роль ID, даже в пределах выборки.
Ratings: 0 negative/0 positive

Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
Aleksey Tsingauz [MSFT]
Автор

Сообщений: 407
Дата: 20.03.06 07:14:56ОтветитьЦитировать
Здравствуйте, Владимир!

Действительно вопрос достаточно серьезный и общие пояснения не помешают. Существут несколько разных способов извлечения значения IDENTITY после вставки новой записи на сервере и примеры демонстрируют как использовать эти способы с CursorAdapter-ом.

1) Запросить значение @@IDENTITY (как уже было отмечено, иногда этот способ использовать нельзя).
Как с помощью CursorAdapter-а заставить TABLEUPDATE автоматически сохранять значение в поле локального курсора:
- Вручную послать запрос на сервер и изменить значение поля в обработчике AfterInsert (Пример №1), работает для ODBC и ADO.
- Вручную запросить значение поля из RecordSet-a и изменить значение поля в обработчике AfterInsert, работает только для ADO и только если CursorAdapter сконфигурирован для вставки новых записей через RecordSet.
- Использовать хранимую процедуру для вставки и обновлять значение поля с помощью OUTPUT параметра (аналогично примерам №2 и №4), работает для ODBC и ADO.
- Использовать InsertCmdRefreshFieldList без указания InsertCmdRefreshCmd (Пример ниже), работает только для ADO и только если CursorAdapter сконфигурирован для вставки новых записей через RecordSet.
- Использовать InsertCmdRefreshFieldList вместе с InsertCmdRefreshCmd (Пример №3.1 и №3.2), работает для ODBC и ADO.

2) Запросить значение SCOPE_IDENTITY().
Как с помощью CursorAdapter-а заставить TABLEUPDATE автоматически сохранять значение в поле локального курсора:
- Использовать хранимую процедуру для вставки и обновлять значение поля с помощью OUTPUT параметра (Пример №2 и №4), работает для ODBC и ADO.

3) Запросить значение из записи используя альтернативный уникальный ключ записи.
Как с помощью CursorAdapter-а заставить TABLEUPDATE автоматически сохранять значение в поле локального курсора:
- Вручную послать запрос на сервер и изменить значение поля в обработчике AfterInsert, работает для ODBC и ADO.
- Использовать хранимую процедуру для вставки и обновлять значение поля с помощью OUTPUT параметра, работает для ODBC и ADO.
- Использовать InsertCmdRefreshFieldList вместе с InsertCmdRefreshKeyFieldList (Пример №3.3), работает для ODBC и ADO.
- Использовать InsertCmdRefreshFieldList вместе с InsertCmdRefreshCmd, работает для ODBC и ADO.

Владимир Максимов
3) AutoRefresh - в таблицу вводится поле или набор полей, по значению которого также можно однозначно идентифицировать запись. Это НЕ ключевые поля. После вставки записи осуществляется поиск новой записи по значению этих вспомогательных полей
Как раз таки ЭТО КЛЮЧЕВЫЕ поля. Часто таблица имеет несколько уникальных ключей, надо просто не проглядеть это во время дизайна и создать соответствующие CONSTRAINTS.

Владимир Максимов
3) AutoRefresh - Далеко не всегда в таблице есть дублирующие поля, которые могут взять на себя роль ID, даже в пределах выборки.
Если рассматривать таблицу с IDENTITY, то не всегда, но очень, очень часто таблица имеет альтернативный уникальный ключ. Почему? Потому что, как правило, IDENTITY поле - это служебное поле, используемое для создания связей между таблицами и не несущее никакой смысловой нагрузки. С точки зрения дизайна таблицы, можно считать, что это поле добавляется в уже готовую таблицу, которая по законам дизайна уже должна иметь уникальный ключ. После добавления IDENTITY, тот ключ никуда не исчезает, просто его "забывают", что делать очень вредно, хотя бы исходя из соображений поддержания целостности и непротиворечивости базы.

Простой пример:
Для таблицы Categories в Northwind создан всего один уникальный ключ по CategoryId - явный баг в дизайне. Должен быть еще уникальный ключ по CategoryName. Разве имеет смысл иметь две разные категории с одним и тем же именем? Как пользователь должен их различать, помнить номера?

Владимир Максимов
2) и 4) через ADO - Вполне рабочее решение. Единственный недостаток - это избыточность (зачем посылать те поля, в которых ничего не ввели) и проблемы в случае, если в таблице допустимы поля со значением NULL (как отличить те поля в которые ничего не ввели от тех, в которые ввели пустое значение. Здесь подошло бы DEFAULT на поля, но как это настроить в CAD?)
Проблему избыточности можно решить, анализируя GETFLDSTATE в BeforeInsert и меняя cInsertCmd параметр на вызов соответствующей процедуры. Но, по-моему, проще передавать значения для всех полей вместе с GETFLDSTATE(-1) и анализировать GETFLDSTATE(-1) уже в процедуре. Это так же позволит корректно обрабатывать незаданные поля. А чтобы зря не гонять большие объемы данных для неизмененных полей, можно передавать для них NULL. Что-то типа:
  
  ...Cmd="EXEC ..., ?IIF(GETFLDSTATE('Descr','CACategories')%2=0,CACategories.Descr,NULL), ?GETFLDSTATE(-1,'CACategories')"

Алексей.

Обещанный пример:
  
  ********************************    
  *Here is an example for ADO data source type, when Recordset is used to update data source.    
        
    SET EXCLUSIVE OFF    
    SET MULTILOCKS ON    
    SET DELETED ON    
    CLOSE DATABASES ALL     
        
    CLEAR     
        
    oCA=CREATEOBJECT("CACategoriesAdo2")    
    IF !oCA.AutoOpen()    
    	AERROR(aerrs)  
    	DISPLAY MEMORY LIKE aerrs  
    	RETURN   
    ENDIF  
    WAIT WINDOW "Insert/Update/Delete records in the BROWSE"    
    BROWSE NORMAL    
        
        
    DEFINE CLASS CACategoriesAdo2 AS CursorAdapter    
    	Alias="CACategories"    
        
  	* set data source type, SELECT command and cursor schema    
    	DataSourceType="ADO"    
    	SelectCmd="select CategoryId, CategoryName, Description from categories"    
    	CursorSchema="Id I, Name C(15), Descr M"    
    	Tables ="categories"  
    	UpdatableFieldList = "Name, Descr"  
    	KeyFieldList = "Id"  
    	UpdateNameList = "Id categories.CategoryId, Name categories.CategoryName, Descr categories.Description"  
    
    	InsertCmdRefreshFieldList = "Id"  
        
    	PROCEDURE Init    
    		LOCAL oCon as ADODB.Connection, oRS as ADODB.Recordset    
        
  		* When object is created, create ADO connection object and connect    
  		* to the Northwind database on the local SQL Server    
    		oCon=CREATEOBJECT("ADODB.Connection")    
    		oCon.Open("Provider=SQLOLEDB.1;Integrated Security=SSPI;Initial Catalog=Northwind;Data Source=(local)")    
        
  		* Create Recordset object and configure it for data source update,    
    		oRs=CREATEOBJECT("ADODB.Recordset")    
    		oRS.ActiveConnection=oCon    
    		oRS.CursorLocation = 3  && adUseClient   
    		oRS.CursorType= 1  && adOpenKeyset  
    		oRS.LockType= 3  && adLockOptimistic  
        
    		this.DataSource=oRS			    
    	ENDPROC    
        
    	FUNCTION AutoOpen()    
    		NODEFAULT     
    		RETURN this.CursorFill(.T.,.F.,-1) && use schema     
    	ENDFUNC    
        
        
  	* The following events are used only to print    
  	* commands that are being executed by the object    
    	PROCEDURE BeforeInsert     
    		LPARAMETERS cFldState, lForce, cInsertCmd    
    		?    
    		? PROGRAM()    
    		? "cInsertCmd=",cInsertCmd    
    	ENDPROC     
        
    	PROCEDURE BeforeUpdate     
    		LPARAMETERS cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd    
    		?    
    		? PROGRAM()    
    		? "cUpdateInsertCmd=",cUpdateInsertCmd    
    		? "cDeleteCmd=",cDeleteCmd    
    	ENDPROC     
        
    	PROCEDURE BeforeDelete     
    		LPARAMETERS cFldState, lForce, cDeleteCmd	    
    		?    
    		? PROGRAM()    
    		? "cDeleteCmd=",cDeleteCmd    
    	ENDPROC     
        
    ENDDEFINE    
  ********************************   



Исправлено: Aleksey Tsingauz [MSFT], 20.03.06 07:17
Ratings: 0 negative/0 positive

Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
WiRuc

Сообщений: 1012
Дата: 20.03.06 10:07:00ОтветитьЦитировать
Небольшое уточнение.
CREATE procedure #CategoryUpd @id int, @OldName varchar(15),     
    							@Name varchar(15), @Descr text    
    			AS    
    			SET ANSI_NULLS OFF    
        
    			UPDATE Categories SET CategoryName=@Name,Description=@Descr    
    			WHERE CategoryId=@Id AND CategoryName=@OldName    
        
    			IF @@ROWCOUNT=0 AND @@ERROR=0    
    				RAISERROR (' Update conflict.', 16, 1)

Настройка ANSI_NULLS запоминается при создании ХП, так что строка SET ANSI_NULLS OFF просто игнорируется. Надо было сначала выдать SET ANSI_NULLS OFF через SQLEXEC, а затем создавать процедуру.

А по сути вопроса, считаю, что метод 2 (через ХП) является единственно правильным.
Ratings: 0 negative/0 positive

Re: Как автомат-ки получать автоинкриментное поле SQL сервера в КА
Aleksey Tsingauz [MSFT]
Автор

Сообщений: 407
Дата: 10.04.06 03:44:54ОтветитьЦитировать
Поскольку вопрос продолжает быть актуальным, помещаю еще один способ извлечения IDENTITY CursrAdapter-ом с помощью SCOPE_IDENTITY(). Этот способ основан на использовании вспомогательной временной хранимой процедуры и на использовании события CursorAdapter.BeforeInsert, которое добавляет вызов процедуры в конец команды INSERT (будь то автоматически сгенерированная команда или команда явно заданная через CursorAdapter.InsertCmd). Код процедуры и события BeforeInsert абсолютно не зависят от схемы таблицы, единственное что требуется знать - имя столбца в курсоре куда сохранить значение. Думаю, что самые придирчевые критики будут удовлетворены этим подходом, можно написать один базовый класс и использовать его для любой таблицы с IDENTITY.

Пример для ODBC, но способ также должен работать для ADO:
  
 **************************************      
    CLOSE DATABASES all      
    SQLDISCONNECT(0)      
    CLEAR      
    SET MULTILOCKS ON      
        
    LOCAL nAutoRefreshConn as Integer      
        
  * Change connection string to connect to any available SQL Server      
    nAutoRefreshConn=SQLSTRINGCONNECT("DRIVER=SQL Server;SERVER=(local);Trusted_Connection=Yes;")      
    IF (nAutoRefreshConn <1)      
     	? "Failed to connect!"      
       AERROR(aerrs)      
       DISPLAY MEMORY LIKE aerrs      
       return      
    ENDIF      
        
    TEXT TO cSQL NOSHOW      
       CREATE TABLE #CAAutoRefreshDemo      
       (      
        f_IDENTITY int NOT NULL IDENTITY PRIMARY KEY,      
        f1 int NOT NULL UNIQUE,      
        f_TIMESTAMP timestamp      
       )      
    ENDTEXT      
        
    IF SQLEXEC(nAutoRefreshConn ,cSQL)!=1      
       ? "Failed to create demo table!"      
       AERROR(aerrs)      
       DISPLAY MEMORY LIKE aerrs      
    ENDIF      
        
    LOCAL oCA as CursorAdapter    
        
    oCA=CREATEOBJECT("CA_SCOPE_IDENTITY")    
        
    oCA.Alias = "CATest"      
    oCA.BufferModeOverride= 5     
    oCA.DataSource= nAutoRefreshConn      
    oCA.DataSourceType="ODBC"      
    oCA.SelectCmd="select * from #CAAutoRefreshDemo"      
    oCA.CursorSchema="f_IDENTITY I, f1 I, f_TIMESTAMP Q(8)"        
    oCA.UseCursorSchema= .T.    
    oCA.Tables="#CAAutoRefreshDemo"      
    oCA.UpdatableFieldList="f1"      
    oCA.UpdateNameList="f1 #CAAutoRefreshDemo.f1, f_TIMESTAMP #CAAutoRefreshDemo.f_TIMESTAMP, f_IDENTITY #CAAutoRefreshDemo.f_IDENTITY"      
    oCA.KeyFieldList="f_IDENTITY"  
        
    oCA.CursorFill()    
        
    oCA.IDENTITY_Field="f_IDENTITY"    
    oCA.RefreshTimestamp= .F. && just for demo purposes will include f_TIMESTAMP into InsertCmdRefreshFieldList    
    oCA.InsertCmdRefreshFieldList="f_TIMESTAMP "    
        
    INSERT INTO CATest (f1) VALUES (100)    
    INSERT INTO CATest (f1) VALUES (200)    
    INSERT INTO CATest (f1) VALUES (300)    
    INSERT INTO CATest (f1) VALUES (400)    
    INSERT INTO CATest (f1) VALUES (500)    
    INSERT INTO CATest (f1) VALUES (600)    
    INSERT INTO CATest (f1) VALUES (700)    
    INSERT INTO CATest (f1) VALUES (800)    
    INSERT INTO CATest (f1) VALUES (900)    
        
    IF !TABLEUPDATE(.T.)    
     	? "TABLEUPDATE is failed!"      
       AERROR(aerrs)      
       DISPLAY MEMORY LIKE aerrs      
    ELSE    
    	LIST     
    ENDIF    
        
        
    TABLEREVERT(.T.)    
    SQLDISCONNECT(0)    
    RETURN     
    
    
    DEFINE CLASS CA_SCOPE_IDENTITY AS CursorAdapter    
      IDENTITY_Field=""  
        
     PROCEDURE DataSource_ASSIGN   
        LPARAMETERS tAssign    
        this.DataSource = tAssign      
        TRY  
        SQLEXEC(this.DataSource, ;  
        		"create procedure #Get_ValHelper @in Int, @out int OUTPUT " + ;  
        		"AS SET NOCOUNT ON " + ;  
        		"SELECT @out=@in")   
  	  CATCH  
  	  ENDTRY  
     ENDPROC  
        
    	PROCEDURE BeforeInsert     
    		LPARAMETERS cFldState, lForce, cInsertCmd    
    		IF LEN(ALLTRIM(this.IDENTITY_Field))>0  
    			cInsertCmd = cInsertCmd + ;  
    				"; DECLARE @id int; SELECT @id = SCOPE_IDENTITY()" + ;  
    				"; EXEC #Get_ValHelper @id, ?@" + ;  
    				this.Alias + "." + ALLTRIM(this.IDENTITY_Field)  
    		ENDIF  
    		?    
    		? PROGRAM()    
    		? "cInsertCmd=",cInsertCmd    
    	ENDPROC     
        
    ENDDEFINE    
  ********************************   

Алексей.
Ratings: 0 negative/0 positive



Извините, только зарегистрированные пользователи могут писать в этом форуме.

On-line: 28 Taran  and Guests: 27


© 2000-2017 Fox Club 
Яндекс.Метрика