PHP. Javascript. Редактирование имени файлов перед отправкой на сервер

Как известно, со стороны javascript мы не можем обратиться напрямую к файлу или файлам. Единственное, можно лишь получить имя файла. Допустим, перед нами поставили задачу, организовать возможность до загрузки файла на сервер редактировать имя выбранного файла. В лоб это невозможно, но можно воспользоваться обходным путем: 1 - нужно попытаться как-то скрыть стандартный элемент обозревателя файлов, 2 - организовать вспомогательные интерфейсы, способные вызывать обозреватель файлов и получать в свое распоряжение имя файла, 3 - при сохранении файла на стороне сервера подменять имя файла на редактируемое.
Ниже представлено одно из решений данной задачки. Обратите внимание на хак, скрывающего стандартный элемент обозревателя файлов. Также в интерфейсе редактирования имени файла использован атрибут - readonly, если попытаться использовать - disabled, для предупреждения режима редактирования файла, но тогда фактически из формы будет исключен данный элемент, а значит невозможно будет отсылать редактируемое имя серверу. Решение протестировано на большинстве браузеров свежих релизов. 

<?php
    if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'save') {
      
        if (!empty($_FILES['file']['name'])) {
      
            $short_name = $_REQUEST['short_name'];
            $exstension = !empty($_REQUEST['exstension']) ? '.'.$_REQUEST['exstension'] : '';
            $file_name     = $short_name.$exstension;
            $upload_dir = "./files/test";
      
            if (!file_exists($upload_dir)) {
                mkdir($upload_dir);
            }
          
            if ($_FILES['file']['error'][0] == 0) {
                $upload_file = $upload_dir.'/'.$file_name;
                move_uploaded_file($_FILES['file']['tmp_name'][0], $upload_file);
            }
      
        }
  
    }
?>

<html>
  
<head>
    <title>test</title>
</head>

<style>

    #choose_btn {
        width: 80px;
    }
  
    #submit_btn {
        width: 80px;
    }
  
    #file_view table {
        width: 300px;
    }
  
    #file_view {
        background: #FFF;
        border: solid #364658;
        border-width: 1px;
        width: 300px;
    }
  
    #file_view td {
        width: 100px;
        border: solid #364658;
        border-width: 1px;
        text-align: center;
    }
  
    .input_file {
        position: absolute;
        margin-left: -9000px;
        -moz-opacity: 0;
        filter: alpha(opacity=0);
        opacity: 0.5;
    }

</style>

<script src="jquery.js"></script>

<script>
  
    $(document).on('change', '#input_file_btn', function(){
  
        var file                     = {};
            file.name              = this.value,
            win                        = /.*\\(.*)/,
            unix                       = /.*\/(.*)/,
            separator              = /\./,
            extension              = /\w+/,
            file.name               = file.name.replace(win, "$1"),
            file.name               = file.name.replace(unix, "$1"),
            file.name_parts    = file.name.split(separator),
            file.short_name    = file.name_parts[0],
            file.extension        = file.name_parts[file.name_parts.length - 1];
            file.extension        = file.name_parts.length > 1 && extension.test(file.extension) ? file.extension : '';
          
        $('#file_short_name').val(file.short_name);
        $('#file_exstension').val(file.extension);
      
        $('#file_short_name').prop('readonly', true);
      
    }).on('click', '#edit', function(){
  
        $('#file_short_name').prop('readonly', false);
  
    }).on('click', '#choose_btn', function(){
  
        $('#input_file_btn').trigger('click');
  
    });

</script>

<body>
 
    <form action="" method="post" enctype="multipart/form-data">
 
        <input type="hidden" name="action" value="save"/>
        <input type="file" name="file[]" id="input_file_btn" class="input_file"/>
      
        <div>
            <table id="file_view">
                <thead>
                    <tr>
                        <td>mode</td>
                        <td>file short name</td>
                        <td>file exstension</td>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>
                            <span id="edit" style="color: blue; border-bottom: 1px dashed; cursor: pointer;">
                                edit
                            </span>
                        </td>
                        <td>
                            <input type="text" id="file_short_name" name="short_name" readonly />
                        </td>
                        <td>
                            <input type="text" id="file_exstension" name="exstension" readonly />
                        </td>
                    </tr>
                </tbody>          
            </table>
        </div>
      
        <div>
            <input id="choose_btn" type="button" value="choose file"/>
            <input id="submit_btn" type="submit" value="save file"/>
        </div>
  
    </form>
 
</body>
  
</html>

PHP. Soap service. Remote Procedure Call. Разработка тестового веб-сервиса

Наш тестовый веб-сервис будет решать одну задачу: на любой входной запрос, состоящего из даты в формате - день.месяц.год - он должен возвращать дату в виде timestamp. Создадим для этого процедуру - test(). Итак, для разворачивания soap - сервиса необходимо убедиться в том, что в настройках разрешено использование soap - сервера и клиента

Далее выделяете необходимую директорию для последующих служебных файлов. В моем примере в корне хоста test.ru была выделена директория - /web_service, куда разместил следующие файлы:
.htaccess - поддержка веб - сервиса;
index.php - реализация движка веб - сервиса (класс соединения и вызываемые процедуры);
web_service.wsdl - описание нашего тестового веб - сервиса и доступа к нему;
test.php - для демонстрации работы веб - сервиса.
Ниже приведено их содержимое, думаю по ним не составит труда разобраться.

.htaccess

# Enables or disables WSDL caching feature.
php_flag soap.wsdl_cache_enabled 0

# Sets the directory name where SOAP extension will put cache files.
php_value soap.wsdl_cache_dir "/tmp"

# (time to live) Sets the number of second while cached file will be used
# instead of original one.
php_value soap.wsdl_cache_ttl 0


AddDefaultCharset utf8

index.php

<?php
   
    ini_set('soap.wsdl_cache_enabled', 'Off');
   
    class Connection
    {
        /*
            Возвращает заданную дату в виде timestamp.
            Формат даты - dd.mm.yyyy
        */
        function test($dt) {
       
            if (!empty($dt)) {
                $dt = explode('.', $dt);
                $time_stamp = mktime(0, 0, 0, $dt[1], $dt[0], $dt[2]);
                return $time_stamp;
            }
            else {
                return false;
            }
           
        }   
       
    }
   
    try
    {
        $server = new SoapServer('http://192.168.126.128/web_service/web_service.wsdl?'.rand());
        $server->setClass("Connection");
        $server->handle();
    }
    catch (ExceptionFileNotFound $e)
    {
        echo 'Error message: ' . $e->getMessage();
    }

