Страницы

Поиск по вопросам

понедельник, 15 октября 2018 г.

Размещение файлов библиотек в подкаталоге

Как собрать программу на C++ c загружаемыми модулями таким образом, чтобы приложение находило файлы библиотечных модулей не в каталоге запуска, а в отдельном подкаталоге? Например, исполняемому файлу для работы требуется библиотечный файл "db2.dll", который расположен в папке "libs":
Application | +-- main.exe +-- config.txt +-- libs | +-- net.dll +-- db2.dll
Вопрос относится только к сборке приложений для ОС MS-Windows в разных компиляторах/сборщиках (MS-VC, MinGW, CodeBlocks и т.д.). Под Linux/Unix/*BSD для таких случаев просто используется параметр компилятора (линковщика): "-Wl,-rpath,./libs"


Ответ

Как ни странно, задача имеет решение и библиотеки, необходимые для приложения действительно можно положить в отдельный каталог и при этом не использовать динамическую загрузку библиотек, используя только статическое связывание. Это делается при помощи так называемых Assemblies (сборок), поддержка которых появилась в Windows XP
Вкратце технология выглядит следующим образом. В исполняемый файл приложения внедряется манифест-файл, который предписывает приложению использовать некоторую сборку. Загрузчик исполняемых файлов читает этот манифест и ищет каталог со сборкой, причём среди путей поиска присутствует и каталог с исполняемым файлом приложения.

Теперь более подробно, на примере и в деталях.
Создадим простейшее приложение на C++
#include
int main(int, char*[]) { std::cout << "Hello, World!" << std::endl; }
Будучи скомпилированным при помощи компилятора MinGW (MinGW-builds 6.3.0), это приложение в моём случае требует для своей работы файлы libgcc_s_sjlj-1.dll, libstdc++-6.dll и libwinpthread-1.dll. При этом сам исполняемый файл импортирует только libstdc++-6.dll, которая, в свою очередь, импортирует две остальные. Назовём нашу "сборку", например, libs, создадим в каталоге с основным исполняемым файлом нашего приложения каталог libs и разместим внутри него три обозначенных выше DLL.
Далее создаём манифест. Имя файла манифеста должно состоять из имени сборки и иметь расширение .manifest, т.е., в нашем случае, libs.manifest. Содержимое файла должно быть следующим:

Особо следует отметить, что хотя наше приложение импортирует только одну библиотеку, в манифесте нужно прописать все, иначе приложение работать не будет. Таким образом, у нас получилась следующая структура каталогов и файлов (app.exe - имя нашего приложения):
├─ app.exe └─ libs ├─ libs.manifest ├─ libgcc_s_sjlj-1.dll ├─ libstdc++-6.dll └─ libwinpthread-1.dll
И, наконец, финальный аккорд! Создаём манифест, который будем внедрять в наше приложение. Пусть он лежит в файле с именем manifest.xml

Параметры сборки, указанные в этом манифесте, должны совпадать с теми, что мы указали ранее. Теперь осталось только создать файл ресурсов, в котором указать на наш манифест
#include
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "manifest.xml"
и прикомпоновать его к приложению (в случае CMake, например, указав имя файла ресурсов в списке исходников).

Разное поведение программы на Win7 и WinXP

Есть такой код (минимальный рабочий пример):
#include #include
HDC hDC; HDC hDCMem; HBITMAP hbitmap; HWND hwnd; int ScreenMaxX; int ScreenMaxY;
short pattern[8]={~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF, ~0xFF}; HBRUSH brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern));
void bar(int nLeft, int nTop, int nRight, int nBottom) { RECT rect; rect.left = nLeft; rect.right = nRight; rect.top = nTop; rect.bottom = nBottom;
::SetTextColor(hDCMem, 0xFF00FF); ::SetBkColor(hDCMem, 0xFF00FF); //brush=::CreatePatternBrush(::CreateBitmap(8, 8, 1, 1, pattern)); ::FillRect(hDCMem, &rect, brush); }
void flush(){ ::BitBlt(hDC, 0, 0, ScreenMaxX, ScreenMaxY, hDCMem, 0, 0, SRCCOPY); }
CRITICAL_SECTION graphics_cs;
uint8_t thread_cnt=0; uint8_t total_threads=2; HANDLE turnstile1=CreateSemaphoreW(nullptr, 0, 2, nullptr);
void thread_func(int num){ int x,y; if(num==0){ x=20; y=0; } else { x=110; y=0; }
while(true) { while(true) { EnterCriticalSection(&graphics_cs); if (thread_cnt == num) { thread_cnt++; bar(x, y, x+40, y+40); y+=1; //flush(); if(thread_cnt==total_threads){ thread_cnt = 0; flush(); ReleaseSemaphore(turnstile1, total_threads, nullptr); } LeaveCriticalSection(&graphics_cs); break; } else { LeaveCriticalSection(&graphics_cs); } }
WaitForSingleObject(turnstile1, INFINITE);
Sleep(100); } }
void mainx () { InitializeCriticalSection(&graphics_cs); for(int i=0; iDWORD Th(LPVOID param) // новая нить { (void)param; ::SetWindowPos(hwnd, HWND_TOP, // дескриптор окна и порядок размещения 10, // позиция по-горизонтали 10, // по-вертикали 400, // ширина 500, // высота SWP_SHOWWINDOW // отобразить ); mainx(); flush(); return 0; } DWORD g_nMainThreadID;
//обработка сообщений главного окна приложения long FAR PASCAL WindowProc(HWND hWnd,UINT message, WPARAM wParam,LPARAM lParam ) { switch (message) { case WM_PAINT: flush(); break; case WM_DESTROY: PostQuitMessage(0); break; } return DefWindowProc(hWnd, message, wParam, lParam); }
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd ) { (void)hPrevInstance, (void)lpCmdLine; WNDCLASS wc; MSG msg;
wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "Menu_one"; wc.lpszClassName = "NAME";
if (!RegisterClass(&wc)) {return 0; };
//создаем главное окно приложения hwnd = CreateWindow("NAME", // имя класса окна "!", // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, //р-ры и расположение по-умолчанию CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, // идентификатор родительского окна NULL, // идентификатор меню hInstance, // идентификатор приложения NULL // доп параметры ); ScreenMaxX = ::GetSystemMetrics(SM_CXSCREEN); ScreenMaxY = ::GetSystemMetrics(SM_CYSCREEN); hDC = ::GetDC(hwnd); hDCMem = ::CreateCompatibleDC(hDC); hbitmap = ::CreateCompatibleBitmap(hDC, ScreenMaxX, ScreenMaxY ); ::SelectObject(hDCMem, hbitmap); auto hbrush = (HBRUSH)::GetStockObject(WHITE_BRUSH); ::SelectObject(hDCMem, hbrush); ::PatBlt(hDCMem, 0,0, ScreenMaxX, ScreenMaxY, PATCOPY ); ::DeleteObject(hbrush);
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Th, (LPVOID)hwnd, 0,&g_nMainThreadID);
ShowWindow(hwnd, nShowCmd); UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
Этот код медленно рисует две розовые полосы сверху вниз в разных потоках (потоку соответствует функция thread_func). Причём на Windows 7 полосы рисуются равномерно, а на Windows XP — с разной скоростью.

(На Windows 7 левая полоса движется с той же скоростью, что и правая)
Я обнаружил, что если раскомментировать хотя бы одну из закомментированных строк с кодом, то поведение будет одинаковым. Почему поведение программы различается и почему при раскомментировании указанных строк она работает одинаково на разных ОС?


Ответ

Как написано, в ответе @SeNS, причина различия скоростей в том, что прямоугольники отрисовываются после 18 вызовов FillRect. Однако это не ошибка, а, в принципе, документированное поведение.
Данный механизм называется «пакетирование» (batching). Оказывается, если вызывать одну и ту же GDI функцию несколько раз в одном и том же потоке, то эти вызовы накапливаются в пакете (в каждом потоке создаётся свой пакет). И пока либо не вызовется другая GDI функция, либо размер пакета не превысит указанный максимальный размер (который возвращает функция GdiGetBatchLimit), либо не заполнится буфер пакетирования, отрисовки не происходит. В моём случае выполняется третье условие, так как максимальный размер пакета в моей системе равен 20-ти, а прямоугольник появляется после 18-го вызова.
Поэтому решением проблемы может быть, например, вызов функции GdiFlush после FillRect. Из названия понятно, что эта функция производит выполнение функций в пакете и его очистку. Но, на мой взгляд, в моей программе намного лучше вообще отключить пакетирование: GdiSetBatchLimit(1) в начале кода потока, так как всегда рисуется только один прямоугольник и сразу же выводится на экран.
Почему на некоторых Windows 7 пакетирование не работает, несмотря на то, что размер пакета также равен 20-ти, мне всё ещё не ясно. Возможно, дело в более новом менеджере рабочего стола, который как-то более хитро производит отрисовку. Также, не совсем очевидно, какие функции могут попасть в пакет, например, Ellipse срабатывает сразу.

На что способен explicit?

Вроде бы как в новых стандартах можно использовать ключевое слово explicit не только к конструкторам, чтобы предотвратить неявное приведение, но и к операторам. Можете объяснить, для чего это нужно и привести пример? Возможно, есть и другие применения?


Ответ

explicit для операторов преобразования нужен по той же причине, что и для конструкторов. Требование от программиста явно указывать тип, чтобы избежать неожиданного поведения кода при неявном приведении типов.
Самый популярный пример это operator bool. До c++11 отсутствие возможности задавать explicit для оператора преобразования в тип требовало городить довольно неочевидные схемы для реализации идиомы Safe Bool. Т.е. использование объекта нашего типа в контексте, когда требуется приведение в булев тип, например внутри if или while. В частности можно обратить внимание на std::ios::operator bool. По ссылке как раз хорошо видно различие между c++98 и c++11.
То, что раньше нельзя было задавать "явность" для оператора преобразования по сути - досадная недоработка, т.к. оператор преобразования и конструктор с одним параметром (для которого как раз актуален explicit) по сути предназначены для одного и того же. Разница лишь в том с какой стороны смотреть. Если мы создаём класс N, то конструктор вида N(O o) будет осуществлять преобразование типа O в тип N. Для преобразование в обратную сторону нужен либо конструктор вида O(N n) (но это порой невозможно, т.к. тип O может быть закрыт или вовсе не быть классом), либо operator O(N n)

Как показать невидимое окно?

У меня есть окно, которое выполняет какой-то процесс с визуализацией. И мне надо чтобы при запуске приложение оно работало, но не показывалось (то есть чтобы произошло событие Load). При этом у пользователя всегда есть возможность его показывать и скрывать. Проблема в том что у меня не получается решить этот вопрос без начального мерцания из-за подобного кода.
SomeWindow.Show(); SomeWindow.Hide();
Ни в Windows Forms, ни в WPF не вижу решения этой проблемы.
Проблема также в том, что окно показывается в TaskBar, но только когда оно видимое. Поэтому мне не подходят хаки вроде
Установки прозрачности окна. Установки нулевой широты, высоты и удаления рамок. Вынос окна за пределы экрана и т.д.
Для чего это надо и почему нельзя всё вынести в какой-нибудь Task? Потому что код выполняемый окном зависит от визуализации. Мне нужно чтобы человек мог скрывать и показывать окно когда и сколько угодно, но чтобы его состояние было таким же как будто его не скрывали.


Ответ

Получил ответ на англ. SO. Этот же код работает для WPF если заменить FormWindowState на WindowState. Суть в том, что надо сначала чтобы перед "показом" установить (в коде или дизайнере):
SomeWindow.ShowInTaskbar = false; SomeWindow.WindowState = FormWindowState.Minimized;
Затем выполнить код
SomeWindow.Show(); SomeWindow.Hide();
И сразу после этого подготавливаем окно для нормального отображения при последующих вызовах SomeWindow.Show
SomeWindow.ShowInTaskbar = true; // Если надо чтобы форма показывалась на панели быстого запуска. SomeWindow.WindowState = FormWindowState.Normal; // или FormWindowState.Maximized если надо
Это можно превратить в метод расширения для Windows Forms:
public static class FormHelper { public static void ShowInvisible(this Form form) { // сохраняем параметры окна bool needToShowInTaskbar = form.ShowInTaskbar; FormWindowState initialWindowState = form.WindowState;
// делаем окно невидимым form.ShowInTaskbar = false; form.WindowState = FormWindowState.Minimized;
// показываем и скрываем окно form.Show(); form.Hide();
// восстанавливаем параметры окна form.ShowInTaskbar = needToShowInTaskbar; form.WindowState = initialWindowState; } }
или для WPF:
public static class WindowHelper { public static void ShowInvisible(this Window window) { // сохраняем параметры окна bool needToShowInTaskbar = window.ShowInTaskbar; WindowState initialWindowState = window.WindowState;
// делаем окно невидимым window.ShowInTaskbar = false; window.WindowState = WindowState.Minimized;
// показываем и скрываем окно window.Show(); window.Hide();
// восстанавливаем параметры окна window.ShowInTaskbar = needToShowInTaskbar; window.WindowState = initialWindowState; } }

Оптимизация SQL запроса в Postgres

есть запрос для построения маршрутов из рейсов
WITH RECURSIVE tree AS ( SELECT f0.id::text AS flights, '/'||origin_id||'/'||destination_id||'/' AS route, '/'||airline||'/' AS airlines, '/'||invisible||'/' AS invisible, origin_id, destination_id, std AS departure, sta AS arrival, 0 as hops, (sta - std)::INTERVAL AS total_time, '00:00'::INTERVAL AS waiting_time, available AS available FROM flights f0 INNER JOIN airports a ON f0.origin_id = a.id AND NOT a.cargo_accepted = false WHERE origin_id = '13' AND airline IN ('LH', 'OK', 'QS', 'U6') AND std >= '2017-06-02 06:11:02 UTC' AND NOT f0.available = false UNION ALL SELECT s.flights || '-->' || f1.id::text AS flights, s.route||f1.destination_id||'/', s.airlines||f1.airline||'/', s.invisible||f1.invisible||'/', s.origin_id, f1.destination_id, s.departure AS departure, f1.sta AS arrival, s.hops + 1 AS hops, s.total_time + (f1.sta - f1.std)::INTERVAL AS total_time, s.waiting_time + (f1.std - s.arrival)::INTERVAL AS waiting_time, s.available FROM tree s JOIN flights f1 INNER JOIN airports a1 ON f1.origin_id = a1.id AND NOT a1.cargo_accepted = false AND NOT a1.transit = false ON f1.origin_id = s.destination_id AND f1.std >= s.arrival + '8 hour' AND f1.std <= s.arrival + '7 day' AND NOT s.route LIKE '%/'||f1.destination_id||'/%' AND s.hops < 4 AND NOT s.available = false AND NOT f1.available = false AND f1.airline IN ('LH', 'OK', 'QS', 'U6') ) SELECT * FROM tree INNER JOIN airports a3 ON destination_id = a3.id AND NOT a3.cargo_accepted = false WHERE destination_id = '112' AND waiting_time < '25 day' AND arrival <= '2017-06-27 06:00:28 UTC' AND airlines LIKE '%U6%' ORDER BY arrival, departure, waiting_time
количество рейсов растет и запрос начинает все дольше и дольше думать на данный момент в базе 64105 рейсов, как бы это мелочи для DB но запрос проходит примерно за 12-16 секунд.
Помогите разобраться, что не так?
делал анализ https://gist.github.com/sanyco86/26f07fbf567e09639cc75ac7b557e660
ну и собственно, если кто-то захочет вникнуть в проблему тестовые данные http://beta.u6-cargo.com/assets/Postgtes_backup.sql


Ответ

Основная проблема данного запроса в быстром размножении количества обрабатываемых записей в рекурсии. Основным и практически единственным способом борьбы с этим является как можно большее ограничение подбираемых внутри рекурсии записей. А одна лишняя запись на первом шаге рекурсии может породить за собой тысячи записей к 4 шагу...
Первое, что бросается в глаза: запрос внутри рекурсивной части выбирает все рейсы начиная с конкретной даты, но никак не ограничивая максимально возможную дату. При этом после рекурсии все рейсы с датой посадки больше определенной отбрасываются. Добавление условий на максимальную дату в обе части рекурсивного CTE сокращают время работы с 12 секунд до 2.5.
Второе, что можно сделать - это оптимизировать работу с индексами. На таблице создано много индексов, но по одиночным полям. А использование сразу нескольких индексов по одной таблице в запросе хоть и возможно, но не сильно увеличивает производительность, а иногда ее и снижает. Весь вопрос в селективности того или иного индекса. Если по условию индекса отбирается слишком много записей, то работа по нему лишь замедляет выборку. Для того что бы повысить селективность стоит делать составные индексы из нескольких полей подбирающие по условию в запросе как можно меньше записей. В вашем случае необходимо сделать следующее:
DROP INDEX "index_flights_on_available"; CREATE INDEX "index_dep_std" ON "flights" USING "btree" ("origin_id", "std", "sta", "destination_id", "available");
Индекс по бинарному полю available обладает очень низкой селективностю, отбирая по одному ключу сразу половину всей таблицы. В нем нет смысла. А в данном случае оптимизатор все таки его использовал объединяя результаты с выбором по другому индексу, но это несколько замедляло запрос, затраты на объединение были выше, чем давала фильтрация по нему. Создаваемый же индекс максимально покрывает потребности данного запроса, так как поиск рейсов начинается с определенного АП вылета по интервалу дат, поэтому мы включаем эти поля первыми, сначала проверяемое на точное равенство, потом те, что на диапазон. Включение АП назначения и available позволяет проверить практически все условия из запроса двигаясь по дереву индекса. Данная операция сокращает запрос до 1 секунды.
И наконец еще одно предложение, самое сложное по реализации ... В настоящее время запрос перебирает все возможные сочетания аэропортов из всей маршрутой сети, но очевидно, что полеты через некоторые АП проверять бесполезно. Если нам надо попасть из Москвы в Сочи то лететь первым делом в Мирный, который мягко говоря не в той стороне и главное, у которого весьма специфичная сеть своих рейсов, заведомо проигрышный вариант. Однако рекурсия рассматривает и такие варианты и порождает за одним перелетом в Мирный еще ворох рейсов, следующих из него. Для фильтрации таких ситуаций можно заранее рассчитать достижимость каких либо АП при пролете через другие и в рекурсии не заходить в ветви, по которым требуемый АП назначения заведомо не достижим за оставшиеся N перелетов. Создадим таблицу достижимости АП из других:
create table approach( orig integer not null, -- Аэропорт вылета dest integer not null, -- Аэропорт назначения hop integer not null, -- Минимальное количество перелетов для достижения constraint approach_pkey primary key(dest,orig) );
Теперь ее надо заполнить, это самая интересная и сложная часть. Дело в том, что содержимое данной таблицы надо будет всегда поддерживать в актуальном состоянии, как только появляется рейс связывающий аэропорты, между которыми до этого не было прямого сообщения - нам надо пересчитать всю сеть. Изменяются поля cargo_accepted и transit в справочнике аэропортов - опять пересчитать. И что бы таблица отвечала текущей ситуации дополнительно надо ее пересчитывать например раз в сутки. Пересчет выполняется следующим запросом:
WITH RECURSIVE Q(o,d,transit) as( select origin_id o, destination_id d, a.transit -- Список пар АП из рейсов from flights f, airports a where std >= localtimestamp - interval '1 day' -- and std <= localtimestamp + interval '60 day' and a.id=f.origin_id and NOT a.cargo_accepted = false group by origin_id, destination_id, a.transit ), Tree(orig, dest, hop, transit, route) as( select o, d, 1, transit, -- Затравка рекурсивной части '/'||o||'/'||d||'/' AS route from Q union all -- Рекурсия select T.orig, Q.d, T.hop+1, T.transit, T.route||Q.d||'/' from Tree T, Q where Q.o=T.dest and not Q.transit=false and T.route not like '%/'||Q.d||'/%' and T.hop<4 ), New(orig,dest,hop) as( -- Таким должно быть содержимое таблицы select orig, dest, min(hop) hop from Tree group by orig, dest union select distinct o,o,0 -- Записи из АП в него самого за 0 перелетов нужны поиску from Q ), Upd(orig,dest) as( -- Обновляем существующие записи update approach a set hop=n.hop from New n where a.orig=n.orig and a.dest=n.dest returning a.orig, a.dest ), Ins(orig, dest) as( -- Вставляем недостающие insert into approach(orig,dest,hop) select orig, dest, hop from New n where (n.orig,n.dest) not in(select orig,dest from Upd) returning orig,dest ) -- И удаляем устаревшие delete from approach where (orig,dest) not in(select orig,dest from New)
Особое внимание обратите на условия в самом начале. В данном случае берутся все рейсы, начиная со вчерашнего дня, поэтому использовать таблицу можно будет только при поиске будущих рейсов. Если надо искать, что было в прошлом (надеюсь все таки не надо), придется либо менять это условие, что понизит точность в текущий момент, либо не пользоваться ей при поиске в истории. Так же, если например системе никогда не надо искать рейсы, которые вылетят через два месяца, можете раскоментарить ограничение на максимальную дату.
Еще раз напоминаю, что таблицу надо поддерживать в актуальном состоянии. Для этого рекомендую сделать хранимую процедуру с данным запросом и вызывать ее раз в день, в триггере на справочник аэропортов и в триггере на добавление рейсов (что бы это не было слишком частым, вызывать ее только если в таблице approach нет записи с парой аэропортов из добавляемого рейса с полем hop равным 1 (прямой перелет)). В принципе, на текущей сети рейсов она выполняется 80 ms.
И наконец итоговая версия запроса поиска:
WITH RECURSIVE tree AS ( SELECT f0.id::text AS flights, '/'||origin_id||'/'||destination_id||'/' AS route, '/'||airline||'/' AS airlines, '/'||invisible||'/' AS invisible, origin_id, destination_id, std AS departure, sta AS arrival, 0 as hops, (sta - std)::INTERVAL AS total_time, '00:00'::INTERVAL AS waiting_time, available AS available FROM flights f0 INNER JOIN airports a ON f0.origin_id = a.id AND NOT a.cargo_accepted = false WHERE origin_id = 13 AND airline IN ('LH', 'OK', 'QS', 'U6') AND std >= '2017-06-02 06:11:02 UTC' and sta <= '2017-06-27 06:00:28 UTC' AND NOT f0.available = false UNION ALL SELECT s.flights || '-->' || f1.id::text AS flights, s.route||f1.destination_id||'/', s.airlines||f1.airline||'/', s.invisible||f1.invisible||'/', s.origin_id, f1.destination_id, s.departure AS departure, f1.sta AS arrival, s.hops + 1 AS hops, s.total_time + (f1.sta - f1.std)::INTERVAL AS total_time, s.waiting_time + (f1.std - s.arrival)::INTERVAL AS waiting_time, s.available FROM tree s JOIN airports a1 ON s.destination_id = a1.id AND NOT a1.cargo_accepted = false AND NOT a1.transit = false JOIN flights f1 ON f1.origin_id = s.destination_id AND f1.std >= s.arrival + '8 hour' AND f1.std <= s.arrival + '7 day' AND NOT f1.available = false JOIN approach app ON app.dest=49 -- <-- Заменить на требуемый АП назначения and app.hop<4-1-s.hops -- <-- 4 - та же, что в условии s.hops<4 and f1.destination_id=app.orig WHERE NOT s.route LIKE '%/'||f1.destination_id||'/%' AND s.hops < 4 AND NOT s.available = false AND f1.airline IN ('LH', 'OK', 'QS', 'U6') and f1.sta <= '2017-06-27 06:00:28 UTC' ) SELECT * FROM tree INNER JOIN airports a3 ON destination_id = a3.id AND NOT a3.cargo_accepted = false WHERE destination_id = 49 AND waiting_time < '25 day' AND arrival <= '2017-06-27 06:00:28 UTC' AND airlines LIKE '%U6%'
ORDER BY arrival, departure, waiting_time
К сожалению, на маршрутах, действительно требующих нескольких перелетов, скорость уменьшилась только до 500-600ms (вроде 13-49 в примере). Зато на рейсе 13-112 время выполнения уменьшилось до 11 ms (т.е. в 1000 раз по сравнению с вариантом из вопроса). Рекомендую посравнивать на всякий случай результаты после выполнения реальных запросов без использования approach и с ней. В принципе разницы в количестве записей на нескольких тестовых направлениях я не заметил.

Быстрое переключение мониторов

Как изменять состояние Multiple displays одной командой?


Ответ

Как говорится в https://stackoverflow.com/a/16791451/4928642, можно запустить стандартную программу DisplaySwitch с одним из следующих параметров:
/clone - Duplicate these displays /external - Show desktop only on 2 /internal - Show desktop only on 1 /extend - Extend this displays
Соответственно, можно создать нужные ярлыки и запускать их.

Как сделать placeholder для select

Как сделать так, чтобы при выборе, к примеру, США, слова Выбранная страна оставались. То есть Выбранная страна: постоянно должны быть внутри независимо от выбора страны.


Ответ

Сделать можно так:
помещаем select в отдельный контейнер, например, .block добавляем в .block еще один блок, например, .placeholder, в который будем выводить текст "Выбранная страна:" + текст выбранной option, и располагаем его поверх select для .placeholder в css указываем pointer-events:none;, чтобы не припятствовать клику по select
На jQuery это будет выглядеть вот так:
function selPlaceholder(block) { var placeholder = block.find('.placeholder'), select = block.find('select'); placeholder.text(placeholder.attr('data-text') + select.find('option:selected').text()); } $('.block').each(function() { selPlaceholder($(this)); }); $('.block select').on('change', function() { selPlaceholder($(this).closest('.block')); }); .block, .block select { position: relative; width: 300px; font-family: 'Arial'; font-size: 12px; } .block .placeholder { position: absolute; z-index: 1; left: 5px; top: 2px; pointer-events: none; background: #fff; }