Лучший способ вставить текстовый файл с помощью ColdFusion и SQL?

#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>