?>


web_service.wsdl

<?xml version="1.0" encoding="utf-8"?>

<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xs="http://www.w3.org/2001/XMLSchema" name="webserviceService" targetNamespace="http://tempuri.org/" xmlns:tns="http://tempuri.org/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/">

<message name="test_input">
    <part name="dt" type="xs:string"/>
</message>

<message name="test_output">
    <part name="request" type="xs:string"/>
</message>

<message name="null_response"/>

<portType name="Connection">
    <operation name="test">
        <input message="tns:test_input"/>
        <output message="tns:test_output"/>
    </operation>
</portType>

<binding name="bind_connect" type="tns:Connection">

    <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>

    <operation name="test">
        <soap:operation soapAction="urn:web-service#test" style="rpc"/>
        <input message="tns:test_input">
            <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:web-service"/>
        </input>
        <output message="tns:test_output">
            <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:web-service"/>
        </output>
    </operation>

</binding>

<service name="webserviceService">
    <port name="webservicePort" binding="tns:bind_connect">
        <soap:address location="http://
192.168.126.128/web_service/index.php"/>
    </port>
</service>

</definitions>


test.php

<?php

header('Content-type: text/html; charset=utf-8');
@set_time_limit(300);

try {
   
    $client = new SoapClient("http://192.168.126.128/web_service/web_service.wsdl?".rand(), 

                                                     array('trace' => 1, 'exceptions' => 1));
    echo 'test('.date("d.m.Y", time()).') = '.$client->test(strval(date("d.m.Y", time())));
   
}
catch (SoapFault $e) {

    echo "<pre>".print_r($e, true)."</pre>";
    echo "<br/><br/>Error Caught";

}

?>


Стоит отметить, что при инстанцировании soap-сервера и клиентов, а также в файле описания веб-сервиса, был использован ip-адрес веб-сервера, также можно использовать и непосредственное имя хоста (test.ru).
После завершения всех работ, пробуем протестировать наш веб-сервис. На запрос

http://test.ru/web_service/test.php

мы получаем ожидаемый ответ

test(27.03.2014) = 1395903600

Что говорит о работоспособности сервиса. Всем успеха. Исходники можно скачать по следующей ссылке - download

Javascript. Типовое сравнение объектов

Объявим пространство имен NS (регистрозависим)

var NS = {};

Далее объявим в нем конструктор Test и создадим на его основе экземпляр t

NS.Test = function() {
    this.name = 'Test';
};

NS.t = new NS.Test();


Далее создадим объект o, используя стандартный объект Object

NS.o = {};

Для определения принадлежности того или иного экземпляра своему "классу" (в js понятие класса условно), можно воспользоваться следующими стандартными способами

NS.t.constructor == NS.Test // true
NS.t instanceof NS.Test     // true

NS.o.constructor == Object     // true
NS.o instanceof Object         // true


Если попытаться сопоставить объект через явно объявленный конструктор с объектом Object,
то можно получить следующие результаты

NS.t.constructor == Object     // false
NS.t instanceof Object         // true


Первое сравнение вернуло false, это и понятно, так как этот конструктор явно объявлен и ссылается на функцию Test(), второе сравнение истинно потому, что сам конструктор является объектом, что уж там, в javascripte практически все представлено объектом, такова его реализация. В подтверждение сказанного приведу следующее сравнение

NS.Test.constructor == Object     // false
NS.Test instanceof Object         // true


В виде дополнения, приведу список встроенных в javascript объектов

// Массив пронумерованных элементов, также может служить стеком или очередью
Array

// Объект для булевых значений
Boolean

// Функции для работы с датой и временем
Date

// объект для представления ошибок
Error

// Ошибка при выполнении функции eval
EvalError

// Каждая функция в яваскрипт является объектом класса Function
Function

// Встроенный объект, предоставляющий константы и методы для математических вычислений
Math

// Объект для работы с числами
Number

// Базовый объект javascript
Object

// Ошибка, когда число не лежит в нужном диапазоне
RangeError

// Ошибку при ссылке на несуществующую переменную
ReferenceError

// Позволяет работать с регулярными выражениями.
RegExp

// Базовый объект для строк. Позволяет управлять текстовыми строками, форматировать их и выполнять поиск строк
String

// Ошибка при интерпретации синтаксически неверного кода
SyntaxError

// Ошибка в типе значения
TypeError

// Ошибка при некорректном URI
URIError


Небольшой пример сверки типа объекта

var stack = [];

if (stack instanceof Array) {
    stack.push(1);
    stack.push(2);
    stack.push(3);
}

Oracle. Get dates in russian language

select to_char(sysdate, 'Month', 'nls_date_language = russian') current_month from dual

Oracle. Особенности NULL значений

Как известно, в sql то или иное поле либо определено (NOT NULL), и тогда имеет определенное значение, либо не определено (NULL), и тогда с этими полями начинают обращаться более аккуратно, так как они могут вызвать неоднозначность и ошибочность в ожидаемом запросе. Для примера продемонстрирую следующий сценарий, в результатах которого (см. рисунок ниже) видно, как изменяется значение среднего арифметического значения, суммы значения, общего количества и количества строк со значащими значениями. Для сравнения поведения приведены строками ниже результаты подзапросов, где введена замещающая пустое значение на ноль функция nvl(). Видно, что при подсчетах пустые значения просто напросто не учитываются, функция nvl(), напротив, приводит к тому, что "пустые" значения включаются в расчет. 

