#sql #coldfusion #cfloop
#sql #coldfusion #cfloop
Вопрос:
У меня есть несколько текстовых файлов, которые я должен вставить в одну из своих таблиц в SQL. У меня есть отдельная таблица, в которой хранятся имена моих текстовых файлов. Я должен получить имя из этой таблицы, а затем использовать fileOpen()
для получения файла из определенной папки. После того, как я закончу этот шаг, у меня возникнет проблема решить, каков наилучший способ выполнить следующие шаги. Поэтому я должен использовать fileReadLine()
, чтобы получить контекст каждой строки текстового файла. Также я не должен читать первую строку, поскольку она содержит только имена столбцов. Во-вторых, я должен проверить, как только я дойду до конца файла. В самом конце я должен выполнить цикл и выполнить ВСТАВКУ в таблицу. Мне было интересно, есть ли какие-либо новые способы сделать это? В моем текущем коде их два cfloops
, и это выглядит очень неэффективно. Вот мой текущий код:
<!--- Grab stuff from File Table. --->
<cfquery datasource="test" name="myQuery1">
SELECT *
FROM FilesTxt
</cfquery>
<cfloop query="myQuery1">
<!--- Read File --->
<cfset dataFile = fileOpen(here is my pathamp;""amp;#FileName#, "read" ) />
<cfset line = fileReadLine( dataFile ) />
<!--- Loop to see if hit the end of file, if not, read next line --->
<cfloop condition="!fileIsEOF( dataFile )">
<cfset line = fileReadLine( dataFile ) />
<cfif trim(line) NEQ "">
<cfset line = #Replace(line,"'","","ALL")#>
<cfset line = #Replace(line,'"',"","ALL")#>
<!--- Build array of junk in the file --->
<cfset sList = ListToArray(line, chr(9),'yes')>
<cftry>
<cfquery datasource="test" name="Insert">
//Here is my Insert statement
</cfquery>
</cftry>
</cfif>
</cfloop>
</cfloop>
Я думал о выполнении отдельного цикла, который создаст массив со всеми элементами, которые должны быть вставлены, а затем запустить другой цикл для выполнения вставки. Я не уверен, каков наилучший подход в этом случае. Если кто-нибудь знает какой-либо другой способ, пожалуйста, дайте мне знать. Спасибо
Комментарии:
1. В большинстве баз данных есть инструменты для загрузки текстовых файлов, которые обычно намного эффективнее, чем циклирование. Инструменты специфичны для СУБД. Выполните поиск в MySQL — ЗАГРУЗКА ДАННЫХ, SQL Server — МАССОВАЯ ВСТАВКА и так далее. Есть множество примеров. (Кстати, при вопросах о базе данных всегда указывайте свою СУБД и версию.)
Ответ №1:
Вы можете напрямую перебирать строки файла следующим образом:
<cfloop file="**path/filename**" index="LineOfMyFile">
<cfoutput>#LineOfMyFile#</cfoutput>
</cfloop>
Цикл завершается в конце файла, поэтому вам действительно не нужна функция fileisEOF().
Внутри вашего цикла вы можете использовать функции списка, а не перебирать массив. Если вы знаете позиции элементов в вашем списке. вот так:
<cfloop file="**path/filename**" index="LineOfMyFile">
<cfquery name="myinsert" datasource="#blah#">
INSERT INTO myTable (col1, col2, col3)
VALUES (<cfqueryparam cfsqltype="CF_SQL_INTEGER" value="#listgetat(lineOfMyFile,1,char(9))#">,
<cfqueryparam cfsqltype="CF_SQL_CHAR" value="#listgetat(lineofmyFile,2,char(9))#">,
<cfqueryparam cfsqltype="CF_SQL_CHAR" value="#listgetat(lineofMyFile,3,char(9))#">)
</cfquery>
</cfloop>
Это будет один цикл. В зависимости от размера вашего файла это может быть или не быть более эффективным. Кроме того, обычно выполняются проверки на наличие type, null, пустых строк и тому подобного. Таким образом, у вас может быть часть этого кода для массирования данных перед вставкой. Надеюсь, это поможет.
Комментарии:
1. Хороший улов,
<cfloop file>
я об этом не подумал. (ListLen()
Проверка была бы хорошей, так как это приведет к сбою для строк, в которых по какой-то причине слишком мало столбцов)2. @MarkAKruger У меня есть еще несколько вопросов, связанных с вашим ответом выше. Должен ли я удалить свой код Replace ()? Это уже сделано в вашем коде? Также с использованием listgetAt() Я в основном просто указываю позицию элемента, который мне нужен, из моего файла? Или я не понял, как это работает… Также я должен переместить свою ВСТАВКУ В … часть из cfloop и поместить цикл после ЗНАЧЕНИЙ? Я думаю, что это может быть лучшим вариантом, но не уверен на 100%?
3. Я тестировал listGetAt(), который укажет позицию, но у меня все еще есть первая строка с именами столбцов. Как я могу избавиться от первой строки из моего .txt-файла? Заранее спасибо.
4. Также файл cfloop игнорирует пробелы / пустые строки в моем файле. Это приводит к смещению и изменению положения моих столбцов. Есть ли какой-либо способ, как это можно исправить?
5. Установите счетчик равным 1 вне цикла и increment (
counter
) в конце внутри цикла. Затем заключите код вставки в скобки<cfif counter IS NOT 1>
. Что касается разделителей, которые будут зависеть от данных.
Ответ №2:
Если вы хотите вставить 100 000 значений в таблицу, невозможно обойти выполнение 100 000 инструкций INSERT.
Специализированные инструменты или команды, принадлежащие базе данных, могут сделать это более оптимизированным способом, но лично я не вижу ничего плохого во вложенном цикле здесь.
<cfquery name="files" datasource="test">
SELECT FileName FROM FilesTxt
</cfquery>
<cfloop query="files">
<cfset file = fileOpen("here is my path#FileName#", "read")>
<cfset fileReadLine(file)>
<cfloop condition="not fileIsEOF(file)">
<cfloop list="#fileReadLine(file)#" delimiters="#Chr(9)#" index="item">
<cftry>
<cfquery datasource="test">
INSERT testTable (testColumn) VALUES (
<cfqueryparam value="#Trim(item)#" cfsqltype="CF_SQL_VARCHAR">
)
</cfquery>
</cftry>
</cfloop>
</cfloop>
</cfloop>
Примечания:
- Не делайте
SELECT *
. Назовите нужные вам столбцы. - Не комментируйте очевидное. «Захватить материал из файловой таблицы» — это совершенно лишний комментарий, об этом говорится в коде.
- Используйте правильные имена переменных.
files
намного лучше, чемmyQuery1
. -
Нет необходимости использовать,
##
если вы не хотите интерполировать переменное содержимое в строку, в атрибут тега CF или в выходные данные.<cfset line = #Replace(...)#> <!--- useless use of ## ---> <cfset line = Replace(...)> <!--- much better --->
-
Вы можете использовать
<cfloop list="">
для перебора строки из файла CSV. В конце концов, это простой список. - Всегда используйте
<cfqueryparam>
в своих запросах. Таким образом, вам не нужно беспокоиться о кавычках в значениях. Это также более эффективно в цикле. - Запросы на вставку на самом деле не нуждаются в
name
- Избегайте
<cftry>
без<cfcatch>
, если вы действительно не заботитесь об ошибках. - И последнее, но не менее важное: CSV — более сложный формат, чем можно было бы подумать. «Просто разделите его по строкам на символ табуляции» не будет работать, если по какой-либо причине ТАБУЛЯЦИЯ или ПЕРЕВОД СТРОКИ являются частью значения (что было бы допустимо, если значение указано в кавычках). Посмотрите вокруг на анализатор CSV (возможно, начните здесь).
Комментарии:
1. Используйте массовую вставку. Зачем делать более 1 вызова базы данных?
2. @LrakWortep Конечно, но это зависит от базы данных. Нет команды ColdFusion «массовая вставка».
3. пакетная вставка — это t-sql и работает для всех версий sql. В coldfusion также нет обычной команды «Вставить». Вы пишете свой sql и выполняете его.
4. @LrakWortep Ваш подход в худшем случае подвержен SQL-инъекциям, случайным синтаксическим ошибкам в менее худшем случае, а для больших входных данных может превышать ограничение длины одного оператора SQL. Это может быть приемлемым решением для одноразового использования, хотя для одноразового использования цикл с параметризованным запросом тоже не так уж плох. Но это не особенно хорошее решение для производственного использования. Я бы предпочел параметризованный запрос в любое время или — для лучшей производительности — проприетарную функцию массовой загрузки CSV рассматриваемой СУБД.
5. Просто сказать, что это утверждение абсолютно неверно «невозможно обойти создание 100 000 инструкций INSERT».
Ответ №3:
Все эти ответы неверны. Если вы хотите вставить кучу значений, используйте SQL BULK INSERT
<cfset myInserts = "">
<cfloop file="test" index = "line">
<cfset myInserts = listAppend(myInserts,"(#line#)">
</cfloop>
<cfquery>
INSERT INTO myTable VALUES #preserveSingelQuotes(myInserts)#
</cfquery>