Получить первичный ключ после использования CFINSERT - ColdFusion

Когда я использую CFINSERT, данные формы вставляются в мою базу данных, потому что имена полей совпадают с именами столбцов.

МОЙ ВОПРОС: как я могу получить первичный ключ только что добавленной строки с помощью CFINSERT?

Я знаю, что не могу использовать "Result='variable"" аналогично стандартному cfquery, так как же лучше всего получить первичный ключ?

Если я запущу следующий запрос сразу после моего cfinsert, он должен вернуть предыдущий PK:

<cfquery name="getID" datasource="#mydsn#" result="#result#">
select Max(id) as NewID from myTablename;
</cfquery>

Это лучший способ выполнить то, что я пытаюсь сделать?


person Denoteone    schedule 21.04.2015    source источник
comment
Какую систему баз данных вы используете? Кроме того, вы полны решимости использовать <cfinsert>? Возможно, полезной альтернативой будет <cfquery> с одним оператором INSERT для вставки данных и одним оператором SELECT для получения нового идентификатора.   -  person Tomalak    schedule 21.04.2015
comment
Я только что нашел это: ‹cfquery name=getID datasource=#mydsn# result=#result#> выберите Max(id) как NewID из myTablename; ‹/cfquery›   -  person Denoteone    schedule 21.04.2015
comment
Что, я думаю, сработает, если я запущу его после cfinsert. Я использую сервер MSSQL, чтобы ответить на ваш вопрос.   -  person Denoteone    schedule 21.04.2015
comment
Это нехорошо, потому что это состояние гонки - когда две формы отправляются одновременно, тогда оба раза SELECT MAX(id) будет возвращать одно и то же значение.   -  person Tomalak    schedule 21.04.2015
comment
Я подумал, что это может быть проблемой. У вас есть какие-нибудь рекомендации?   -  person Denoteone    schedule 21.04.2015
comment
Это зависит от вашей системы баз данных. SQL Server имеет SCOPE_IDENTITY для решения этой конкретной проблемы. Другие системы БД используют другие функции. Итак, это зависит.   -  person Tomalak    schedule 21.04.2015
comment
Я не уверен, что это можно комбинировать с cfinsert, поскольку на самом деле я не пишу запрос для использования Scope_Identity.   -  person Denoteone    schedule 21.04.2015
comment
Вот почему я спросил, решили ли вы использовать <cfinsert> или нет. Видите ли, если бы вы только ответили на два вопроса в моем первом комментарии...   -  person Tomalak    schedule 21.04.2015
comment
Извините, что ответил на него, но похоже, что он был обрезан между моим вторым и третьим комментарием, когда я случайно нажал «Возврат». ДА, я хочу использовать cfinsert, потому что у меня более 120 входных данных, и это сэкономит мне время.   -  person Denoteone    schedule 21.04.2015
comment
Я до сих пор не знаю вашу систему баз данных, поэтому вы не ответили на другой вопрос. Правда, сейчас на 10 комментариев позже.   -  person Tomalak    schedule 21.04.2015
comment
Что, я думаю, сработает, если я запущу его после cfinsert. Я использую сервер MSSQL, чтобы ответить на ваш вопрос. – Denoteone 15 минут назад Было недостаточно информации?   -  person Denoteone    schedule 21.04.2015
comment
Хм, ладно, проскользнуло. Прости. Итак, вместо MAX(id) используйте SELECT SCOPE_IDENTITY() as NewId в cfquery.   -  person Tomalak    schedule 21.04.2015
comment
Я сделаю это. Если вы поместите это в ответ, я могу пометить его как закрытый.   -  person Denoteone    schedule 21.04.2015
comment
Я не совсем уверен, что это позволяет избежать состояния гонки, потому что если вы выполняете cfinsert и отдельный cfquery, это два пакета (документы: два оператора находятся в одной области, если они находятся в одной и той же хранимой процедуре, функции, или партия). SCOPE_IDENTITY даже возвращает значение при таком использовании?   -  person Tomalak    schedule 21.04.2015
comment
возможный дубликат SQL — вставка строки и возврат первичного ключа   -  person da_didi    schedule 21.04.2015
comment
@da_didi На самом деле это не дубликат, потому что обстоятельства зависят от того, как это можно использовать в ColdFusion.   -  person Tomalak    schedule 21.04.2015
comment
Не используйте cfinsert.....никогда.   -  person Scott Stroz    schedule 21.04.2015


Ответы (2)


Поскольку вы работаете на SQL Server, вы можете использовать функцию SCOPE_IDENTITY(), чтобы безопасно получить последнее вставленное значение идентификатора в текущей области.

В документации говорится

SCOPE_IDENTITY (Transact-SQL)

Возвращает последнее значение идентификатора, вставленное в столбец идентификаторов в той же области. Область видимости — это модуль: хранимая процедура, триггер, функция или пакет. Таким образом, два оператора находятся в одной области, если они находятся в одной и той же хранимой процедуре, функции или пакете.

При использовании в двух отдельных тегах ColdFusion (<cfinsert> с последующим <cfquery>) это две партии, и SCOPE_IDENTITY() больше не будет работать. Следовательно, операторы INSERT и SELECT должны быть частью одного и того же пакета. К сожалению, это не может быть достигнуто с помощью <cfinsert>.

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

<cfset fieldNames = "all,relevant,field,names,from,http,post">
<cfset fieldTypes = "INTEGER,VARCHAR,VARCHAR,DATETIME,INTEGER,VARCHAR,VARCHAR">
<cfset fieldNullable = "false,true,true,true,false,true,false">
<cfset fieldCount = ListLen(fieldNames)>