with
  -- emulation of test table
  virt_table( id, val ) as (
    select 1, 1 from dual union all
    select 2, 2 from dual union all
    select 3, null from dual union all
    select 4, null from dual union all
    select 5, 5 from dual
  ),

  -- test data without nulls
  clear_nulls as (
    select id, nvl(val, 0) val from virt_table
  ),
  -- comparison of test data
  compare( count_rows, count_val, sum_val, avg1_val, avg2_val, avg3_val, commentary ) as (
    -- with nulls
    select
      count(*),
      count(val),
      sum(val),
      avg(val),
      sum(val) / count(val),
      sum(val) / count(*),
      'with nulls' commentary
    from virt_table
    union all
    -- without nulls
    select
      count(*),
      count(nvl(val, 0)),
      sum(nvl(val, 0)),
      avg(nvl(val, 0)),
      sum(nvl(val, 0)) / count(nvl(val, 0)),
      sum(nvl(val, 0)) / count(*),
      'manual cleaning of nulls' commentary
    from virt_table
    union all
    select
      count(*),
      count(val),
      sum(val),
      avg(val),
      sum(val) / count(val),
      sum(val) / count(*),
      'general cleaning of nulls' commentary
    from clear_nulls
  )
select * from compare






Приведу для примера и поведение аналитических функций с пустыми значениями.

with
  -- emulation of test table
  virt_table( id, val ) as (
    select 1, 1 from dual union all
    select 2, 2 from dual union all
    select 3, null from dual union all
    select 4, null from dual union all
    select 5, 5 from dual
  ),
  -- test data without nulls
  clear_nulls as (
    select id, nvl(val, 0) val from virt_table
  ),
  -- for testing analytical functions with nulls
  test( count_rows, count_val, sum_val, avg_val ) as (
    select
      count(*) over (),
      count(val) over (),
      sum(val) over (),
      avg(val) over ()
    from virt_table
    union all
    select
      count(*) over (),
      count(val) over (),
      sum(val) over (),
      avg(val) over ()
    from clear_nulls
  )
select * from test



Для удобства результаты аналитических функций разделены красной линией (верхняя часть - с пустыми значениями, нижняя - без пустых значений). Разница налицо. Поэтому общий вывод таков, необходимо помнить о таких аномалиях и быть очень внимательными с такого рода неопределенного состояния полями. Стоит также отметить, что наличие таких полей в выборках определенных запросов также может приводить к невозможности выполнения последующих подзапросов. Вот один из классических примеров, который можно встретить при работе с иерархическими таблицами. Рассмотрим следующий запрос

with
  -- emulation of test table
  nodes( node, name, parent ) as (
    select 1, 'root', null from dual union all
    select 2, 'node2', 1 from dual union all
    select 3, 'node3', 2 from dual union all
    select 4, 'node4', 3 from dual union all
    select 5, 'node5', 4 from dual
  ),
  -- get path of nodes -- /root/node2/node3/...
  get_nodes_path as (
    select level, sys_connect_by_path ( name, '/' ) path
    from nodes
    connect by prior node = parent
    start with parent is null
  ),
  -- get just root
  get_root as (
    select node, name, parent
    from nodes
    where parent is null
  ),
  -- get just leaf -- wrong variant
  get_leaf_wrong as (
    select node, name, parent
    from nodes
    where node not in (
      select parent
      from nodes
    )
  ),
  -- get just leaf -- correct variant
  get_leaf_correct as (
    select node, name, parent
    from nodes
    where node not in (
      select parent
      from nodes
      where parent is not null
    )
  )
select * from get_nodes_path



Как видно, мы имеем дело с простым деревом. Теперь о классическом примере. Допустим, нам нужно найти все концевые узлы нашего дерева, точнее - листья. В приведенном выше запросе для этого предусмотрены два вложенных представления - get_leaf_wrong и get_leaf_correct. Вроде бы, если обратиться к представлению get_leaf_wrong то мы должны получить ожидаемые листья дерева, но здесь есть нюанс, так как в выборке 

select parent 
from nodes

содержится значение NULL (присущее корневому узлу), которое нельзя сравнивать с другими значениями, на выходе мы получим пустой результат. Если же исключить строку с данным неопределенным значением, что сделано в представлении - get_leaf_correct, тогда будет получен правильный ответ

Всем хорошего дня :)

Oracle. Арифметика дат

В Oracle как и в других системах управления базами данных с датами можно производить арифметические действия. Стоит только помнить, что сложение и вычитание с числовыми значениями происходит по-умолчанию в контексте дней. То есть выражение вида

select sysdate + 1 from dual

на выходе даст текущую дату увеличенную ровно на 1 день, поэтому, результат разницы между датами будет исчисляться днями, это и понятно - date1 + N = date2, откуда date2 - date1 = N. Складывать даты между собой запрещено, так как это не имеет смысла. Если нужны другие контексты (месяцы, года, секунды и прочее), тогда прибегают к иному способу

select sysdate ± interval '1' year from dual
select sysdate ± interval '1' month from dual
select sysdate ± interval '1' day from dual
select sysdate ± interval '1' hour from dual
select sysdate ± interval '1' minute from dual
select sysdate ± interval '1' second from dual 
  
select sysdate ± numtodsinterval(1, 'day')
select sysdate ± numtodsinterval(1, 'hour')
select sysdate ± numtodsinterval(1, 'minute') select sysdate ± numtodsinterval(1, 'second')

select sysdate ± numtoyminterval(1, 'month')
select sysdate ± numtoyminterval(1, 'year')

Но разность дат по-умолчанию возвращается в днях, это стоит помнить. Приведу небольшой пример

with
  -- first reper date
  first_date( fd ) as (
    select to_date('01.01.2014 00:00:00', 'dd.mm.yyyy hh24:mi:ss') as fd
    from dual
  ),
  -- last reper date
  last_date( ld ) as (
    select to_date('02.01.2014 00:00:00', 'dd.mm.yyyy hh24:mi:ss') as ld
    from dual
  ),
  -- get the difference of the last and the first reper dates
  get_diff_dates( diff ) as (
    select abs((select ld from last_date) - (select fd from first_date))
    from dual
  )
select
  diff days,
  diff * 24 hours,
  diff * 24 * 60 minutes,
  diff * 24 * 60 * 60 seconds
from get_diff_dates



Вообще для манипуляции с датами в Oracle есть и другие возможности, с которыми лучше всего ознакомиться самостоятельно в документации. Всем успехов.

Unix/Linux. Online построитель команды find

http://ru.clihelper.com/find/

Oracle. внешние и внутренние соединения, декартово произведение таблиц

Общая схема соединений представлена на следующем рисунке



