Excel is a powerful and most popular tool for data analysis! HTTP requests in VBA gives additional capabilities to Excel. XmlHttpRequest object is used to make HTTP requests in VBA. HTTP requests can be used to interact with a web service, API or even websites. Let’s understand how it works.
Open an excel file and open VBA editor (Alt + f11) > new module and start writing code in a sub
Public sub XmlHttpTutorial End Sub
Define XMLHttpRequest
Define http client using following code
Dim xmlhttp as object Set xmlhttp = CreateObject("MSXML2.serverXMLHTTP")
If you need VBA’s Intellisense autocomplete then do it this way :
First, Add a reference to MSXML (Tools > references)
Select appropriate version based on your PC :
1. Microsoft XML, v 3.0.
2. Microsoft XML, v 4.0 (if you have installed MSXML 4.0 separately).
3. Microsoft XML, v 5.0 (if you have installed Office 2003 – 2007 which provides MSXML 5.0 for Microsoft Office Applications).
4. Microsoft XML, v 6.0 for latest versions of MS Office.
Then define http client
Dim xmlhttp As New MSXML2.XMLHTTP 'Dim xmlhttp As New MSXML2.XMLHTTP60 for Microsoft XML, v 6.0
VBA Intellisense will show you the right one when you start typing.
Make requests
Requests can be made using open and send methods. Open method syntax is as follows :
xmlhttp.Open Method, URL, async(true or false)
I’m using requestBin to test requests. Create a bin there and send requests to that URL to test requests.
A simple GET request would be :
Dim xmlhttp As New MSXML2.XMLHTTP60, myurl As String myurl = "http://requestb.in/15oxrjh1" //replace with your URL xmlhttp.Open "GET", myurl, False xmlhttp.Send MsgBox(xmlhttp.responseText)
Run this code, a message box is displayed with the response of the request.
Request headers
Request headers can be set using setRequestHeader method. Examples :
xmlhttp.setRequestHeader "Content-Type", "text/json" xmlhttp.setRequestHeader "Content-Type", "application/x-www-form-urlencoded" xmlhttp.setRequestHeader "User-Agent", "Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405" xmlhttp.setRequestHeader "Authorization", AuthCredentials
Simple POST request to send formdata
POST requests are used to send some data, data can be sent in Send
method. A simple POST request to send form data :
Public Sub httpclient() Dim xmlhttp As New MSXML2.XMLHTTP, myurl As String myurl = "http://requestb.in/15oxrjh1" xmlhttp.Open "POST", myurl, False xmlhttp.setRequestHeader "Content-Type", "application/x-www-form-urlencoded" xmlhttp.Send "name=codingislove&[email protected]" MsgBox (xmlhttp.responseText) End Sub
Basic Authentication in VBA
When we need to access web services with basic authentication, A username and password have to be sent with the Authorization header. Username and password should also be base64 encoded. Example :
user = "someusername" password = "somepassword" xmlhttp.setRequestHeader "Authorization", "Basic " + Base64Encode(user + ":" + password)
Here’s a paste of utility function that helps to encode string to Base64
Practical use cases
Practical use cases of http requests in VBA are unlimited. Some of them are pulling data from Yahoo finance API, weather API, pulling orders from Ecommerce store admin panel, uploading products, retrieving web form data to excel etc.
Read : Parse HTML in Excel VBA – Learn by parsing hacker news home page where I retrieve a web page using HTTP GET request and parse its HTML to get data from a web page.
Read How to build a simple weather app in Excel VBA where I make a HTTP Get request to weather API
Read JSON Api in Excel VBA where I call JSON Apis using HTTP GET and POST requests.
If you have and questions or feedback, comment below.
- Author
- Recent Posts
A CA- by education, self taught coder by passion, loves to explore new technologies and believes in learn by doing.
Содержание
- SQL запрос из Excel VBA
- Vba excel запрос к запросу
- Как сформировать SQL запросы в Excel?
- Vba excel запрос к запросу
- Эффективная работа в MS Office
- Экономия 5 минут в час за счет более продуктивной работы дает за год экономию в 4 рабочие недели
- Excel VBA. SQL-запросы в подключениях
SQL запрос из Excel VBA
SQL расшифровывается как Structured Query Language (структурированный язык запросов) и является языком, который используется для получения информации из баз данных (таких как Access , SQL Server from Microsoft , Oracle , Sybase , SAP и других). Вы также можете получать данные из интернета, текстовых файлов или других Excel или CSV файлов.
Итак, нам нужно соединение с базой данных (переменная varConn в макросе ниже) и SQL запрос (переменная varSQL ), чтобы автоматизировать получение данных из базы для отчета. В примере ниже есть SQL запрос , который получает данные с малой базы данных в Access.
Нажмите скачать базу данных Access . Для корректного соединения база данных должна быть в папке «Мои документы«. Файл Access будет выглядеть:
Давайте напишем свой макрос, который будет осуществлять SQL запрос .
Меню Сервис — Макрос — Редактор Visual Basic , вставьте новый модуль (меню Insert — Module ) и скопируйте туда текст макроса:
Нажимаем сохранить и возвращаемся к Excel . Выбираем в меню Вид — Макросы (Alt + F8) название нашего макроса » SQLQuery_1 «.
Источник
Vba excel запрос к запросу
Функция GetQueryRange предназначена для автоматизации загрузки данных с веб-страниц.
Например, нам надо из макроса Excel получить данные с нескольких однотипных страниц сайта .
Самый простой способ достичь этого — выполнять почти идентичные веб-запросы (где незначительно отличаться будет только URL страницы),
каждый раз анализируя данные, загруженные веб-запросом на лист Excel
Поскольку количество обращений ко мне, с просьбами сделать программу загрузки данных из интернета , с каждым днём растёт, я решил сделать для этих целей универсальную функцию:
Dim ra As Range: On Error Resume Next
Set ra = GetQueryRange( «http://ExcelVBA.ru/» , 6)
Debug. Print ra.Address ‘ переменная ra содержит ссылку на диапазон ячеек $A$1:$C$15,
‘ содержащий данные 6-й таблицы главной страницы сайта ExcelVBA.ru
Set ra = GetQueryRange( «http://excelvba.ru/sitemap.xml» )
Debug. Print ra.Address ‘ теперь переменная ra содержит ссылку на диапазон ячеек $A$1:$D$502,
‘ содержащий данные карты сайта ExcelVBA.ru
При запуске функции, создаётся скрытый лист с именем «tmpWQ» (если такой лист уже есть — используется существующий), и на этом листе выполняется веб-запрос к указанному сайту .
В качестве параметра можно задать номер интересующей нас таблицы сайта.
Номер (или номера) требуемых страниц можно узнать, записав макрорекордером код веб-запроса к нужной веб-странице.
Функция загружает данные на лист в формате HTML, — соответственно, мы можем получить доступ к гиперссылкам в результатах веб-запроса.
В большинстве случаев, эту функцию требуется использовать внутри цикла, формируя в теле цикла очередной URL, затем выполняя запрос к сформированному URL, получая необходимые данные с листа Excel, и т.д. для каждой из ссылок:
Dim ra As Range, cell As Range, n As Long : On Error Resume Next
URL$ = «http://www.planetaexcel.ru/forum.php?forum_id=129&page_forum=» & i
Set ra = GetQueryRange(URL$, 6) ‘ выполняем веб-запрос
‘ перебирая ячейки таблицы-результата, выводим список тем в окно Immediate
For Each cell In ra.Columns(2).Cells
If cell.Hyperlinks.Count Then
n = n + 1: Debug. Print «Тема №» & n, cell.Text
Debug. Print «Ссылка на тему: » & cell.Hyperlinks(1).Address: Debug. Print
Скриншот результата этого макроса:
Код функции GetQueryRange:
Function GetQueryRange( ByVal SearchLink$, Optional ByVal Tables$) As Range
On Error Resume Next : Err.Clear
Dim tmpSheet As Worksheet: Set tmpSheet = ThisWorkbook.Worksheets( «tmpWQ» )
If tmpSheet Is Nothing Then
Set tmpSheet = ThisWorkbook.Worksheets.Add
If tmpSheet Is Nothing Then
msg$ = «Не удалось добавить скрытый лист «tmpWQ» в файл программы»
MsgBox msg, vbCritical, «Невозможно выполнить запрос к сайту» : End
tmpSheet.Cells.Delete: DoEvents: Err.Clear
With tmpSheet.QueryTables.Add( «URL;» & SearchLink$, tmpSheet.Range( «A1» ))
If Len(Tables$) Then
.FillAdjacentFormulas = False : .PreserveFormatting = True
.RefreshOnFileOpen = False : DoEvents
.Refresh BackgroundQuery:= False : DoEvents
Источник
Как сформировать SQL запросы в Excel?
Представьте себе ситуацию, Вы получили целевую выборку из одной базы данных, но для полноты картины, как всегда, нужны дополнительные данные. Проблема может быть в том, что нужная информация хранится в другой базе данных и возможности создать на ней свою таблицу нет, подключиться используя link тоже нельзя, да и количество элементов, по которым нужно получить данные, несколько больше, чем допустимое на данном источнике. Вот и получается, что возможность написать SQL запрос и получить нужные данные есть, но написать придется не один запрос, а потом потратить время на объединение полученных данных.
Выйти из подобной ситуации поможет Excel.
Уверен, что ни для кого не секрет, что MS Excel имеет встроенный модуль VBA и надстройки, позволяющие подключаться к внешним источникам данных, то есть по сути является мощным инструментом для аналитики, а значит идеально подходит для решения подобных задач.
Для того чтобы обойти проблему, нам потребуется таблица с целевой выборкой, в которой содержатся идентификаторы, по которым можно достаточно корректно получить недостающую информацию (это может быть уникальный идентификатор, назовем его ID, или набор из данных, находящихся в разных столбцах), ПК с установленным MS Excel, и доступом к БД с недостающей информацией и, конечно, желание получить ту самую информацию.
Создаем в MS Excel книгу, на листе которой размещаем таблицу с идентификаторами, по которым будем в дальнейшем формировать запрос (если у нас есть уникальный идентификатор, для обеспечения максимальной скорости обработки таблицу лучше представить в виде одного столбца), сохраняем книгу в формате *.xlsm, после чего приступаем к созданию макроса.
Через меню «Разработчик» открываем встроенный VBA редактор и начинаем творить.
Sub job_sql() — Пусть наш макрос называется job_sql.
Пропишем переменные для подключения к БД, записи данных и запроса:
Опишем параметры подключения:
Объявим процедуру свойства, для присвоения значения:
Вот теперь можно приступать непосредственно к делу.
Как вы уже поняли конечное значение i=1000 здесь только для примера, а в реальности конечное значение соответствует количеству строк в Вашей таблице. В целях унификации можно использовать автоматический способ подсчета количества строк, например, вот такую конструкцию:
Тогда открытие цикла будет выглядеть так:
Как я уже говорил выше MS Excel является мощным инструментом для аналитики, и возможности Excel VBA не заканчиваются на простом переборе значений или комбинаций значений. При наличии известных Вам закономерностей можно ограничить объем выгружаемой из БД информации путем добавления в макрос простых условий, например:
Итак, мы определились с объемом и условиями выборки, организовали подключение к БД и готовы формировать запрос. Предположим, что нам нужно получить информацию о размере ежемесячного платежа [Ежемесячный платеж] из таблицы [payments].[refinans_credit], но только по тем случаям, когда размер ежемесячного платежа больше 0
Если значений для формирования запроса несколько, соответственно прописываем их в запросе:
В целях самоконтроля я обычно записываю сформированный макросом запрос, чтобы иметь возможность проверить его корректность и работоспособность, для этого добавим вот такую строчку:
Источник
Vba excel запрос к запросу
РАБОТА С ИНТЕРНЕТОМ ЧЕРЕЗ EXCEL VBA
Сегодня расскажу Вам как работать с интернетом через VBA Excel.
Давайте перейдем в редактор кода Excel, для этого нажмите сочетание горячих клавиш Alt + F11 или через вкладку «просмотр кода». В открывшемся окне редактора через вкладку insert -> procedure добавим простую процедуру “Test” и нажмем ок.
Теперь нужно подключить специальный внешний компонент т.к. excel по умолчанию не умеет работать с удаленными серверами. Сам компонент представляет из себя файл библиотеку с расширением .dll (dynamic link library).
Чтобы добавить библиотеку нажмите на вкладку Tools -> References
В открывшемся окне найдите библиотеку с названием (Microsoft XML, v6.0). При установке windows данная библиотека устанавливается на Ваш компьютер автоматически. Проставьте галочку и нажмите ок.
Теперь внутри любой процедуры нам будет доступен класс XMLHTTP60. Внутри нашей процедуры напишем следующее выражение.
Public Sub Test()
Dim xhr As New XMLHTTP60
Вообще данный класс XMLHTTP60 в большинстве случаев известен как XmlHttpRequest, используется сегодня в разработке современных веб приложений, которые модно называют web 2.0 или Ajax. Однако сам класс довольно древний, впервые был разработан как не странно компанией Microsoft и работал уже в Internet Explorer 5. В те времена основным форматом обмена дынными был XML, поэтому в название класса присутствует xml хотя ничего общего c xml на сегодняшний день не имеет.
Давайте просто получим разметку странички google. Пока не очень полезно, но для примера сойдет.
В коде напишем следующий текст.
Public Sub Test()
Dim xhr As New XMLHTTP60
xhr.Open «GET», «https://google.com», False
Если пример вернут ошибку 70, то вставьте другой адрес. Ошибка может быть связанна с механизмом безопасности google, потому просто проигнорируйте её и добавьте любой другой адрес, любимого сайта.
Запустим нажав на иконку run или клавишу F5
Как мы видим в окне immediate мы получили сырую разметку гугла.
Давайте откроем браузер. В области открытой страницы браузера нажмите правую кнопку мышки и выберете «Посмотреть код страницы»
В открывшейся вкладке мы увидим то, что получили через нашу среду разработки в Excel. Можете сравнить разметку, чтобы убедится в этом, но я не рекомендую терять время.
Теперь давайте пробежимся по строчкам нашего кода.
Сначала мы объявляем переменную xhr и стразу инициализируем её через ключевое слово new. Если не совсем понятно, как работает данная строчка, то посмотрите уроки по объектам в Excel ссылка в описание.
Далее мы обязательно должны указать основные параметры через метод Open
П ервый параметр — это глагол запросов протокола http. Официально их не много около 8 штук, могут быть и пользовательские. Но сейчас достаточно запомнить два часто употребляемых глагола GET и POST ( более подробно сюда )
Второй параметр – это адрес веб ресурса, тут всё ясно.
Третий параметр – это указатель на… хотя просто запомните, что сюда ставится false))
Потом мы просто вызываем удаленный сервер методом send
Наша программа на некоторое время зависает т.к. ожидает ответа от сервера, возможно вы не почувствуете этого. Но если Ваш интернет слабый Вы можете долго ожидать ответа до тех пор, пока не вылети таймаут.
Свойство responseText содержит все данные которые вернул сервер, обычно это текстовый формат.
Необходимо добавить, что если всё хорошо и удаленный сервер работает верно, то сервер вместе с текстом или телом ответа, возвращает код ответа.
Если всё хорошо, то код ответа начинается с цифры 200 (201, 202, 203, 204, 205, 206) (код ответов можно посмотреть тут (http://lib.ru/WEBMASTER/rfc2068/))
О днако бывает такое, что удаленный ресурс отработал неверно. Скорее всего сервер в таком случае вернет ошибку с кодом ответа начинающуюся с цифры 400, например, знаменитую ошибку 404 (страница не найдена) или 500 (ошибка сервера).
Чтобы проверить, что сервер отработал верно и вернул нам то, что надо проверим код ошибки.
Изменим на следующий блок кода.
Public Sub Test()
Dim xhr As New XMLHTTP60
xhr.Open «GET», «https://google.com», False
If xhr.Status = 200 Then ‘If xhr.Status >= 200 And xhr.Status
В комментариях я указал более надежный способ проверки положительного ответа от сервера всё что выше 200, но ниже 300 считается ок.
Очистите окно Immediate
Нажмите F5 или иконку старт.
Мы видим, что всё тоже самое ответ пришел верный.
Чтобы проверить блок else давайте в адрес добавим что-то неправильное, например, восклицательные знаки https://google.com/ !!
Нажмите F5 или иконку старт.
В окне Immediate уже пришла страничка с ошибкой.
Таким образом можно обращается к серверу через VBA Excel
Я рекомендую нажать клавишу F8 чтобы по шагам прогрессировать программу и посмотреть каждый шаг.
Источник
Эффективная работа в MS Office
Экономия 5 минут в час за счет более продуктивной работы дает за год экономию в 4 рабочие недели
Excel VBA. SQL-запросы в подключениях
UPDATE 21.10.15 Добавил «обратный» макрос — VBA в SQL и макрос для доступа к строке запроса SQL
Некоторое время назад я прошел несколько курсов по SQL. И мне было очень интересно — какую часть из мощного инструмента под названием T-SQL можно применять без использования SQL-Server (не дают мне сервачек под мои нужды, хнык-хнык).
Итак… Начнем с простого — подключение через Query Table в VBA. Можно записать через макрорекордер — для этого нужно создать подключение через Microsoft Query.
Выбираем Excel Files, указываем путь к файлу (пытаясь при этом не ругать разработчиков за интерфейс из 90х годов), фильтруем как-угодно поля. Нам сейчас это не важно — главное получить код, который дальше можно будет корректировать.
Должно получится что-то вроде этого:
Строчка .CommandText = «SELECT…» — отвечает за SQL запрос. Если хотя бы немного почитать поисковую выдачу google по запросу QueryTable можно упростить код до следующего:
Теперь начинаем копаться глубже — какого уровня запросы можно строить из VBA. Самые-самые базовые, основные конструкции — все работает, все ок.
Заполнение нового столбца одинаковым значением
Переименование столбцов
Фильтрация записей
Сортировка
Агрегация записей
Дату можно впрямую через конструкцию
Но я люблю отталкиваться от текущей даты. За пару текущая дата-время отвечает функция SYSDATETIME() и она может вернуть в том числе текущий день. Для этого нужна еще одна функция — CONVERT(type,value)
С функцией DATEFROMPARTS строка запроса в Excel почему-то не дружит, поэтому придется использовать костыли функцию DATEADD:
Эта строчка в любой день октября 2015 вернет значение — 30.11.15 23:59
А теперь — немного best practice!
Объединение + Агрегация + Join + Подзапросы. И самое интересное — подключение к нескольким источникам:
Одна проблема — если осуществлять такого вида запрос для соединения нескольких Excel-файлов, он будет выполняться достаточно медленно. У меня вышло порядка 2 минут. Но не стоит думать что это бесполезно — если подобные запросы выполнять при подключении к SQL-серверу, то время обработки будет 1-2 секунды (само собой, все зависит от сложности запроса, базы, и прочие прочие факторы).
Бонусы
Формировать более-менее сложный запрос SQL вручную в VBA мягко говоря неудобно. Поэтому я написал мини-макрос, который берет информацию из буфера обмена, и возвращает туда строчки для вставки в VBE.
Сами запросы просто и удобно создавать, например, используя Notepad++. Создали многострочный запрос SQL, копируете его в буфер обмена, запускаете макрос и вуаля — в буфере обмена строчки кода, готовые для вставки в ваши макросы. При желании вы можете настроить название переменной и количество табуляций.
И еще один небольшой бонус. Если у вас есть отчет по менеджерам/руководителям, построенный на запросах, то вам наверняка потребуется получать доступ к строке запроса через VBA. Сделать это можно через замечательную команду .CommandText — работает на чтение и запись. Мне для формирования отчета на 25 человек очень пригодился.
Источник
To complete the response of the other users:
For this I have created an «WinHttp.WinHttpRequest.5.1» object.
Send a post request with some data from Excel using VBA:
Dim LoginRequest As Object
Set LoginRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
LoginRequest.Open "POST", "http://...", False
LoginRequest.setRequestHeader "Content-type", "application/x-www-form-urlencoded"
LoginRequest.send ("key1=value1&key2=value2")
Send a get request with token authentication from Excel using VBA:
Dim TCRequestItem As Object
Set TCRequestItem = CreateObject("WinHttp.WinHttpRequest.5.1")
TCRequestItem.Open "GET", "http://...", False
TCRequestItem.setRequestHeader "Content-Type", "application/xml"
TCRequestItem.setRequestHeader "Accept", "application/xml"
TCRequestItem.setRequestHeader "Authorization", "Bearer " & token
TCRequestItem.send
Excel is probably one of the most used tools in this world, so the demand for integrations with extremely complex spreadsheets is a recurring scenario. APIs allow easy access to information in systems, which is becoming more and more standard in the market, with that in mind, some demands of connecting to systems via API in Excel are necessary and very useful, so I decided to share a little how I created this integration. Let’s learn how to query Rest APIs using VBA and convert the result to JSON for use in the spreadsheet.
This article hopes you will know the basics of Excel and VBA, as well as what is an API and how it works . Our goal will be to consult a Public Pokemon API and list the result in the tab results
.
Creating a blank worksheet
First let’s create a blank sheet with macro enabled, inside it I’ll create a tab called results
.
Creating the macro to query the API
by shortcut alt + f11
let’s open the Excel macro editor, and create a module called list pokemons
.
Importing the VBA-JSON library
As the API we’re going to query returns a JSON as an answer we will need to import the library VBA JSON , it will take care of all the boring work of translating the JSON and returning as an array and object. Installation is very simple, just download the latest version here and in the macro editor go to File > Import File > JsonConverter.bas
.
Enabling Microsoft Scripting Runtime
We also need to enable Microsoft Scripting Runtime, to do this just browse Tools > References
and search and enable in the list the Microsoft Scripting Runtime
.
Creating the VBA macro to query the REST API
Below is the complete code for our request, it might sound scary, but don’t worry, I’ll explain what each part is doing:
Sub listPokemons() Dim json As String Dim jsonObject As Object, item As Object Dim i As Long Dim ws As Worksheet Dim objHTTP As Object 'We selected our results sheet Set ws = Worksheets("results") 'We create our request object and send Set objHTTP = CreateObject("WinHttp.WinHttpRequest.5.1") URL = "https://pokeapi.co/api/v2/pokemon" objHTTP.Open "GET", URL, False objHTTP.Send strResult = objHTTP.responseText json = strResult Set objectJson = JsonConverter.ParseJson(json) 'We create the header cells ws.Cells(1, 1) = "name" ws.Cells(1, 2) = "link" 'We loop the results property of the API response i = 2 'We will start the counter on line 2 For Each pokemon InJsonObject("results") ws.Cells(i, 1) = pokemon("name") ws.Cells(i, 2) = pokemon("url") i = i + 1 next End Sub
First we define all the variables that we will use in our scripts, including the VBA JSON library import that we previously imported into our project.
Dim json As String Dim jsonObject As Object, item As Object Dim i As Long Dim ws As Worksheet Dim xmlhttp As Object Set xmlhttp = CreateObject("MSXML2.serverXMLHTTP") Dim objHTTP As Object
Then we select the spreadsheet we want to display the results of the API query, in our case Worksheets("results")
and then we create an object that will allow us to make the request to the API. https://pokeapi.co/api/v2/pokemon
. We’ll take the answer and put it in the variable json
, for now it is nothing more than a text.
'We selected our results sheet Set ws = Worksheets("results") 'We create our request object and send Set objHTTP = CreateObject("WinHttp.WinHttpRequest.5.1") URL = "https://pokeapi.co/api/v2/pokemon" objHTTP.Open "GET", URL, False objHTTP.Send strResult = objHTTP.responseText json = strResult
Here where the magic happens, the function parsejson
from the VBA JSON library converts the text of our variable json
for an accessible object in our script. We are now able to access all properties programmatically in our code.
Set objectJson = JsonConverter.ParseJson(json)
Now that we have the result of our API accessible, we create in the first row of the spreadsheet the header containing the columns Name and link .
'We create the header cells ws.Cells(1, 1) = "name" ws.Cells(1, 2) = "link"
Now before analyzing the script we need to understand the result of the API. If you open the link https://pokeapi.co/api/v2/pokemon in your browser you will see the following result:
{ "count": 964, "next": "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20", "previous": null, "results": [ { "name": "bulbasaur", "url": "https://pokeapi.co/api/v2/pokemon/1/" }, { "name": "ivysaur", "url": "https://pokeapi.co/api/v2/pokemon/2/" }, { "name": "venusaur", "url": "https://pokeapi.co/api/v2/pokemon/3/" }, { "name": "charmander", "url": "https://pokeapi.co/api/v2/pokemon/4/" }, { "name": "charmeleon", "url": "https://pokeapi.co/api/v2/pokemon/5/" }, { "name": "charizard", "url": "https://pokeapi.co/api/v2/pokemon/6/" }, { "name": "squirtle", "url": "https://pokeapi.co/api/v2/pokemon/7/" }, { "name": "wartortle", "url": "https://pokeapi.co/api/v2/pokemon/8/" }, { "name": "blastoise", "url": "https://pokeapi.co/api/v2/pokemon/9/" }, { "name": "caterpie", "url": "https://pokeapi.co/api/v2/pokemon/10/" }, { "name": "metapod", "url": "https://pokeapi.co/api/v2/pokemon/11/" }, { "name": "butterfree", "url": "https://pokeapi.co/api/v2/pokemon/12/" }, { "name": "weedle", "url": "https://pokeapi.co/api/v2/pokemon/13/" }, { "name": "kakuna", "url": "https://pokeapi.co/api/v2/pokemon/14/" }, { "name": "beedrill", "url": "https://pokeapi.co/api/v2/pokemon/15/" }, { "name": "pidgey", "url": "https://pokeapi.co/api/v2/pokemon/16/" }, { "name": "pidgeotto", "url": "https://pokeapi.co/api/v2/pokemon/17/" }, { "name": "pidgeot", "url": "https://pokeapi.co/api/v2/pokemon/18/" }, { "name": "rattata", "url": "https://pokeapi.co/api/v2/pokemon/19/" }, { "name": "raticate", "url": "https://pokeapi.co/api/v2/pokemon/20/" } ] }
We are interested in the property. results
, an array containing a list of pokemons with their names and links to more details. We will access this matrix at jsonobject("results")
and we will loop to display each pokemon result in a new row of our table.
'We loop the results property of the API response i = 2 'We will start the counter on line 2 For Each pokemon InJsonObject("results") ws.Cells(i, 1) = pokemon("name") ws.Cells(i, 2) = pokemon("url") i = i + 1 next
If everything goes as expected, by pressing f5
to run our macro, in your spreadsheet you should see the following result:
Conclusion
This was a very simple example of a query, with the object of HT TP it is possible to perform all types of requests, GET, POST, UPDATE, … The interesting thing is to understand how the request is made and how you can display the result, thanks to the VBA JSON library, which already drastically reduces the work required. Now you just need to adapt this flow and script to your needs.
3.7
6
votos
Nota do Artigo
UPDATE 21.10.15 Добавил «обратный» макрос — VBA в SQL и макрос для доступа к строке запроса SQL
Некоторое время назад я прошел несколько курсов по SQL. И мне было очень интересно — какую часть из мощного инструмента под названием T-SQL можно применять без использования SQL-Server (не дают мне сервачек под мои нужды, хнык-хнык).
Итак… Начнем с простого — подключение через Query Table в VBA. Можно записать через макрорекордер — для этого нужно создать подключение через Microsoft Query.
Выбираем Excel Files, указываем путь к файлу (пытаясь при этом не ругать разработчиков за интерфейс из 90х годов), фильтруем как-угодно поля. Нам сейчас это не важно — главное получить код, который дальше можно будет корректировать.
Должно получится что-то вроде этого:
Sub Макрос1() With ActiveSheet.ListObjects.Add(SourceType:=0, Source:=Array(Array( _ "ODBC;DSN=Excel Files;DBQ=D:DropboxExcelтест excel_SQL-2015.xlsx;DefaultDir=D:DropboxExcel;DriverId=1046;MaxBufferSize=2048;Page" _ ), Array("Timeout=5;")), Destination:=Range("$A$1")).QueryTable .CommandType = 0 .CommandText = Array( _ "SELECT Продажи.F2, Продажи.F3" & Chr(13) & "FROM `D:DropboxExcelтест excel_SQL-2015.xlsx`.Продажи Продажи" _ ) .RowNumbers = False .FillAdjacentFormulas = False .PreserveFormatting = True .RefreshOnFileOpen = False .BackgroundQuery = True .RefreshStyle = xlInsertDeleteCells .SavePassword = False .SaveData = True .AdjustColumnWidth = True .RefreshPeriod = 0 .PreserveColumnInfo = True .ListObject.DisplayName = "Таблица_Запрос_из_Excel_Files" .Refresh BackgroundQuery:=False End With End Sub
Строчка .CommandText = «SELECT…» — отвечает за SQL запрос. Если хотя бы немного почитать поисковую выдачу google по запросу QueryTable можно упростить код до следующего:
Sub CopyFromRecordset_To_Range() DBPath = "C:InputData.xlsx" sconnect = "Provider=MSDASQL.1;DSN=Excel Files;DBQ=" & DBPath & ";HDR=Yes';" Conn.Open sconnect sSQLSting = "SELECT * FROM [Sheet1$]" rs.Open sSQLSting, Conn Set QT1 = ActiveSheet.QueryTables.Add(rs, Range("A1")) QT1.Refresh rs.Close Conn.Close End Sub
Теперь начинаем копаться глубже — какого уровня запросы можно строить из VBA. Самые-самые базовые, основные конструкции — все работает, все ок.
Заполнение нового столбца одинаковым значением
SELECT 'YTikhonov', * FROM [Sheet1$]
Переименование столбцов
SELECT [Advertiser] AS 'Рекламодатель', [Quantity] AS 'Количество' FROM [Sheet1$]
Фильтрация записей
SELECT * FROM [Sheet1$] WHERE [Year] = 2014
Сортировка
SELECT * FROM [Sheet1$] ORDER BY [Advertiser] DESC
Агрегация записей
SELECT [Advertiser], Sum([Cost]) FROM [Sheet1$] GROUP BY [Advertiser]
Работа с датой
Дату можно впрямую через конструкцию
[SomeDateField] = {ts '2015-01-01 00:00:00'}
Но я люблю отталкиваться от текущей даты. За пару текущая дата-время отвечает функция SYSDATETIME() и она может вернуть в том числе текущий день. Для этого нужна еще одна функция — CONVERT(type,value)
SELECT CONVERT(date,SYSDATETIME())
С функцией DATEFROMPARTS строка запроса в Excel почему-то не дружит, поэтому придется использовать костыли функцию DATEADD:
DATEADD(minute, 59, DATEADD(hour, 23, DATEADD(month, MONTH(SYSDATETIME())+1, DATEADD(year, YEAR(SYSDATETIME()) - 1900, 0))))-1
Эта строчка в любой день октября 2015 вернет значение — 30.11.15 23:59
А теперь — немного best practice!
Объединение + Агрегация + Join + Подзапросы. И самое интересное — подключение к нескольким источникам:
SELECT [Year], O.Numbers, SCost, SVolume, SQuantity FROM ( SELECT [Year], Month, SUM([Cost RUB]) AS SCost, SUM(Volume) AS SVolume, SUM(Quantity) AS SQuantity FROM ( SELECT Advertiser, 2013 as [Year], Month, [Cost RUB], Quantity, Volume FROM [N:GKRadioМаркетингСлужебный2013.xlsb].[Мониторинг$] UNION SELECT Advertiser, 2014 as [Year], Month, [Cost RUB], Quantity, Volume FROM [N:GKRadioМаркетингСлужебный2014.xlsb].[Мониторинг$] UNION SELECT Advertiser, 2015 as [Year], Month, [Cost RUB], Quantity, Volume FROM [N:GKRadioМаркетингСлужебный2015.xlsb].[Мониторинг$] ) WHERE [Advertiser] = 'METRO GROUP' GROUP BY [Year], Month ) as T INNER JOIN [C:testMonth.xlsb].[Test$] AS O ON T.[Month] = O.[Month]
Одна проблема — если осуществлять такого вида запрос для соединения нескольких Excel-файлов, он будет выполняться достаточно медленно. У меня вышло порядка 2 минут. Но не стоит думать что это бесполезно — если подобные запросы выполнять при подключении к SQL-серверу, то время обработки будет 1-2 секунды (само собой, все зависит от сложности запроса, базы, и прочие прочие факторы).
Бонусы
Формировать более-менее сложный запрос SQL вручную в VBA мягко говоря неудобно. Поэтому я написал мини-макрос, который берет информацию из буфера обмена, и возвращает туда строчки для вставки в VBE.
'работа с буфером обмена http://excelvba.ru/code/clipboard Private Function ClipboardText() ' чтение из буфера обмена With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}") .GetFromClipboard ClipboardText = .GetText End With End Function Private Sub SetClipboardText(ByVal txt$) ' запись в буфер обмена With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}") .SetText txt$ .PutInClipboard End With End Sub Public Sub SQL_String_To_VBA() Dim sInput As String, sOut As String Dim ArrInput, i As Integer Dim cIdent As Integer: cIdent = 1 'Count of tabs Dim sVar As String: sVar = "strSQL" 'Name of variable sInput = ClipboardText() ArrInput = Split(sInput, Chr(13)) For i = LBound(ArrInput) To UBound(ArrInput) sOut = sOut & sVar & " = " & sVar & " & " & Chr(34) sOut = sOut & String(cIdent, Chr(9)) sOut = sOut & Replace(ArrInput(i), Chr(10), "") sOut = sOut & Chr(34) & "& chr(10)" & Chr(10) Next i SetClipboardText (sOut) End Sub Public Sub VBA_String_To_SQL() Dim sInput As String, sOut As String Dim ArrInput, i As Integer, sTemp sInput = ClipboardText() ArrInput = Split(sInput, Chr(10)) For i = LBound(ArrInput) To UBound(ArrInput) sTemp = Replace(ArrInput(i), "& chr(10)", "") If Right(sTemp, 1) = " " Then sTemp = Left(sTemp, Len(sTemp) - 1) If Right(sTemp, 1) = Chr(34) Then sTemp = Left(sTemp, Len(sTemp) - 1) If Len(sTemp) > 0 Then sTemp = Right(sTemp, Len(sTemp) - InStr(1, sTemp, Chr(34))) sOut = sOut & Chr(10) & sTemp End If Next i SetClipboardText (sOut) End Sub
Сами запросы просто и удобно создавать, например, используя Notepad++. Создали многострочный запрос SQL, копируете его в буфер обмена, запускаете макрос и вуаля — в буфере обмена строчки кода, готовые для вставки в ваши макросы. При желании вы можете настроить название переменной и количество табуляций.
И еще один небольшой бонус. Если у вас есть отчет по менеджерам/руководителям, построенный на запросах, то вам наверняка потребуется получать доступ к строке запроса через VBA. Сделать это можно через замечательную команду .CommandText — работает на чтение и запись. Мне для формирования отчета на 25 человек очень пригодился.
Public Sub ReplaceCommandText() Dim con As WorkbookConnection Dim sTemp As String For Each con In ActiveWorkbook.Connections sTemp = con.ODBCConnection.CommandText con.ODBCConnection.CommandText = sTemp con.Refresh Next con End Sub
PS Ссылка с ответом на вопрос — как вставить данные из Excel в SQL
https://www.simple-talk.com/sql/t-sql-programming/questions-about-using-tsql-to-import-excel-data-you-were-too-shy-to-ask/
Приятного использования!
Уровень сложности
Средний
Время на прочтение
9 мин
Количество просмотров 14K
Работая в IoT-сфере и плотно взаимодействуя с одним из основных элементов данной концепции технологий – сетевым сервером, столкнулся вот с какой проблемой (задачей): необходимо отправлять много запросов для работы с умными устройствами на сетевой сервер. На сервере был реализован REST API с оболочкой Swagger UI, где из графической оболочки можно было отправлять только разовые запросы. Анализ сторонних клиентов, типа Postman или Insomnia показал, что простого визуального способа поместить в скрипт массив из необходимого перечня идентификаторов устройств (или любых других элементов сервера), для обращения к ним – не нашлось.
Так как большая часть работы с выгрузками и данными была в Excel, то решено было вспомнить навыки, полученные на учебе в университете, и написать скрипт на VBA, который бы мою задачку решал.
Необходимо было:
-
получать информацию по устройствам с различными параметрами фильтрации (GET);
-
применять изменения в конфигурации по устройствам: имя, профиль устройства, сетевые лицензии и пр. (PUT);
-
отправлять данные для конфигурации и взаимодействия с устройствами (POST).
И сегодня я расскажу вам про то, как с помощью Excel, пары формул и самописных функций на VBA можно реализовать алгоритм, отправляющий любое необходимое количество REST-API запросов с использованием авторизации Bearer Token.
Данная статья будет полезная тем, кто воспользуется данным решением под Windows, но еще больше она будет полезна тем людям, которые хотят использовать данное решение на MacOS (с Excel x64) . Как вы уже догадались, ниже будут рассмотрены два варианта реализации под разные системы, так как с MacOS есть нюанс.
Часть 1. Реализация решения под Windows
GET
Начнем с самого простого: GET – запросов. В данном примере необходимо получить ответ (информацию) от сервера по заданному списку устройств.
Для реализации GET – запросов нам дано:
1) Ссылка, в которой указываются параметры запроса.
https://dx-api.thingpark.io/core/latest/api/devices?deviceEUI=
2) Заголовки запроса + Токен авторизации (Bearer Token)
—header ‘Accept: application/json’ —header ‘Authorization: Bearer
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJTVUJTQ1JJQkVSOjY3MDAiXSwiZXhwIjozNzc0MTY0MzE4LCJqdGkiOiI5OTNiOTk1Ny03NGY1LTQ5MDgtYjg4Ni0xYjk5NTVkZDQwZTEiLCJjbGllbnRfaWQiOiJkZXYxLWFwaS9lcnRoLnRlY2guZGVzayt2bGFkaXNsYXYuZ2F0Y2Vua29AZ21haWwuY29tIn0.dqybsMdVXXpQV8_ykufNZoQpSPZrVA67uieOJan-qs8W7rAImyy0552buniHXPWy6ilvdwJKPCdIKE__LghP6A
3) Параметр, указываемый в ссылке (в данном примере это идентификаторы устройств – DevEUI):
1ABCDEFF00AABBCC
0016ACC4DCF15A23
D88039FFFE954DF4
0000000000001103
0000000000001104
Имея такие данные на входе, делаем в Excel лист-шаблон, который заполняем в соответствии с тем, что имеем:
-
столбец А уходит вот значения параметров
-
столбец F уходит под ссылку-родителя
-
столбец H уходит под заголовки, где в ячейке H1 единоразово для текущего листа указывается токен:
=СЦЕП("--header 'Accept: application/json' --header 'Authorization: Bearer ";$H$1;"'")
-
столбец I уходит под URL (ссылки-дети, на основе ссылки-родителя)
=СЦЕПИТЬ($F$1;A2)
-
столбец J уходит под результат (ответ от сервера)
Далее, нам необходимо реализовать подпрограмму(макрос) отправки GET-запросов. Состоит она из четырех частей:
-
цикла, который считает количество строк для работы по листу, пробегая по столбцу А с 2 по первую пустую ячейку, чтобы у цикла был конец.
-
функции, для работы с REST API (используется стандартная, библиотека Msxml2.XMLHTTP.6.0, встроенная в Windows., поэтому сложностей с реализацией не возникает. Для MacOS есть альтернатива)
-
временной задержки, в случае если нужно отправлять запросы не сразу, после получения ответа, а задав время ожидания
-
таймером, который показывает время выполнения всего макроса после завершения
Код:
Sub GET_Request() Dim i As Integer Dim j As Integer Dim objHTTP As Object Dim Json As String Dim result As String Dim URL As String Dim Token As String a = Timer i = 1 Do While Not IsEmpty(Cells(i, 1)) i = i + 1 Loop i = i - 1 'MsgBox i For j = 2 To i Json = Range("D" & j) URL = Range("I" & j) Token = Range("H1") Set objHTTP = CreateObject("Msxml2.XMLHTTP.6.0") objHTTP.Open "GET", URL, False objHTTP.setRequestHeader "Content-type", "application/json" objHTTP.setRequestHeader "Accept", "application/json" objHTTP.setRequestHeader "Authorization", "Bearer " + Token objHTTP.Send (Json) result = objHTTP.responseText Range("J" & j).Value = result Set objHTTP = Nothing 'Application.Wait (Now + TimeValue("0:00:01")) Next j MsgBox Timer - a End Sub
Привязываем подпрограмму к кнопкам для удобства и выполним скрипт. Получается:
Таким образом, скрипт проходит по столбцу I, забирая из значения каждой ячейки URL, для тех строк, где в столбце А есть значения (которые и подставляются в URL). Для удобства также сделаны кнопки очистки полей и подсветка запросов условным форматированием, в случае успешного ответа на запрос.
PUT
Чуть-чуть усложним задачу и перейдем к PUT-запросам. В данном примере необходимо изменить профиль устройства, чтобы сервер по-другому с ним взаимодействовал.
К исходным данным для GET – запроса добавляется тело запроса с ключем-значением (п4). Итого дано:
1) Ссылка, в которой указываются параметры запроса.
https://dx-api.thingpark.io/core/latest/api/devices/
2) Заголовки запроса + Токен авторизации (Bearer Token)
—header ‘Content-Type: application/json’ —header ‘Accept: application/json’ —header ‘Authorization: Bearer
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJTVUJTQ1JJQkVSOjY3MDAiXSwiZXhwIjozNzc0MTY0MzE4LCJqdGkiOiI5OTNiOTk1Ny03NGY1LTQ5MDgtYjg4Ni0xYjk5NTVkZDQwZTEiLCJjbGllbnRfaWQiOiJkZXYxLWFwaS9lcnRoLnRlY2guZGVzayt2bGFkaXNsYXYuZ2F0Y2Vua29AZ21haWwuY29tIn0.dqybsMdVXXpQV8_ykufNZoQpSPZrVA67uieOJan-qs8W7rAImyy0552buniHXPWy6ilvdwJKPCdIKE__LghP6A
3) Параметр, указываемый в ссылке (в данном примере это внутренние идентификаторы устройств – hRef):
17272
18199
17242
17245
17248
4) Тело запроса, с ключом и значением:
{«deviceProfileId»:»LORA/GenericA.1.0.3a_ETSI»}
Немного дополняем новый PUT-лист в Excel по сравнению с GET (остальное без изменений):
-
новый столбец B теперь отвечает за ключ deviceProfileId (ячейка B1), а все значения ниже за его возможные значения)
-
столбец D отвечает за формирование итогового тела сообщения в формате JSON.
=СЦЕПИТЬ("'{""";$B$1;""":""";B2;"""";"}'")
Немного поменяем макрос и вынесем его в отдельную подпрограмму:
Код:
Sub PUT_Request()
Dim i As Integer
Dim j As Integer
Dim objHTTP As Object
Dim Json As String
Dim result As String
Dim URL As String
Dim Token As String
a = Timer
i = 1
Do While Not IsEmpty(Cells(i, 1))
i = i + 1
Loop
i = i - 1
'MsgBox i
For j = 2 To i
Json = Range("D" & j)
URL = Range("I" & j)
Token = Range("H1")
Set objHTTP = CreateObject("Msxml2.XMLHTTP.6.0")
objHTTP.Open "PUT", URL, False
objHTTP.setRequestHeader "Content-type", "application/json"
objHTTP.setRequestHeader "Accept", "application/json"
objHTTP.setRequestHeader "Authorization", "Bearer " + Token
objHTTP.Send (Json)
result = objHTTP.responseText
Range("J" & j).Value = result
Set objHTTP = Nothing
'Application.Wait (Now + TimeValue("0:00:01"))
Next j
MsgBox Timer - a
End Sub
Привяжем макрос к кнопке и выполним.
Логика абсолютно аналогична GET запросу.
POST
Для POST запросов все аналогично PUT. Только немного меняется код в части типа запроса. В данном примере на устройство отправляется команда-конфигурация с указанием тела посылки (payload_hex) и порта (fport) для конкретного устройства.
Код:
Sub PUT_Request()
Dim i As Integer
Dim j As Integer
Dim objHTTP As Object
Dim Json As String
Dim result As String
Dim URL As String
Dim Token As String
a = Timer
i = 1
Do While Not IsEmpty(Cells(i, 1))
i = i + 1
Loop
i = i - 1
'MsgBox i
For j = 2 To i
Json = Range("D" & j)
URL = Range("I" & j)
Token = Range("H1")
Set objHTTP = CreateObject("Msxml2.XMLHTTP.6.0")
objHTTP.Open "PUT", URL, False
objHTTP.setRequestHeader "Content-type", "application/json"
objHTTP.setRequestHeader "Accept", "application/json"
objHTTP.setRequestHeader "Authorization", "Bearer " + Token
objHTTP.Send (Json)
result = objHTTP.responseText
Range("J" & j).Value = result
Set objHTTP = Nothing
'Application.Wait (Now + TimeValue("0:00:01"))
Next j
MsgBox Timer - a
End Sub
Получившаяся таблица выглядит следующим образом:
На этом часть для Windows заканчивается. Здесь все оказалось довольно просто: стандартная библиотека, простенький алгоритм перебора значений в цикле.
Часть 2. Реализация решения под MacOS и Excel 64-bit
В виду того, что работал я на двух машинах под управлением разных ОС, хотелось, чтобы решение было универсальным. В итоге, собрав по крупицам информацию по интернет-форумам с данной тематикой у меня вышло следующее решение. Принцип работы его остается схожим, а изменения были внесены в часть, где использовалась стандартная библиотека WindowsMsxml2.XMLHTTP.6.0, которой в MacOS не было по понятным причинам.
Чтобы обойти данное ограничение, был выбран единственный рабочий подход через cUrl, exec и функции. Данное решение точно работает под версией MacOS 10.14 и Excel 16.51. Функция ниже, в том или ином виде, встречается на различных форумах, однако на текущих версиях софта – не работает. В итоге, после небольших правок получили рабочий вариант:
Была отлажена функция вызова ExecShell:
Код:
Option Explicit
Private Declare PtrSafe Function popen Lib "/usr/lib/libc.dylib" (ByVal Command As String, ByVal Mode As String) As LongPtr
Private Declare PtrSafe Function pclose Lib "/usr/lib/libc.dylib" (ByVal file As LongPtr) As Long
Private Declare PtrSafe Function fread Lib "/usr/lib/libc.dylib" (ByVal outStr As String, ByVal size As LongPtr, ByVal items As LongPtr, ByVal stream As LongPtr) As Long
Private Declare PtrSafe Function feof Lib "/usr/lib/libc.dylib" (ByVal file As LongPtr) As LongPtr
Function execShell(Command As String, Optional ByRef exitCode As Long) As String
Dim file As LongPtr
file = popen(Command, "r")
If file = 0 Then
Exit Function
End If
While feof(file) = 0
Dim chunk As String
Dim read As Long
chunk = Space(500)
read = fread(chunk, 1, Len(chunk) - 1, file)
If read > 0 Then
chunk = Left$(chunk, read)
execShell = execShell & chunk
End If
Wend
exitCode = pclose(file)
End Function
И написаны отдельные функции для работы с различным методами GET / PUT / POST, которые на входе принимают URL и параметры):
Код:
Function HTTPGet(sUrl As String, sQuery As String) As String
Dim sCmd As String
Dim sResult As String
Dim lExitCode As Long
sCmd = "curl -X GET " & sQuery & "" & " " & sUrl
sResult = execShell(sCmd, lExitCode)
HTTPGet = sResult
End Function
Function HTTPPost(sUrl As String, sQuery1 As String, sQuery2 As String) As String
Dim sCmd As String
Dim sResult As String
Dim lExitCode As Long
sCmd = "curl -X POST " & sQuery1 & "" & " -d " & sQuery2 & "" & " " & sUrl
sResult = execShell(sCmd, lExitCode)
HTTPPost = sResult
End Function
Function HTTPPut(sUrl As String, sQuery1 As String, sQuery2 As String) As String
Dim sCmd As String
Dim sResult As String
Dim lExitCode As Long
sCmd = "curl -X PUT " & sQuery1 & "" & " -d " & sQuery2 & "" & " " & sUrl
sResult = execShell(sCmd, lExitCode)
HTTPPut = sResult
End Function
Так как мы заменяем библиотеку Msxml2.XMLHTTP.6.0 – поменялась реализация макросов в этой части: мы заменили Msxml2 на написанные выше функции и получили следующее:
Код:
'GET-запросы
Sub SendGETRequest()
Dim i As Integer
Dim j As Integer
Dim result As String
Dim URL As String
Dim Auth As String
a = Timer
'Подсчет заполненных ячеек первого столбца
i = 1
Do While Not IsEmpty(Cells(i, 1))
i = i + 1
Loop
i = i - 1
'Цикл, который отправляет запрос от 2 до последнего элемента
For j = 2 To i
URL = Range("I" & j)
Auth = Range("H" & j)
result = HTTPGet(URL, Auth)
Range("J" & j).Value = result
'Application.Wait (Now + TimeValue("0:00:01"))
Next j
MsgBox Timer - a
End Sub
'PUT-запросы
Sub SendPUTRequest()
Dim i As Integer
Dim j As Integer
Dim result As String
Dim URL As String
Dim Auth As String
Dim Message As String
a = Timer
'Подсчет заполненных ячеек первого столбца
i = 1
Do While Not IsEmpty(Cells(i, 1))
i = i + 1
Loop
i = i - 1
'Цикл, который отправляет запрос от 2 до последнего элемента
For j = 2 To i
Message = Range("D" & j)
URL = Range("I" & j)
Auth = Range("H" & j)
result = HTTPPut(URL, Auth, Message)
Range("J" & j).Value = result
'Application.Wait (Now + TimeValue("0:00:01"))
Next j
MsgBox Timer - a
End Sub
'POST-запросы
Sub SendPOSTRequest()
Dim i As Integer
Dim j As Integer
Dim result As String
Dim URL As String
Dim Auth As String
Dim Message As String
a = Timer
'Подсчет заполненных ячеек первого столбца
i = 1
Do While Not IsEmpty(Cells(i, 1))
i = i + 1
Loop
i = i - 1
'Цикл, который отправляет запрос от 2 до последнего элемента
For j = 2 To i
Message = Range("D" & j)
URL = Range("I" & j)
Auth = Range("H" & j)
result = HTTPPost(URL, Auth, Message)
Range("J" & j).Value = result
'Application.Wait (Now + TimeValue("0:00:01"))
Next j
MsgBox Timer – a
Итог
В итоге, у меня получилось аналогичное windows по работе и функционалу решение для MacOS c использованием Excel 64-bit.
На просторах интернета я не нашел какого-то сборного и единого описания, только фрагменты кода и подходов, которые в большинстве случаев не работали полностью или частично. Поэтому решил объединить все в рабочее решение и выложить на хабр для истории.
На текущий момент я все еще не встретил иного решения, которое бы позволяло в пару кликов копипастить тысячи идентификаторов и параметров из excel и массово их отправлять на сервер. Надеюсь, кому-то пригодится
Если такие сторонние решения есть, а я не в курсе, и все можно было сделать проще, быстрее и изящнее – делитесь опытом в комментариях.
Примечание:
Файл-пример, который можно потыкать, пока жив сервер и «бессрочный» токен:
https://disk.yandex.ru/d/y7EVtn8afM4QPg
Открытое описание API, если кому-то будет любопытно
28 Aug Return Stock Data Using Excel VBA
Posted at 12:35h
in Excel VBA
0 Comments
Howdee! Querying data over the web becomes more common every day. Almost every cloud based program has the ability to serve up its data through some sort of API. Sometimes, the data can be extracted to Excel through some sort of reporting interface within the program. However, I’ve found that this functionality is usually difficult to use and/or incredibly slow or doesn’t exist at all. This is where the ability to make an Excel http get request is so valuable.
In my example today I’ll be building a small excel application that will allow the user to search for a company’s ticker and then return the most recent stock data using an http request. To do this, we will use the site http://dev.markitondemand.com/MODApis/Api/v2/doc. It is a free service that provides the ability to search for tickers and company names and then fetch stock data on that entity.
HTTP Basics
Before diving into the technical aspects of an Excel http get request, let’s first explore what http requests are. HTTP stands for “Hypertext Transfer Protocol”. In laymen’s terms, it is a means of communicating between a host and a client.
Let’s assume you type www.facebook.com into a web browser. You, the client, have just initiated an HTTP GET request to Facebook, the host (for thoroughness, it’s worth mentioning that sites like Facebook that contain sensitive information use an HTTPS request to secure the data being transferred). What you’ve sent is called the “Request” and the HTML page that Facebook sends back is the “Response”.
The request is sent using a URL (Uniform Resource Locator). While you might be familiar with URL’s, you might not be familiar with their structure. They consist of these components:
Protocol is usually HTTP but, as I said earlier, HTTPS is used when the connection needs to be secured (personal data, credit card information, etc.). The host is just that, the domain address a host has chosen for you to reach their server. The port is usually not needed to be explicitly typed. The default port is 80 and the URL implicitly sends that if you exclude it. The resource path and the query are the import parts I want to point out.
The resource path is instructions sent to the host that tell the server where to navigate to so that your information can be found. Lastly, the query section is always kicked off with a question mark. This section provides any parameters you want to include as well the option to explicitly list the precise data you want returned.
This is as dep as I’ll go into HTTP since this is enough to get started querying information. However, if you’re planning to develop an application that will make HTTP calls, I recommend reading up on the subject so you can handle several types of responses. Let’s say a user passes a bad request to the program you build; the host will send back an HTTP response but it could be an error 404 response and not include any data. You’ll need to be able to handle that and other issues that could arise. This article on the Mozilla Developer Network is a great place to start.
Excel HTTP Get Request – Creating the Search
Now that we know a little about HTTP, let’s dive into the Excel http request using VBA. To get started open a new Excel file and press alt + F11 to open the VBA editor window. The first thing we need to do is add some library references so we can access the controls we will need to make HTTP calls. Click on “Tools” and then “References” and add a check box to Microsoft Scripting Runtime, Microsoft Internet Controls, and Microsoft WinHTTP Services, version 5.1 as pictured below.
In this application, we want to allow the user the ability to search for a company’s stock ticker. We will then need to pass that data into our code. When my program needs to interact with data on a spreadsheet, I prefer to name the range or cell that it will be interacting with so that, when I’m writing code, it’s evident exactly what data I’m collecting or where I need to write data to. I’ve named a cell “Search_For_Company” and put instructions on the sheet so the user realizes that he/she needs to enter a search term there.
Now, we need to write the code that will accept this input, and then make an http get request to return potential matches. But, before that, let’s consider the returned results. I user might type in “face” when searching for Facebook. That would return dozens of companies so we need somewhere to store those values temporarily. For this, I’ve created a basic user form with a list box to display the values. It looks like this:
Typically I’ll break my code down in to sections to discuss, but I think this makes sense to look at the code in its entirety and then explain what’s happening step by step.
Sub GetCompanyInformation()
Dim hReq As Object, JSON As Dictionary
Dim i As Long
Dim var As Variant
Dim ws As Worksheet
Set ws = Sheet1
'create our URL string and pass the user entered information to it
Dim strUrl As String
strUrl = "http://dev.markitondemand.com/MODApis/Api/v2/Lookup/json?input=" & ws.[Search_For_Company]
Set hReq = CreateObject("MSXML2.XMLHTTP")
With hReq
.Open "GET", strUrl, False
.Send
End With
'wrap the response in a JSON root tag "data" to count returned objects
Dim response As String
response = "{""data"":" & hReq.ResponseText & "}"
Set JSON = JsonConverter.ParseJson(response)
'set array size to accept all returned objects
ReDim var(JSON("data").Count, 1)
i = 1
'loop through returned data and get Symbol & Name
For Each Item In JSON("data")
var(i, 0) = Item("Symbol")
var(i, 1) = Item("Name")
i = i + 1
Next
ufCompaniesReturned.lbCompaniesReturned.List = var
Erase var
Set var = Nothing
Set hReq = Nothing
Set JSON = Nothing
If ufCompaniesReturned.Visible = False Then
ufCompaniesReturned.Show
End If
End Sub
Let’s skip over the variable declarations as we’ll discuss each as they’re used. The first line simply assigns the “ws” variable to the sheet where I’m working, Sheet1 in this case. Next, I dimension a new string variable (I could have dimensions this variable above with the others but this is just personal preference when I’m creating a variable that represents a URL) and assign the URL I’m using to it. Note here that for the query section of the URL, I’m using a combination of text entered and I’m pulling the value the user typed into the range we named earlier. This is why I like to use that approach in my Excel http get request programs. I can easily see that I’m passing a value from Sheet1 that represents a company to search for.
Next set the Object variable “hReq” equal to a new object in the Microsoft XML Core Services. If you want to read more about what this entails, MSDN has a great write up on it. Suffice it to say this service allows us to send HTTP requests and parse the responses. Now, with that variable we will open a connection and pass it three parameters. We are telling it that we are sending a “GET” request, we pass the URL to use, and lastly, we want to explicitly tell it the call is not asynchronous (also could be said the call is synchronous). This prevents anything further from happening until we get a response from the host. If we left this blank, the default is true. The program could then move on while we are waiting the call and break. There are two more optional parameters in this Open method for username and password, in that order. If that information is required for your request, you’ll need to provide them. They are not required in this example however.
In the next section of code, we create a response variable and store the response from the request as a string. Since this response is returned in JSON, I’m using the VBA-JSON library to parse the JSON. If you’re unfamiliar with this, click here for my blog post on how to use the library. Note that I wrap my response in some additional text. This is a root JSON object that I wrap the data in so I can perform a count on how many objects were returned, which I do in the next step when I’m resizing the array I declared at the beginning of the code.
Lastly we will loop through each object returned by our Excell http get request, and assign the company’s symbol and name to the array. Once the array has been populated, we will pass that array to a listbox, dispose of some resources, and finally check if the form is already visible and, if it isn’t, show it.
Excel HTTP Get Request – Returning the Stock Quote
Now that we’ve successfully written code that will perform our search query, let’s move on to the http get request to return our stock quote. In the form that pops up, our user will select a company for which they want to return a stock quote. Once they click “OK” we will want the result to be returned. To do this, we need to have this process kick off when the event for the button click occurs. To create a sub routine that subscribes to that event, simply double click the “OK” on your user from inside the VBA editor window.
We can certainly embed all the code we want to run inside this event and call it a day. However, I prefer to keep my code separate. I’ve created another module called “Functions” and created a function inside to run my Excel http get request stock quote call when passed the correct parameters. That function looks like this:
Function GetStockData(company As String, companyName As String)
Dim hReq As Object, JSON As Dictionary
Dim sht As Worksheet
Set sht = Sheet1
Dim strUrl As String
strUrl = "http://dev.markitondemand.com/Api/v2/Quote/json?symbol=" & company
Set hReq = CreateObject("MSXML2.XMLHTTP")
With hReq
.Open "GET", strUrl, False
.Send
End With
Dim response As String
response = hReq.ResponseText
Set JSON = JsonConverter.ParseJson(response)
sht.[Last_Price] = JSON("LastPrice")
sht.[Change] = JSON("Change")
sht.[Change_Percent] = JSON("ChangePercent") / 100
sht.[Quote_Time] = JSON("MSDate")
sht.[Market_Cap] = JSON("MarketCap") / 1000000
sht.[Volume] = JSON("Volume")
sht.[Change_YTD] = JSON("ChangeYTD")
sht.[Change_YTD_Percent] = JSON("ChangePercentYTD") / 100
sht.[High] = JSON("High")
sht.[Low] = JSON("Low")
sht.[Open] = JSON("Open")
sht.[Search_For_Company].Value = companyName
End Function
As you can see, this code structure is very like the previous code structure so I won’t go through every detail. The first thing you might notice is that this isn’t a subroutine. I’ve made this a function and given it two parameters to receive. I first pass in the “company” parameter which will represent the stock ticker symbol. This is the parameter I pass to the strUrl variable to complete my Excel http get request string. Next, I pass in the company Name parameter which will be the formal name returned from our previous query.
The next difference you might notice is that I don’t wrap the HTTP response like I did previously. Since I know this response will only return one object, I don’t need to count or loop through the response. I can simply parse that response into a dictionary and then access them through the respective keys. Also, I’ve created several more named ranges to write these values to. Again, this is not necessary but I find it helps tremendously with the readability of my code. Lastly, I just assign the formal name I used in the call back to the cell.
To call this function, I used this code in the button click event handler:
Dim i As Long
For i = 0 To ufCompaniesReturned.lbCompaniesReturned.ListCount - 1
If lbCompaniesReturned.Selected(i) = True Then
Call GetStockData(lbCompaniesReturned.List(i, 0), lbCompaniesReturned.List(i, 1))
Else
'do nothing
End If
Next i
ufCompaniesReturned.Hide
This code is pretty straight forward. I’m first looping through each item in my list box and checking to see if that item has a selected property of true. If it does, I’m passing the two values in the list box to my GetStockData function. From there, the code is handled by the function in my Functions module. All of this together ends up with this result:
I hope this article showed you the power of being able to make http requests from within an Excel application. You can customize the user experience, pull only the data you need, and it’s very fast. As always, the file I worked with today is available for download if you’re a subscriber to my site. It’s free, easy to sign-up, and I won’t be spamming you with dozens of emails each week. If you have questions on anything I went over, please drop them in the comments section below.
Cheers,
R
Представьте себе ситуацию, Вы получили целевую выборку из одной базы данных, но для полноты картины, как всегда, нужны дополнительные данные. Проблема может быть в том, что нужная информация хранится в другой базе данных и возможности создать на ней свою таблицу нет, подключиться используя link тоже нельзя, да и количество элементов, по которым нужно получить данные, несколько больше, чем допустимое на данном источнике. Вот и получается, что возможность написать SQL запрос и получить нужные данные есть, но написать придется не один запрос, а потом потратить время на объединение полученных данных.
Выйти из подобной ситуации поможет Excel.
Уверен, что ни для кого не секрет, что MS Excel имеет встроенный модуль VBA и надстройки, позволяющие подключаться к внешним источникам данных, то есть по сути является мощным инструментом для аналитики, а значит идеально подходит для решения подобных задач.
Для того чтобы обойти проблему, нам потребуется таблица с целевой выборкой, в которой содержатся идентификаторы, по которым можно достаточно корректно получить недостающую информацию (это может быть уникальный идентификатор, назовем его ID, или набор из данных, находящихся в разных столбцах), ПК с установленным MS Excel, и доступом к БД с недостающей информацией и, конечно, желание получить ту самую информацию.
Создаем в MS Excel книгу, на листе которой размещаем таблицу с идентификаторами, по которым будем в дальнейшем формировать запрос (если у нас есть уникальный идентификатор, для обеспечения максимальной скорости обработки таблицу лучше представить в виде одного столбца), сохраняем книгу в формате *.xlsm, после чего приступаем к созданию макроса.
Через меню «Разработчик» открываем встроенный VBA редактор и начинаем творить.
Sub job_sql() — Пусть наш макрос называется job_sql.
Пропишем переменные для подключения к БД, записи данных и запроса:
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim sql As String
Опишем параметры подключения:
sql = «Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=True;Data Source=Storoge.company.ru Storoge.»
Объявим процедуру свойства, для присвоения значения:
Set cn = New ADODB.Connection
cn.Provider = » SQLOLEDB.1″
cn.ConnectionString = sql
cn.ConnectionTimeout = 0
cn.Open
Вот теперь можно приступать непосредственно к делу.
Организуем цикл:
For i = 2 To 1000
Как вы уже поняли конечное значение i=1000 здесь только для примера, а в реальности конечное значение соответствует количеству строк в Вашей таблице. В целях унификации можно использовать автоматический способ подсчета количества строк, например, вот такую конструкцию:
Dim LastRow As Long
LastRow = ActiveSheet.UsedRange.Row — 1 + ActiveSheet.UsedRange.Rows.Count
Тогда открытие цикла будет выглядеть так:
For i = 2 To LastRow
Как я уже говорил выше MS Excel является мощным инструментом для аналитики, и возможности Excel VBA не заканчиваются на простом переборе значений или комбинаций значений. При наличии известных Вам закономерностей можно ограничить объем выгружаемой из БД информации путем добавления в макрос простых условий, например:
If Cells(i, 2) = «Ваше условие» Then
Итак, мы определились с объемом и условиями выборки, организовали подключение к БД и готовы формировать запрос. Предположим, что нам нужно получить информацию о размере ежемесячного платежа [Ежемесячный платеж] из таблицы [payments].[refinans_credit], но только по тем случаям, когда размер ежемесячного платежа больше 0
sql = «select [Ежемесячный платеж] from [PAYMENTS].[refinans_credit] » & _
«where [Ежемесячный платеж]>0 and [Номер заявки] ='» & Cells(i, 1) & «‘ «
Если значений для формирования запроса несколько, соответственно прописываем их в запросе:
«where [Ежемесячный платеж]>0 and [Номер заявки] = ‘» & Cells(i, 1) & «‘ » & _
» and [Дата платежа]='» & Cells(i, 2) & «‘»
В целях самоконтроля я обычно записываю сформированный макросом запрос, чтобы иметь возможность проверить его корректность и работоспособность, для этого добавим вот такую строчку:
Cells(i, 3) = sql
в третьем столбце записываются запросы.
Выполняем SQL запрос:
Set rs = cn.Execute(sql)
А чтобы хоть как-то наблюдать за выполнением макроса выведем изменение i в статус-бар
Application.StatusBar = «Execute script …» & i
Application.ScreenUpdating = False
Теперь нам нужно записать полученные результаты. Для этого будем использовать оператор Do While:
j = 0
Do While Not rs.EOF
For ii = 0 To rs.Fields.Count — 1
Cells(i, 4 + j + ii) = rs.Fields(0 + ii) ‘& «;»
Указываем ячейки для вставки полученных данных (4 в примере это номер столбца с которого начинаем запись результатов)
Next ii
j = j + rs.Fields.Count
s.MoveNext
Loop
rs.Close
End If
— закрываем цикл If, если вводили дополнительные условия
Next i
cn.Close
Application.StatusBar = «Готово»
End Sub
— закрываем макрос.
В дополнение хочу отметить, что данный макрос позволяет обращаться как к БД на MS SQL так и к БД Oracle, разница будет только в параметрах подключения и собственно в синтаксисе SQL запроса.
В приведенном примере для авторизации при подключении к БД используется доменная аутентификация.
А как быть если для аутентификации необходимо ввести логин и пароль? Ничего невозможного нет. Изменим часть макроса, которая отвечает за подключение к БД следующим образом:
sql = «Provider= SQLOLEDB.1;Password=********;User ID=********;Data Source= Storoge.company.ru Storoge;APP=SFM»
Но в этом случае при использовании макроса возникает риск компрометации Ваших учетных данных. Поэтому лучше программно удалять учетные данные после выполнения макроса. Разместим поля для ввода пароля и логина на листе и изменим макрос следующим образом:
sql = «Provider= SQLOLEDB.1;Password=» & Sheets(«Лист аутентификации»).TextBox1.Value & «;User ID=» & Sheets(«Лист аутентификации «).TextBox2.Value & «;Data Source= Storoge.company.ru Storoge;APP=SFM»
Место для расположения текстовых полей не принципиально, можно расположить их на листе с таблицей в первых строках, но мне удобней размещать поля на отдельном листе. Чтобы введенные учетные данные не сохранялись вместе с результатом выполнения макроса в конце исполняемого кода дописываем:
Sheets(«Выгрузка»).TextBox1.Value = «« Sheets(»Выгрузка«).TextBox2.Value = »»
То есть просто присваиваем текстовым полям пустые значения, таким образом после выполнения макроса поля для ввода пароля и логина окажутся пустыми.
Вот такое вполне жизнеспособное решение, позволяющее сократить трудозатраты при получении и обработке данных, я использую. Надеюсь мой опыт применения SQL запросов в Excel будет полезен и вам в решении текущих задач.
Many times I was irritated of the lack of some Excel functionality (or just I don’t know there is) to easily transform data w/o using pivot tables. SQL in VBA was the only thing that was missing for me.
Distinct, grouping rows of Excel data, running multiple selects etc. Some time agon when I had a moment of time to spare I did my homework on the topic only to discover that running SQL queries from Excel VBA is possible and easy…
Want to create SQL Queries directly from Excel instead? See my Excel SQL AddIn
Want to learn how to create a MS Query manually? See my MS Query Tutorial
Using SQL in VBA example
Let see how to run a simple SELECT SQL Query in Excel VBA on an example Excel Worksheet. On the right see my Excel Worksheet and the Message Box with the similar output from my VBA Macro. The VBA Code is below:
Sub RunSELECT() Dim cn As Object, rs As Object, output As String, sql as String '---Connecting to the Data Source--- Set cn = CreateObject("ADODB.Connection") With cn .Provider = "Microsoft.ACE.OLEDB.12.0" .ConnectionString = "Data Source=" & ThisWorkbook.Path & "" & ThisWorkbook.Name & ";" & "Extended Properties=""Excel 12.0 Xml;HDR=YES"";" .Open End With '---Run the SQL SELECT Query--- sql = "SELECT * FROM [Sheet1$]" Set rs = cn.Execute(sql) Do output = output & rs(0) & ";" & rs(1) & ";" & rs(2) & vbNewLine Debug.Print rs(0); ";" & rs(1) & ";" & rs(2) rs.Movenext Loop Until rs.EOF MsgBox output '---Clean up--- rs.Close cn.Close Set cn = Nothing Set rs = Nothing End Sub
Explaining the Code
So what is happening in the macro above? Let us break it down:
Connecting to the Data Source
First we need to connect via the ADODB Driver to our Excel Worksheet. This is the same Driver which runs SQL Queries on MS Access Databases:
'---Connect to the Workbook--- Set cn = CreateObject("ADODB.Connection") With cn .Provider = "Microsoft.ACE.OLEDB.12.0" .ConnectionString = "Data Source=" & ThisWorkbook.Path & "" & ThisWorkbook.Name & ";" & _ "Extended Properties=""Excel 12.0 Xml;HDR=YES"";" .Open End With
The Provider is the Drive which is responsible for running the query.
The ConnectionStrings defines the Connection properties, like the path to the Queries File (example above is for ThisWorkbook) or if the first row contains a header (HDR).
The Open command executes the connection.
You can find more information on the ADODB.Connection Object on MSDN.
Running the SQL Select Query
Having connected to our Data Source Excel Worksheet we can now run a SQL SELECT Query:
'---Running the SQL Select Query--- Sql = "SELECT * FROM [Sheet1$]" Set rs = cn.Execute(Sql) Do output = output & rs(0) & ";" & rs(1) & ";" & rs(2) & vbNewLine Debug.Print rs(0); ";" & rs(1) & ";" & rs(2) rs.Movenext Loop Until rs.EOF MsgBox output
So what happens here? First we run the Execute command with our SELECT query:
SELECT * FROM [Sheet1$]
What does it do? It indicates that our records are in Sheet1. We can obviously extend this query just to filter people above the age of 30:
SELECT * FROM [Sheet1$] WHERE Age > 30
This would be the result:
The Execute command returns a ADODB RecordSet. We need to loop through the recordset to get each record:
Do '... 'Loop through records - rs(0) - first column, rs(1) - second column etc. '... rs.Movenext 'Move to next record Loop Until rs.EOF 'Have we reached End of RecordSet
Clean up
Lastly we need to Clean up our Objects to free memory. This is actually quite an important step as if you VBA code is runs a lot of queries or computations you might see a slow-down soon enough!
rs.Close cn.Close Set cn = Nothing Set rs = Nothing
What Else Can I Do?
You can do tons of great things with ADODB / MS Queries / SQL in Excel. Here are some additional ideas:
- Run Queries Across Worksheets – you can run JOIN queries on multiple Excel Worksheets. E.g.
SELECT [Sheet1$].[First Last], [Age], [Salary] FROM [Sheet1$] INNER JOIN [Sheet2$] ON [Sheet1$].[First Last]=[Sheet2$].[First Last]
On the below tables:
- Extracting Data from External Data Sources – use different connection strings to connect to Access Databases, CSV files or text files
- Do more efficient LOOKUPs – read my post on VLOOKUP vs SQL to learn more