<!--- default "" for any fields missing from the HTTP POST --->
<cfloop from="1" to="#fieldCount#" index="i">
  <cfparam name="FORM.#ListGetAt(fieldNames, i)#" default="">
</cfloop>

<cfquery name="insert" datasource="#yourdatasource#">
  INSERT YourTable (#fieldNames#)
  VALUES (
    <cfloop from="1" to="#fieldCount#" index="i">
      <cfif i gt 1>,</cfif>
      <cfset val = FORM[ListGetAt(fieldNames, i)]>
      <cfset type = "CF_SQL_#ListGetAt(fieldTypes, i)#">
      <cfset null = ListGetAt(fieldNullable, i) eq "true" and val eq "">
      <cfqueryparam value="#val#" cfsqltype="#type#" null="#null#">
    </cfloop>
  )

  SELECT SCOPE_IDENTITY() as NewId
</cfquery>

<cfdump var="#insert#">
person Tomalak    schedule 21.04.2015
comment
Если вы собираетесь использовать cfquery, атрибут результата будет проще. Однако использование цикла разумно. - person Dan Bracuk; 21.04.2015
comment
Ах, вы правы, insert.IDENTITYCOL на самом деле проще. - person Tomalak; 21.04.2015
comment
тогда это два пакета Верно, хотя FWIW, заключающий несколько операторов sql в cftransaction, должен гарантировать, что они сохранят одно и то же соединение. Затем вы можете безопасно получить значение scope_identity(). Тем не менее, рекомендуется использовать стандартный INSERT + query.GENERATEDKEY (не зависящий от БД). - person Leigh; 21.04.2015
comment
@Leigh Я думал о том, чтобы порекомендовать <cftransaction>, но я не уверен, что это гарантированно сработает. По моему опыту, SCOPE_IDENTITY ничего не возвращает, если в текущем пакете не было вставки. - person Tomalak; 21.04.2015
comment
@Tomalak - Насколько я понимаю, scope_identity относится к текущему сеансу. Обертывание нескольких операторов в cftransaction обеспечивает один и тот же сеанс, а это гарантирует, что он будет работать. Таким образом, результат должен быть таким же, как если бы вы выполнили эти два оператора в одном окне запроса SSMS, без go между ними. - person Leigh; 21.04.2015
comment
Не имея SQL Server для тестирования, я не хотел давать неопределенные рекомендации. Если вы можете убедиться, что это работает, опубликуйте его как альтернативный ответ. - person Tomalak; 21.04.2015
comment
Редактировать: @Tomalak - Понятно. Мои комментарии были более информативными о cftransaction и сеансах для других, читающих ветку в будущем :) Я могу убедиться, что это работает, как и ожидалось, но я не думаю, что это стоит отдельного ответа. По сравнению с простотой использования query.GENERATEDKEY или одного запроса с SELECT SCOPE_IDENTITY() as NewId использование cftransaction с несколькими cfquery в этом случае было бы ненужным усложнением. Кроме того, я не хочу поощрять использование устаревшего тега cfinsert ;-) Стандартный INSERT — это то, что нужно. - person Leigh; 21.04.2015
comment
Я не думаю, что использование <cftransaction> необходимо для обеспечения того же сеанса БД. Я полагаю, это позволило бы откатить транзакцию в (очень маловероятном) случае, когда SCOPE_IDENTITY() ничего не возвращает. - person David Faber; 22.04.2015
comment
@DavidFaber - Да, логически кажется, что один запрос будет поддерживать одно и то же соединение с БД, но ... Я никогда не видел, чтобы это было задокументировано, что это гарантировано. Так что это может работать и без него, но, насколько мне известно, единственный способ гарантировать, что вы поддерживаете такое же соединение, — это cftransaction. - person Leigh; 22.04.2015
comment
Кроме того, в документации SQL Server четко указано, что это должен быть один и тот же пакет, одного и того же соединения недостаточно. - person Tomalak; 22.04.2015
comment
Истинный. Хотя я думаю, что здесь должно быть достаточно того же соединения. Насколько я понимаю, пакет состоит из одного или нескольких операторов, разделенных ключевым словом TSQL GO. Поскольку GO не поддерживается за пределами клиентов MS, я думаю, что в этом сценарии одно и то же соединение и пакет должны быть эквивалентны. - person Leigh; 23.04.2015
comment
@Leigh Пакет — это группа из одного или нескольких операторов Transact-SQL, одновременно отправляемых из приложения на SQL Server для выполнения. (ref) — GO разделяет несколько инструкций, отправляемых одновременно, на пакеты. Он не создает пакет из нескольких операторов, отправленных в разное время. Доверие к тому, что несколько операторов CF будут выполняться в одном и том же пакете, приведет к проблемам. - person Tomalak; 23.04.2015

Лучший способ справиться с этим — получить первичный ключ из generatedKey из структуры запроса result.

<cfquery name="myQuery" result="queryResult" datasource="#myDSN#">
    INSERT INTO some_table
    (column_one) 
    VALUES 
    (<cfqueryparam value="#stringForColOne#" cfsqltype="CF_SQL_VARCHAR">)
</cfquery>
<cfoutput>
    Generated Key from SQL Insert = #queryResult.generatedKey#
</cfoutput>

См. https://wikidocs.adobe.com/wiki/display/coldfusionen/cfquery#cfquery-Usage

person Luke    schedule 04.06.2015