Для рассмотрения примеров я буду использовать внутренние представления (f1, f2) объединенные во фразе with.
Итак, левое полуоткрытое соединение. Дополнение правой таблицы отсутствующих сравниваемых значений пустыми значениями (NULL), все значения правой таблицы, которые отсутствуют в левой таблице отбрасываются

with
  f1( a, b ) as (
    select 1 as a, 'type 1' as b from dual
    union all
    select a + 1, b
    from f1
    where a < 10
  ),
  f2( a, b ) as (
    select 5 as a, 'type 2' as b from dual
    union all
    select a + 1, b
    from f2
    where a < 15
  )
select f1.a, f1.b, f2.a, f2.b
from f1 left outer join f2
     on f1.a = f2.a
order by f1.a nulls first


данный запрос в версиях ниже 9 записывается немного иначе

with
  f1( a, b ) as (
    select 1 as a, 'type 1' as b from dual
    union all
    select a + 1, b
    from f1
    where a < 10
  ),
  f2( a, b ) as (
    select 5 as a, 'type 2' as b from dual
    union all
    select a + 1, b
    from f2
    where a < 15
  )
select f1.a, f1.b, f2.a, f2.b
from f1, f2
where f1.a = f2.a (+)
order by f1.a nulls first


Правое полуоткрытое соединение. Дополнение левой таблицы отсутствующих сравниваемых значений пустыми значениями (NULL), все значения левой таблицы, которые отсутствуют в правой таблице отбрасываются

with
  f1( a, b ) as (
    select 1 as a, 'type 1' as b from dual
    union all
    select a + 1, b
    from f1
    where a < 10
  ),
  f2( a, b ) as (
    select 5 as a, 'type 2' as b from dual
    union all
    select a + 1, b
    from f2
    where a < 15
  )
select f1.a, f1.b, f2.a, f2.b
from f1 right outer join f2
     on f1.a = f2.a
order by f1.a nulls first



на старый лад (версия ниже 9)

with
  f1( a, b ) as (
    select 1 as a, 'type 1' as b from dual
    union all
    select a + 1, b
    from f1
    where a < 10
  ),
  f2( a, b ) as (
    select 5 as a, 'type 2' as b from dual
    union all
    select a + 1, b
    from f2
    where a < 15
  )
select f1.a, f1.b, f2.a, f2.b
from f1, f2
where f1.a (+) = f2.a
order by f1.a nulls first


Полное открытое соединение. Дополнение отсутствующих сравниваемых значений пустыми значениями (NULL) и в левой, и в правой таблицах. Никакие значения таблиц не теряются

with
  f1( a, b ) as (
    select 1 as a, 'type 1' as b from dual
    union all
    select a + 1, b
    from f1
    where a < 10
  ),
  f2( a, b ) as (
    select 5 as a, 'type 2' as b from dual
    union all
    select a + 1, b
    from f2
    where a < 15
  )
select f1.a, f1.b, f2.a, f2.b
from f1 full outer join f2
     on f1.a = f2.a
order by f1.a nulls last


Внутреннее закрытое соединение. По результату схоже с естественным соединением

with
  f1( a, b ) as (
    select 1 as a, 'type 1' as b from dual
    union all
    select a + 1, b
    from f1
    where a < 10
  ),
  f2( a, b ) as (
    select 5 as a, 'type 2' as b from dual
    union all
    select a + 1, b
    from f2
    where a < 15
  )
select f1.a, f1.b, f2.a, f2.b
from f1 inner join f2
     on f1.a = f2.a
order by f1.a nulls last




Получение декартова произведения таблиц

with
  f1( a, b ) as (
    select 1 as a, 'type 1' as b from dual
    union all
    select a + 1, b
    from f1
    where a < 10
  ),
  f2( a, b ) as (
    select 5 as a, 'type 2' as b from dual
    union all
    select a + 1, b
    from f2
    where a < 15
  )
select f1.a, f1.b, f2.a, f2.b
from f1, f2


или (более предпочтительно)

with
  f1( a, b ) as (
    select 1 as a, 'type 1' as b from dual
    union all
    select a + 1, b
    from f1
    where a < 10
  ),
  f2( a, b ) as (
    select 5 as a, 'type 2' as b from dual
    union all
    select a + 1, b
    from f2
    where a < 15
  )
select f1.a, f1.b, f2.a, f2.b
from f1 cross join f2


если для декартова произведения таблиц наложить условия, тогда получим
внутреннее закрытое соединение таблиц

with
  f1( a, b ) as (
    select 1 as a, 'type 1' as b from dual
    union all
    select a + 1, b
    from f1
    where a < 10
  ),
  f2( a, b ) as (
    select 5 as a, 'type 2' as b from dual
    union all
    select a + 1, b
    from f2
    where a < 15
  )
select f1.a, f1.b, f2.a, f2.b
from f1 cross join f2
where f1.a = f2.a


Стоит отметить, что допускается в синтаксисе внутреннего inner закрытого соединения

from Table1 t1 inner join Table2 t2
         on t1.id = t2.id

опускать тип соединения

from Table1 t1 join Table2 t2
         on t1.id = t2.id

Также сказанное справедливо и для внешних outer соединений (полного, левого полуоткрытого и правого полуоткрытого).

full outer join -> full join
left outer join -> left join
right outer join -> right join

Отличительная особенность декартова произведения — вероятный большой объем результата, а значит более высокая нагрузка на сервер. Поэтому в соединениях необходимо использовать новый синтаксис, так как этот синтаксис требует указания сравнения значений разных столбцов друг с другом (если, конечно, это не natural inner join). Но если все же по каким-то причинам необходимо получить декартово произведение таблиц, как было упомянуто выше, правильней использовать cross join.

Oracle. Аналитические функции.

Общий синтаксис для использования аналитических функций следующий

имя_функции(<аргумент>,< аргумент >, . . . )
over (<конструкция_фрагментации><конструкция_упорядочения><конструкция_окна>)


Рассмотрим основные части данного синтаксиса.

1. <конструкция_фрагментации>

Синтаксис для задания конструкции фрагментации выглядит следующим образом

partition by выражение [, выражение] [, выражение]

Данная конструкция логически разбивает результирующее множество на N групп
по критериям, задаваемым выражениями фрагментации. Аналитические функции применяются к каждой группе независимо, - для каждой новой группы они сбрасываются. Если не указать конструкцию фрагментации, все результирующее множество считается
одной группой.

2. <конструкция_упорядочения>

Конструкция упорядочения имеет следующий синтаксис

order by выражение [asc | desc] [nulls first | nulls last]

Конструкция order by задает критерий сортировки данных в каждой группе (в каждом фрагменте). Это, несомненно, влияет на результат выполнения любой аналитической функции. При наличии (или отсутствии) конструкции order by аналитические функции вычисляются по-другому. Например.
- без конструкции order by

select ename, sal, avg(sal) over ()
from emp




- с конструкцией order by

select ename, sal, avg(sal) over (order by ename)
from emp



Здесь стоит отметить следующее, на самом деле наличие конструкции order by в вызове аналитической функции добавляет стандартную конструкцию окна — RANGE UNBOUNDED PRECEDING. Это означает, что для вычисления используется набор из всех предыдущих и текущей строки в текущем фрагменте. При отсутствии order by стандартным окном является весь фрагмент. То есть по-сути предыдущий запрос будет выглядеть следующим образом

select ename, sal, avg(sal) over (order by ename RANGE UNBOUNDED PRECEDING)
from emp


3. <конструкция_окна>

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

select deptno, ename, sal,
sum(sal) over (partition by deptno order by ename rows 2 preceding) sliding_total
from emp
order by deptno, ename



Можно создавать окна по двум критериям: по диапазону (RANGE) значений данных или по
смещению (ROWS) относительно текущей строки
. Использование конструкции range как было сказано ранее в  некоторых случаях используется неявно, RANGE UNBOUNDED PRECEDING например. Она требует брать все строки вплоть до текущей, в соответствии с порядком, задаваемым конструкцией order by. Следует помнить, что для использования окон
необходимо задавать конструкцию order by.

Окно определяется диапазоном строк, объединяемых в соответствии с заданным порядком.
Применять конструкцию range можно либо с числовыми выражениями (NUMBER), либо с выражениями, значением которого является дата (DATE). Еще одно ограничение для таких окон состоит в том, что в конструкции order by может быть только один столбец — диапазоны по природе своей одномерны. Нельзя задать диапазон в N-мерном пространстве. Пример.
Пусть необходимо выбрать зарплату каждого сотрудника и среднюю зарплату всех принятых на работу в течение 100 предыдущих дней, а также среднюю зарплату всех принятых на работу в течение 100 следующих дней. Соответствующий запрос будет выглядеть так:

select ename, hiredate, sal,
avg(sal) over (order by hiredate asc range 100 preceding) avg_sal_100_days_before,
avg(sal) over (order by hiredate desc range 100 preceding) avg_sal_100_days_after
from emp
order by hiredate desc


Помимо определения окна по диапазону (RANGE), также окна определяются и по количеству строк (ROWS). Для окон по строкам нет ограничений, присущих окнам по диапазону; данные могут быть любого типа и упорядочивать можно по любому количеству столбцов.
Например, пусть нужно вычислить среднюю зарплату для сотрудника и пяти принятых на работу до него и после него. Запрос можно записать следующим образом

select ename, hiredate, sal,
avg(sal) over (order by hiredate asc rows 5 preceding) avg_5_before,
avg(sal) over (order by hiredate desc rows 5 preceding) avg_5_after
from emp
order by hiredate


Зная как определяются окна (по диапазону или по количеству строк), рассмотрим как окончательно задаются окна. В простейшем случае, окно задается с помощью одной из трех следующих взаимоисключающих конструкций.

- UNBOUNDED PRECEDING.
Окно начинается с первой строки текущей группы и заканчивается текущей обрабатываемой строкой.

- CURRENT ROW.
Окно начинается (и заканчивается) текущей строкой.

- Числовое_выражение PRECEDING.
Окно начинается со строки за числовое_выражение строк до текущей, если оно задается по строкам, или со строки, меньшей по значению столбца, упомянутого в конструкции order by, не более чем на числовое выражение, если оно задается по диапазону.

- Числовое_выражение FOLLOWING.
Окно заканчивается (или начинается) со строки, через числовое_выражение строк после текущей, если оно задается по строкам, или со строки, большей
по значению столбца, упомянутого в конструкции order by, не более чем на числовое_выражение, если оно задается по диапазону.

Стоит отметить, что окно CURRENT ROW в простейшем виде, вероятно, никогда не используется, поскольку ограничивает применение аналитической функции одной текущей строкой, а для этого аналитические функции не нужны. В более сложном случае для окна задается также конструкция BETWEEN. В ней CURRENT ROW можно указывать в качестве начальной или конечной строки окна. Начальную и конечную строку окна в конструкции BETWEEN можно задавать с использованием любой из перечисленных выше конструкций. Например, можно  задать  окно  так,  что  обрабатываемая  строка  не  будет
последней, а окажется где-то в середине окна

select ename, hiredate,
first_value(ename) over (order by hiredate asc range between 100 preceding and 100 following),
last_value(ename) over (order by hiredate asc range between 100 preceding and 100 following)
from emp
order by hiredate asc


В данном запросе дополнительно использованы функции first_value() и last_value(), которые возвращают первое значений текущего окна и последнее значение также текущего окна, соответственно, в то время как диапазон окна ограничен слева текущая скользящая дата - 100 дней и справа к текущей скользящей дате + 100 дней, в этом и состоит смысл выражения - between 100 preceding and 100 following.

select
  level,
  count(*) over (order by level asc rows 2 preceding) asc_count,
  count(*) over (order by level desc rows 2 preceding) desc_count
from dual
connect by level <= 10
order by level


Окно rows 2 preceding, как видно из результата запроса, содержит от 1 до 3 строк (это определяется тем, как далеко текущая строка находится от начала группы). Для первой строки группы имеем значение 1 (предыдущих строк нет). Для следующей строки в группе таких строк 2. Наконец, для третьей и далее строк значение count(*) остается постоянным, поскольку мы считаем только текущую строку и две предыдущие.


select
  n,
  sum(n) over (order by days range 2 preceding) n_sum,
  days
from (
  select
     level n,
     ( to_date('10.01.2014', 'dd.mm.yyyy') + (level - 1) ) days
  from dual
  connect by level <= 10
)
order by days


В данном случае идет суммирование в пределах окна, диапазон которого составляет скользящий день и 2 предыдущих дня


select
  ename,
  sal,
  rank() over (order by sal) rank,
  dense_rank() over (order by sal) dens_rank,
  row_number() over (order by sal) row_number
from emp
order by sal


Данный запрос демонстрирует работу ранжирующих функций rank(), dense_rank() и row_number() по окладам работников. Обратите внимание на поведение данных функций в строках с одинаковыми значениями окладов.


select
  ename,
  deptno,
  sal,
  rank() over (partition by deptno order by sal) rank,
  dense_rank() over (partition by deptno order by sal) dens_rank,
  row_number() over (partition by deptno order by sal) row_number
from emp
order by deptno


Можно предыдущий запрос фрагментировать по отделам, то есть разбить на группы по отделам, в пределах которых будут работать аналитические функции, которые при выходе за пределы групп будут сбрасывать результаты. Результат представлен ниже



Рассмотрим пример применения функций lag() и lead(). Для того, чтобы можно было обращаться в текущей строке к предыдущим строкам, необходимо использовать функцию - lag(). Синтаксис ее таков

lag(поле_для_обращения, смещение, значение_для_замещения_null
over (partition by выражение order by выражение)

поле_для_обращения - поле, по которому нужно просматривать значения;
смещение - смещенная строка, с которой просматривается поле, по-умолчанию равно 1, если проставить 0, тогда будет просматриваться текущее поле;
значение_для_замещения_null - по-умолчанию равно null, в случае отсутствия значения в просматриваемом поле, возвращает данное значение. Здесь стоит отметить, что подставляемое значение должно быть того же типа, что и просматриваемое поле;
для данной функции обязательно использование order by

with
  main as (
    select empno, ename, job, mgr, hiredate, sal, comm, deptno, rownum
    from emp
    order by sal
  ),
  numerated_main as (
    select empno, ename, job, mgr, hiredate, sal, comm, deptno, rownum
    from main
  )
select
  ename,
  sal,
  lag(sal) over (order by rownum) previous_sal
from numerated_main



Или вот так

with
  main as (
    select empno, ename, job, mgr, hiredate, sal, comm, deptno, rownum
    from emp
    order by sal
  ),
  numerated_main as (
    select empno, ename, job, mgr, hiredate, sal, comm, deptno, rownum
    from main
  )
select
  ename,
  sal,
  lag(sal, 2, 0) over (order by rownum) previous_sal
from numerated_main




Логика и синтаксис работы функции lead() аналогичен предыдущей функции с одной лишь разницей: просмотр идет не назад, а вперед

with
  main as (
    select empno, ename, job, mgr, hiredate, sal, comm, deptno, rownum
    from emp
    order by sal
  ),
  numerated_main as (
    select empno, ename, job, mgr, hiredate, sal, comm, deptno, rownum
    from main
  )
select
  ename,
  sal,
  lead(sal, 2, 0) over (order by rownum) previous_sal
from numerated_main


Ниже для ознакомления приведен неполный список аналитических функций. Для более полной информации обращайтесь к документации. Всем успехов.

Аналитическая функцияОписание
AVG([DISTINCT | ALL] выражение)Используется  для  вычисления  среднего значения выражения  в  пределах  группы  и окна.  Для  поиска среднего  после  удаления  дублирующихся  значений  можно
указывать ключевое слово DISTINCT
CORR(выражение, выражение)Выдает  коэффициент  корреляции  для  пары  выражений, возвращающих  числовые  значения. В  статистическом  смысле,  корреляция  —  это  степень
связи  между  переменными.  Связь  между переменными означает,  что  значение  одной  переменной  можно  в определенной степени предсказать по значению другой. Коэффициент  корреляции  представляет степень корреляции  в  виде  числа  в  диапазоне  от  -1  (высокая обратная корреляция) до 1 (высокая корреляция).
Значение 0 соответствует отсутствию корреляции
COUNT([DISTINCT][*] [выражение])Эта  функция  считает  строки  в  группах.  Если  указать  * или  любую  константу,  кроме  NULL,  функция  COUNT  будет считать  все  строки.  Если  указать  выражение,  функция COUNT  будет  считать  строки,  для  которых  выражение имеет  значение  не  NULL.  Можно  задавать  модификатор DISTINCT,  чтобы  считать  строки  в  группах  после удаления дублирующихся строк
COVAR_POP(выражение, выражение)Возвращает  ковариацию  генеральной  совокупности (population  covariance)  пары  выражений  с  числовыми значениями
COVAR_SAMP(выражение, выражение)Возвращает  выборочную  ковариацию  (sample  covariance) пары выражений с числовыми значениями
CUME_DISTВычисляет относительную позицию строки в группе. Функция CUME_DIST всегда возвращает число большее  0 и меньше или равное  1. Это число представляет "позицию" строки  в  группе  из  N  арок.  В  группе  из  трех  строк, например,  возвращаются  следующие  значения кумулятивного распределения: 1/3, 2/3 и 3/3
DENSE_RANKЭта  функция  вычисляет  относительный  ранг  каждой возвращаемой  запросом  строки  по  отношению  к  другим строкам,  основываясь  на  значениях  выражений  в конструкции  ORDER  BY.  Данные  в  группе  сортируются  в соответствии  с  конструкцией  ORDER  BY,  а  затем  каждой строке  поочередно  присваивается  числовой  ранг, начиная с 1. Ранг увеличивается при каждом изменении значений выражений,  входящих  в  конструкцию  ORDER  BY.  Строки  с одинаковыми  значениями  получают  один  и  тот  же  ранг (при  этом  сравнении  значения  NULL  считаются одинаковыми).  Возвращаемый  этой  функцией  "плотный" ранг дает ранговые значения без промежутков. Сравните с представленной далее функцией RANK
FIRST_VALUEВозвращает первое значение в группе

LAG(выражение,<смещение>,
<стандартное
значение>)
Функция  LAG  дает  доступ  к  другим  строкам результирующего  множества,  избавляя  от  необходимости выполнять  самосоединения.  Она  позволяет  работать  с курсором  как  с  массивом.  Можно  ссылаться  на  строки, предшествующие  текущей  строке  в  группе.  О  том,  как обращаться  к  следующим  строкам  в  группе,  см.  в описании функции LEAD. Смещение  -  это  положительное  целое  число  со стандартным значением 1 (предыдущая строка). Стандартное  значение  возвращается,  если  индекс выходит  за  пределы  окна  (для  первой  строки  группы будет возвращено стандартное значение)
LAST_VALUEВозвращает последнее значение в группе
LEAD(выpaжeниe,<смещение>,<стандартное
значение>)
Функция  LEAD  противоположна функции  LAG. Если функция LAG  дает  доступ  к  предшествующим  строкам  группы,  то функция  LEAD  позволяет  обращаться  к  строкам, следующим за текущей. Смещение  —  это  положительное  целое  число  со стандартным  значением  1  (следующая  строка). Стандартное  значение  возвращается,  если  индекс выходит  за  пределы  окна  (для  последней  строки  группы будет возвращено стандартное значение)
МАХ(выражение)
Находит  максимальное  значение  выражения  в  пределах окна в группе
МIN(выражение)Находит  минимальное  значение  выражения  в  пределах окна в группе
NTILE(выражение)Делит группу на фрагменты по значению выражения. Например,  если  выражение  =  4,  то  каждой  строке  в группе присваивается число от 1 до 4 в соответствии с фрагментом,  в  которую  она  попадает.  Если  в  группе  20 строк,  первые  5  получат  значение  1,  следующие  5  — значение  2  и  т.д.  Если  количество  строк  в  группе  не делится  на  значение  выражения  без  остатка,  строки распределяются  так,  что  ни  в  одном  фрагменте количество  строк  не  превосходит  минимальное количество в других фрагментах более чем на 1, причем дополнительные  строки  будут  в  группах  с  меньшими номера фрагмента. Например,  если  снова  выражение  =  4,  а  количество строк  =  21,  в  первом  фрагменте  будет  6  строк,  во втором и последующих - 5
PERCENT RANKАналогична  функции  CUME_DIST  (кумулятивное распределение).  Вычисляет  ранг  строки  в  группе  минус 1,  деленный  на  количество  обрабатываемых  строк  минус 1. Эта функция всегда возвращает значения в диапазоне от 0 до 1 включительно
RANKЭта  функция  вычисляет  относительный  ранг  каждой строки,  возвращаемой  запросом,  на  основе  значений выражений,  входящих  в  конструкцию  ORDER  BY.  Данные  в группе  сортируются  в  соответствии  с  конструкцией ORDER  BY,  а  затем  каждой  строке  поочередно присваивается  числовой  ранг,  начиная  с  1.  Строки  с одинаковыми  значениями  выражений,  входящих  в конструкцию  ORDER  BY,  получают  одинаковый  ранг,  но если  две  строки  получат  одинаковый  ранг,  следующее значение ранга пропускается. Если две строки получили ранг  1,  строки с рангом  2  не будет;  следующая строка в  группе  получит  ранг  3.  В  этом  отличие  от  функции DENSE_RANK, которая не пропускает значений
RATIO_TO_REPORT(выражение)Эта  функция  вычисляет  значение  выражение  / (SUM(выражение)) по строкам группы. Это дает процент,  который составляет значение текущей строки по отношению к SUM(выражение)
REGR_xxxxxxx(выражение,выражение)Эти  функции  линейной  регрессии  применяют  стандартную
линейную  регрессию  по  методу  наименьших  квадратов  к
паре  выражений.  Предлагается  9  различных  функций регрессии
ROW_NUMBERВозвращает  смещение  строки  по  отношению  к  началу
упорядоченной  группы.  Может  использоваться  для последовательной  нумерации  строк,  упорядоченных  по определенным критериям
STDDEV(выражение)Вычисляет  стандартное  (среднеквадратичное)  отклонение
(standard  deviation)  текущей  строки  по  отношению  к группе
STDDEV_POP(выражение)Эта  функция  вычисляет  стандартное  отклонение генеральной  совокупности  (population  standard deviation)  и  возвращает  квадратный  корень  из дисперсии  генеральной  совокупности  (population variance).  Она  возвращает  значение,  совпадающее  с квадратным корнем из результата функции VAR_POP
STDDEV_SAMP(выражение)Эта  функция  вычисляет  накопленное  стандартное отклонение  выборки  (cumulative  sample  standard deviation)  и  возвращает  квадратный  корень  выборочной дисперсии  (sample variance). Она возвращает значение, совпадающее  с квадратным корнем из результата функции
VAR_SAMP
SUM(выражение)Вычисляет общую сумму значений  выражения для группы
VAR_POP(выражение)Эта  функция  возвращает  дисперсию  генеральной совокупности  для  набора  числовых  значений  (значения NULL игнорируются)
VAR_SAMP(выражение)Эта  функция  возвращает  выборочную  дисперсию  длянабора  числовых  значений  (значения  NULL игнорируются)
VARIANCE(выражение)Возвращает  дисперсию  для  выражения.  Сервер  Oracle вычисляет дисперсию как:
- 0, если количество строк в группе = 1;            -VAR_SAMP, если количество строк в группе больше 1

Ubuntu Server/Apache2/PHP. Настройка для работы с htaccess

Начнем с установки. Перед установкой веб сервера обновим систему. Обновим список репозиториев и произведем поиск индексов обновленных версий программ, драйверов, ядра и всего прочего

    sudo apt-get update

Далее установим все доступные обновления

    sudo apt-get upgrade

После этого приступим к установке apache2 и php5

    sudo apt-get install apache2
    sudo apt-get install php5
   
Для проверки успешности установки веб-сервера введите в браузере - http://127.0.0.1
Сервер должен вернуть сообщение похожее на - "It`s work". Далее нужно включить поддержку
php5 в apache. Для этого установим необходимый пакет с последующим включением модуля:

    sudo apt-get install libapache2-mod-php5
    sudo a2enmod php5

Без этого пакета при обращении к вашему сайту (предполагается, что в корне лежит индексный php-файл), веб-сервер будет предлагать вам сохранить копию корневого php-файла.

Теперь займемся созданием нашего тестового сайта - test.ru

Во первых - определим корневую директорию нашего сайта. По умолчанию после установки apache2 в ubuntu server будет создан default сайт, с корнем в /var/www
В его недрах будет лежать тот самый html-файл, который возвращает сообщение о работоспособности нашего веб-сервера. Давайте займем его место. Для этого нужно перейти в раздел - /etc/apache2/sites-available:

    cd /etc/apache2/sites-av*

и далее создать файл конфигурации для нашего сайта (я использую редактор nano)

    sudo nano test
   
В нем пропишем на первых порах следующие настройки:

<VirtualHost *:80>
DocumentRoot /var/www/
ServerName test.ru
ServerAdmin test@gmail.com
<Directory /var/www/> 
    DirectoryIndex index.php 
    AllowOverride All
</Directory>
<FilesMatch "^\.ht">
    Order allow,deny
    Allow from all
    Satisfy All
</FilesMatch>
</VirtualHost>

Сохраняем внесенные настройки. Далее деактивируем первоначальный сайт и активируем наш тестовый сайт

    sudo a2dissite default && sudo a2ensite test.ru

Для предупреждения такого рода ошибки

apache2: Could not determine the server's fully qualified domain name, using 127.0.0.1 for ServerName

нужно либо в свой файл настроек, либо в /etc/apache2/httpd.conf, либо в /etc/apache2/conf.d, создать свой файл настроек, и вписать туда
  
    ServerName localhost

Я выбрал второй вариант. Также сюда можно поместить и такие настройки
   
    ## Для того, чтобы Apache интерпретировал php и не предлагал сохранить php-файл
    AddType application/x-httpd-php .php .phtml

    ## Установка кодировки UTF-8 по умолчанию
    AddDefaultCharset UTF-8
   
Но это можно будет сделать и в .htaccess. Далее внесем имя сайта в список хостов сервера

    sudo nano /etc/hosts

и впишем через пробел имя нашего сайта

    127.0.0.1 localhost test.ru
   
Далее очищаем директорию /var/www и создаем корневой php-файл - index.php - вот с таким содержимым:

  <?php
      phpinfo();
  ?>

Перезагружаем apache

    sudo /etc/init.d/apache2 restart

Пробуем обратиться по адресу http://test.ru в веб браузере и если все сделано правильно, на выходе должен появиться массив сообщений о текущих настройках php. Кстати файл настроек php лежит в директории (помним, что в нашем случае php собран как модуль apache)

    /etc/php5/apache2/php.ini

Далее создаем файл .htaccess в той же директории, где расположен index.php и добавляем в него следующие правила (так как в файле конфигурации в Directory указано - AllowOverride All, значит .htaccess будет переопределять все директивы apache)

   AddDefaultCharset utf8
    RewriteEngine On
    RewriteRule ^([a-zA-Z0-9]+)/(.*)$ index.php?path1=$1&var=2&param=%{QUERY_STRING} [L]
    RewriteRule ^([a-zA-Z0-9]*)$ index.php?path1=$1&var=1&param=%{QUERY_STRING} [L]
   
Для проверки работы данных правил добавим в index.php следующие строчки

    <?php
        echo 'test.ru';
        echo '</br>';
        print_r($_REQUEST);
    ?>
   
Теперь пробуем набрать в адресной строке браузера http://test.ru/tester/?p=16
Если все хорошо, значит на выходе должны получить следующее сообщение:

    test.ru
    Array ( [path] => tester [var] => 2 [param] => p=16 )

Если же будете получать 500 ошибку (internal server error), то либо вы допустили синтаксическую ошибку, либо у вас не задействован модуль mod_rewrite. Для диагностики можно попробовать поправить правила в htaccess следующим образом

    AddDefaultCharset utf8
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteRule ^([a-zA-Z0-9]+)/(.*)$ index.php?path1=$1&var=2&param=%{QUERY_STRING} [L]
        RewriteRule ^([a-zA-Z0-9]*)$ index.php?path1=$1&var=1&param=%{QUERY_STRING} [L]
    </IfModule>

Если данная ошибка пропадет, но правила все также не будут работать, значит у вас точно не включен нужный модуль. Включаем данный модуль

    sudo a2enmod rewrite

И снова проверяем работу правил htaccess. Все должно непременно заработать.
Если после настройки веб-сервера, что-то все-таки будет работать некорректно, стоит переустановить apache. Для его удаления введите команду

    sudo apt-get purge apache2 apache2-utils apache2.2-bin apache2-common

И не забудьте очистить/удалить следующие директории

    /etc/apache2
    /var/www
   
Также стоит отметить, если на сервере не поднят DNS-сервер, тогда обращение к нашему тестовому сайту будет напрямую через его ip-адрес - http://XX.XX.XX.XX, чтобы все-таки стучаться к нему по его имени, стоит либо поднять DNS-службу, либо прописать на конечном компе - откуда вы будете стучаться - в хостах соответствие ip сервера его имени

    XX.XX.XX.XX        test.ru

Для Win32/64 как правило данное соответствие расположено в директории

    C:\Windows\System32\drivers\etc\hosts

Всем удачи :)

Apache2/htaccess. Фильтрация ссылок

Небольшое лирическое отступление. Как известно, htaccess способен переопределять директивы apache в своей директории, а также в поддиректориях (если там нет своих htaccess). Это при условии, что в самом apache подключен соответствующий модуль mod_rewrite и разрешено переопределение директив. Также htaccess работает со статическими ссылками, преобразуя их в динамические по прописанным в нем правилам. В общем сие сказанное можно представить следующим рисунком.


Желтым цветом помечено само преобразование, а красным для наглядности выделен некий входной параметр, все зависит от логики вашего веб приложения.

Разработаем правила, по которым мы сможем определенным образом фильтровать ссылки:
- до первого разделителя будем отбирать относительные пути к разделам нашего тестового сайта (path);
- все остальное будем игнорировать, но не забудем и про параметры GET запросов (param).


Для случая http://test.ru/abc
RewriteRule ^([a-zA-Z0-9]*)$ index.php?path=$1&param=%{QUERY_STRING} [L]

Для случая http://test.ru/abc/ или http://test.ru/abc/def
RewriteRule ^([a-zA-Z0-9]+)/(.*)$ index.php?path=$1&param=%{QUERY_STRING} [L]

В итоге получаем:

RewriteEngine On
RewriteRule ^([a-zA-Z0-9]+)/(.*)$ index.php?path=$1&param=%{QUERY_STRING} [L]
RewriteRule ^([a-zA-Z0-9]*)$ index.php?path=$1&param=%{QUERY_STRING} [L]