Модераторы: skyboy

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> запрос для биллинга 
:(
    Опции темы
alligator
Дата 26.8.2015, 19:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 730
Регистрация: 28.1.2004

Репутация: нет
Всего: 1



Всем доброго времени суток, делаю небольшую биллинговую систему для офисной АТС
требуется помощь в составлении запроса
на текущий момент есть запрос:
Код

SELECT b.calldate,b.src, CONCAT(b.prefix, b.number) as dst , b.duration, b.billsec, b.billmin, b.trunk, td.price,  ROUND(CEIL(b.billmin)*td.price, 2) as bill, t.numbertype, i.ispname,sr.subregionname,r.regionname
FROM (SELECT h.calldate ,h.src,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 1, 3 ) AS prefix ,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 4, 7 ) AS number,h.duration,h.billsec , ROUND((CASE h.billsec WHEN 0 THEN 1 ELSE h.billsec END)/60, 2) as billmin,
REGEXP_REPLACE(dstchannel, "(?:SIP|OOH323|LOCAL|SCCP)/(?:FMPR|FMGL)?([a-zA-Z_]+)?.*", '\\1') as trunk
FROM  asteriskcdrdb.cdr h 
WHERE  calldate BETWEEN  '2015-05-01 00:00:00' AND  '2015-05-31 23:59:59' AND LENGTH( h.src ) <=3 AND LENGTH( h.dst ) >3 AND h.disposition='ANSWERED') as b 
inner join astcdr.codes  c on b.prefix=c.abcdef
inner join astcdr.zoneinfo zi on zi.uid=c.zoneinfouid
inner join astcdr.isp i on i.uid=zi.ispuid
inner join astcdr.numtype t on t.uid=c.type
inner join astcdr.region r on r.uid=zi.regionuid
inner join astcdr.subregion sr on sr.uid=zi.subregionuid
inner join astcdr.tariff tr on tr.trunk=b.trunk
inner join astcdr.`tariff-data` td on td.tuid=tr.uid and ........
WHERE  b.number between c.start and c.end and b.trunk='Beeline';

запрос выбирает данные с сервера и выдает сформированный ответ кто, куда , стоимость, страна и т.д.
столкнулся с проблемой при доработке запроса, надо выбрать нужную тарифную опцию по наиболее большому количеству совпадений с таблицей tariff-data на основании данных r.uid,sr.uid,i.uid , посредством гугла и других источников была сделана примерно такая конструкция:
Код

SELECT *, (if(FIND_IN_SET('8',isp),1,0)+if(FIND_IN_SET('131',region),1,0)+if(FIND_IN_SET('1844',subregion),1,0)) as matches from `tariff-data` ORDER BY matches DESC LIMIT 1

она работает только с ручной заменой данных внутри FIND_IN_SET ,как можно доработать основной запрос с этим или подобным функционалом?

Это сообщение отредактировал(а) alligator - 26.8.2015, 19:58
PM MAIL   Вверх
tzirechnoy
Дата 26.8.2015, 21:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1173
Регистрация: 30.1.2009

Репутация: 2
Всего: 16



Цитата
надо выбрать нужную тарифную опцию по наиболее большому количеству совпадений


Если вопрос в выборе номера тарифа -- то вписать его попросту в zoneinfo. Поскольку в примере запроса он только от записи в zoneinfo и зависит. В таком случае мне, правда, непонятно, почему Вы тариф называете тарифной опцыей --  тарифная опцыя по смыслу это что-то, что выставляет оператор при заведении абонента.

А если там действительно есть тарифные опцыи, разные для разных пользователей -- то всё равно вписать номер тарифа для направления в zoneinfo, а пользователю прописать номер его тарифной опцыи, и сделать полную таблицу, в которой содержалось бы дэкартово произведение всех тарифов по направлениям (их, дажэ с учётом всех имеющихся стран, всего в пределах двух сотен) и всех тарифных опцый пользователей (ну, их тожэ дажэ в крупных операторах в пределах нескольких сот) -- получится маленькая таблица с десятком тысяч записей о цэнах.
PM MAIL   Вверх
alligator
Дата 27.8.2015, 10:13 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 730
Регистрация: 28.1.2004

Репутация: нет
Всего: 1



tzirechnoy, вы предлагаете использовать zoneinfo, это подходит только для одного направления а у меня их больше 4, когда я только начинал делать данный запрос он как раз использовал zoneinfo.
Цитата(tzirechnoy @  26.8.2015,  22:18 Найти цитируемый пост)
А если там действительно есть тарифные опцыи, разные для разных пользователей -- то всё равно вписать номер тарифа для направления в zoneinfo, а пользователю прописать номер его тарифной опцыи, и сделать полную таблицу, в которой содержалось бы дэкартово произведение всех тарифов по направлениям (их, дажэ с учётом всех имеющихся стран, всего в пределах двух сотен) и всех тарифных опцый пользователей (ну, их тожэ дажэ в крупных операторах в пределах нескольких сот) -- получится маленькая таблица с десятком тысяч записей о цэнах. 

Не получиться сделать то что вы предлагаете, например у меня таблица zoneinfo содержит пример 14к записей * на кол-во направлений =4 и получим 56к записей

я решил привязываться дополнительно к направлению звонка о чем говорит строка приводящая строку в необходимый вид:
Код

REGEXP_REPLACE(dstchannel, "(?:SIP|OOH323|LOCAL|SCCP)/(?:FMPR|FMGL)?([a-zA-Z_]+)?.*", '\\1') as trunk

тоесть получаеться когда разбираю звонок у меня получается следующая информация посредством которой надо определить тариф, зону и стоимость звонка в зону.:
звонок на 903 123 45 67 относиться к направлению dst1, зоне Москва и МО(131), провайдер Вымпелком(8), подрегион и NULL

в общем :
dst1 , 131 , 8 , null

На основании полученной информации смотрим таблицу tariff в которой ищем тариф по конкретному направлению, затем получаем для тарифа список тарифицируемых зон в tariff-data и получаем что-то типа такого:

uid    tuid    name    price    isp    region    subregion    currency    description
1    1 [->]    Москва (фикс.)    0.58    NULL    50    NULL    NULL    NULL
2    1 [->]    ФСС (Москва,МО)    1.69    NULL    131,165    NULL    NULL    NULL
3    1 [->]    ФСС (Москва,МО) Вымпелком    1.28    8    131    NULL    NULL    NULL

Мне кажется что такое решение не раздувало бы базу, и было бы более оптимальным с точки зрения кол-ва записей.
Но, ввиду того что опыт работы с базами SQL небольшой, не могу утверждать как лучше сделать и собственно поэтому сюда и обратился....



Это сообщение отредактировал(а) alligator - 27.8.2015, 10:20
PM MAIL   Вверх
Akina
Дата 27.8.2015, 10:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20581
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 45
Всего: 454



Цитата(alligator @  27.8.2015,  11:13 Найти цитируемый пост)
 у меня таблица zoneinfo содержит пример 14к записей * на кол-во направлений =4 и получим 56к записей

Это копейки. Кабы речь шла о сотнях миллионов записей - вот тогда можно было бы кивать на их количество.

Цитата(alligator @  27.8.2015,  11:13 Найти цитируемый пост)
Мне кажется что такое решение не раздувало бы базу, и было бы более оптимальным с точки зрения кол-ва записей.

Оптимизировать размер ценой производительности и нормализации, усложняя логику, имхо глупо.


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
alligator
Дата 27.8.2015, 10:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 730
Регистрация: 28.1.2004

Репутация: нет
Всего: 1



Всех понял, поддерживаю, тогда может таблице tariff-data сделать вместо 3-х полей isp,region,subregion одно поле zones и вносить туда zoneinfo uid ,  а проверять FIND_IN_SET ,так будет оптимально?
Если да, то я затрудняюсь сказать какой длинны может получиться строка с zoneinfo uid для москвы к примеру, в какой тип можно выставить поле?


Это сообщение отредактировал(а) alligator - 27.8.2015, 10:54
PM MAIL   Вверх
Akina
Дата 27.8.2015, 11:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20581
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 45
Всего: 454



Цитата(alligator @  27.8.2015,  11:50 Найти цитируемый пост)
ожет таблице tariff-data сделать вместо 3-х полей isp,region,subregion одно поле zones и вносить туда zoneinfo uid

Звучит разумно.

Цитата(alligator @  27.8.2015,  11:50 Найти цитируемый пост)
а проверять FIND_IN_SET

А вот это - нет.
Нахрена где-то там собирать этот сет, чтобы потом его же парсить? почему нельзя по-человечески нормализовать данные и не плодить хитровывернутых геморроев?


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
alligator
Дата 27.8.2015, 11:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 730
Регистрация: 28.1.2004

Репутация: нет
Всего: 1



Цитата(Akina @  27.8.2015,  12:27 Найти цитируемый пост)
Цитата(alligator @  27.8.2015,  11:50 )
а проверять FIND_IN_SET

А вот это - нет.
Нахрена где-то там собирать этот сет, чтобы потом его же парсить? почему нельзя по-человечески нормализовать данные и не плодить хитровывернутых геморроев? 


Как я понимаю ваше предложение хранить каждый zoneinfo uid в отдельной строке по каждой зоне тарификации, правильно?
PM MAIL   Вверх
tzirechnoy
Дата 27.8.2015, 14:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1173
Регистрация: 30.1.2009

Репутация: 2
Всего: 16



Цитата
Не получиться сделать то что вы предлагаете, например у меня таблица zoneinfo содержит пример 14к записей * на кол-во направлений =4 и получим 56к записей


После этой фразы я дажэ понял, что Вы имели в виду, когда говорили, что не получится записать это в zoneinfo. Но с Вас таки довольно тяжэло понимать, так что лучшэ бы Вы приводили примеры реальных данных, на которых можно определить, что Вы имеете в виду и какие проблемы Вы видите.

И да, в zoneinfo я предлагал указывать как раз индэкс тарифа направления. Их, типично, штук 5 по РФ, возможно ещё 1-5 по своей зоне и штук 150 по остальному миру. Если  всё достаточно маньячно -- то будет около 100 по РФ (на каждый субъект) 150 по остальному миру. Если всё совсем маньячно, и надо описывать в каждом регионе несколько локальных зон -- то будет 400 по РФ. Как видите, никаких 15k нет и в помине -- хотя 15k направлений, действительно, можэт быть (можэт быть и большэ).

Ну и, 4 тарифных опцыи что на 500 тарифов по направлениям, что на 15k -- это совершэнно копеечная таблица, которая замечательно поместится в память (да и дажэ, вероятно, в кэш процэссора), и поиск в которой займёт максимум 100ns (десяток доступов к произвольной области паамяти, если она ещё не в кэшэ), а скорее и меньшэ.

PM MAIL   Вверх
Akina
Дата 27.8.2015, 14:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20581
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: 45
Всего: 454



Цитата(alligator @  27.8.2015,  12:47 Найти цитируемый пост)
ваше предложение хранить каждый zoneinfo uid в отдельной строке по каждой зоне тарификации, правильно? 

Конечно.
А ещё точнее - я бы предложил провести анализ как положено и полностью нормализовать всю структуру.


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
alligator
Дата 27.8.2015, 15:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 730
Регистрация: 28.1.2004

Репутация: нет
Всего: 1



Дополнил структуру, в итоге запрос получился таким:
Код

SELECT b.calldate,b.src, CONCAT(b.prefix, b.number) as dst , b.duration, b.billsec, b.billmin, b.trunk, td.price,  ROUND(CEIL(b.billmin)*td.price, 2) as bill, t.numbertype, i.ispname,s.subregionname,r.regionname
FROM (SELECT h.calldate ,h.src,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 1, 3 ) AS prefix ,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 4, 7 ) AS number,h.duration,h.billsec , ROUND((CASE h.billsec WHEN 0 THEN 1 ELSE h.billsec END)/60, 2) as billmin,
REGEXP_REPLACE(dstchannel, "(?:SIP|OOH323|LOCAL|SCCP)/(?:FMPR|FMGL)?([a-zA-Z_]+)?.*", '\\1') as trunk
FROM  asteriskcdrdb.cdr h 
WHERE  calldate BETWEEN  '2015-05-01 00:00:00' AND  '2015-05-31 23:59:59' AND LENGTH( h.src ) <=3 AND LENGTH( h.dst ) >3 AND h.disposition='ANSWERED') as b 
inner join astcdr.codes  c on b.prefix=c.abcdef
inner join astcdr.zoneinfo zi on zi.uid=c.zoneinfouid
inner join astcdr.isp i on i.uid=zi.ispuid
inner join astcdr.numtype t on t.uid=c.type
inner join astcdr.region r on r.uid=zi.regionuid
inner join astcdr.subregion s on s.uid=zi.subregionuid
inner join astcdr.tariff tr on tr.trunk=b.trunk
inner join astcdr.`tariff-data` td on td.tuid=tr.uid
inner join astcdr.`tariff-zones` tz on tz.zone=zi.uid and tz.tduid=td.uid
WHERE  b.number between c.start and c.end and b.trunk='Beeline' ORDER BY b.calldate DESC;

Запрос стал отрабатывать чуток быстрее, но все же недостаточно быстро на текущий момент 43 секунды,  до этого было 52 =(
Сервер на базе виртуальной машины 4 ядра Intel® Core™ i7-4820K CPU @ 3.70GHz\ 4gb ram \ HDD 7200 (физика не нагружена другими виртуалками)
Как можно оптимизировать запрос? уж совсем медленно работает  (
P.S. когда проверяю некоторые запросы отдельно то работает практически мгновенно, но когда в один делаю долго
Explain
user posted image
Во время выполнения запроса, процессор 100%
user posted image



Это сообщение отредактировал(а) alligator - 27.8.2015, 15:52
PM MAIL   Вверх
alligator
Дата 27.8.2015, 16:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 730
Регистрация: 28.1.2004

Репутация: нет
Всего: 1



Цитата(tzirechnoy @  27.8.2015,  15:16 Найти цитируемый пост)
После этой фразы я дажэ понял, что Вы имели в виду, когда говорили, что не получится записать это в zoneinfo. Но с Вас таки довольно тяжэло понимать, так что лучшэ бы Вы приводили примеры реальных данных, на которых можно определить, что Вы имеете в виду и какие проблемы Вы видите.

И да, в zoneinfo я предлагал указывать как раз индэкс тарифа направления. Их, типично, штук 5 по РФ, возможно ещё 1-5 по своей зоне и штук 150 по остальному миру. Если  всё достаточно маньячно -- то будет около 100 по РФ (на каждый субъект) 150 по остальному миру. Если всё совсем маньячно, и надо описывать в каждом регионе несколько локальных зон -- то будет 400 по РФ. Как видите, никаких 15k нет и в помине -- хотя 15k направлений, действительно, можэт быть (можэт быть и большэ).

Ну и, 4 тарифных опцыи что на 500 тарифов по направлениям, что на 15k -- это совершэнно копеечная таблица, которая замечательно поместится в память (да и дажэ, вероятно, в кэш процэссора), и поиск в которой займёт максимум 100ns (десяток доступов к произвольной области паамяти, если она ещё не в кэшэ), а скорее и меньшэ.



согласен что тяжело понимать, надо видеть всю структуру, выкладываю дамп
P.S для работы запроса необходима MariaDB
Если есть время глянуть и посоветовать как сделать лучше, мне бы это очень помогло......


Это сообщение отредактировал(а) alligator - 27.8.2015, 16:50
PM MAIL   Вверх
alligator
Дата 28.8.2015, 09:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 730
Регистрация: 28.1.2004

Репутация: нет
Всего: 1



Провел небольшие тесты
Тестировал так - 36 сек
Код

SELECT b.calldate,b.src, CONCAT(b.prefix, b.number) as dst = 36 сек


В таком виде - 19 сек
Код

SELECT b.calldate,b.src, CONCAT(b.prefix, b.number) as dst 
FROM (SELECT h.calldate ,h.src,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 1, 3 ) AS prefix ,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 4, 7 ) AS number,h.duration,h.billsec , ROUND((CASE h.billsec WHEN 0 THEN 1 ELSE h.billsec END)/60, 2) as billmin,
REGEXP_REPLACE(dstchannel, "(?:SIP|OOH323|LOCAL|SCCP)/(?:FMPR|FMGL)?([a-zA-Z_]+)?.*", '\\1') as trunk
FROM  asteriskcdrdb.cdr h 
WHERE  calldate BETWEEN  '2015-05-01 00:00:00' AND  '2015-05-31 23:59:59' AND LENGTH( h.src ) <=3 AND LENGTH( h.dst ) >3 AND h.disposition='ANSWERED') as b 
inner join astcdr.codes  c on b.prefix=c.abcdef
inner join astcdr.zoneinfo zi on zi.uid=c.zoneinfouid
inner join astcdr.isp i on i.uid=zi.ispuid
inner join astcdr.numtype t on t.uid=c.type
inner join astcdr.region r on r.uid=zi.regionuid
inner join astcdr.subregion s on s.uid=zi.subregionuid
WHERE  b.number between c.start and c.end and b.trunk='Beeline' ORDER BY b.calldate DESC;


так - 9 сек
Код

SELECT b.calldate,b.src, CONCAT(b.prefix, b.number) as dst 
FROM (SELECT h.calldate ,h.src,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 1, 3 ) AS prefix ,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 4, 7 ) AS number,h.duration,h.billsec , ROUND((CASE h.billsec WHEN 0 THEN 1 ELSE h.billsec END)/60, 2) as billmin,
REGEXP_REPLACE(dstchannel, "(?:SIP|OOH323|LOCAL|SCCP)/(?:FMPR|FMGL)?([a-zA-Z_]+)?.*", '\\1') as trunk
FROM  asteriskcdrdb.cdr h 
WHERE  calldate BETWEEN  '2015-05-01 00:00:00' AND  '2015-05-31 23:59:59' AND LENGTH( h.src ) <=3 AND LENGTH( h.dst ) >3 AND h.disposition='ANSWERED') as b 
inner join astcdr.codes  c on b.prefix=c.abcdef
inner join astcdr.zoneinfo zi on zi.uid=c.zoneinfouid
WHERE  b.number between c.start and c.end and b.trunk='Beeline' ORDER BY b.calldate DESC;


Добавил индексы + добавил (FORCE INDEX), убрал 1 join получилось 9 секунд (возможно сделать хотябы до 3 секунд?)
Код

SELECT SQL_NO_CACHE b.calldate,b.src, CONCAT(b.prefix, b.number) as dst , b.duration, b.billsec, b.billmin, b.trunk, td.price,  ROUND(CEIL(b.billmin)*td.price, 2) as bill, i.ispname,s.subregionname,r.regionname
FROM (SELECT h.calldate ,h.src,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 1, 3 ) AS prefix ,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 4, 7 ) AS number,h.duration,h.billsec , ROUND((CASE h.billsec WHEN 0 THEN 1 ELSE h.billsec END)/60, 2) as billmin,
REGEXP_REPLACE(h.dstchannel, "(?:SIP|OOH323|LOCAL|SCCP)/(?:FMPR|FMGL)?([a-zA-Z_]+)?.*", '\\1') as trunk
FROM  asteriskcdrdb.cdr h FORCE INDEX (calldate)
WHERE  h.calldate BETWEEN  DATE'2015-05-01' AND  DATE'2015-05-31' AND LENGTH( h.src ) <=3 AND LENGTH( h.dst ) >3 AND h.disposition='ANSWERED') as b 
inner join astcdr.codes  c on b.prefix=c.abcdef
inner join astcdr.zoneinfo zi on zi.uid=c.zoneinfouid
inner join astcdr.isp i on i.uid=zi.ispuid
inner join astcdr.region r on r.uid=zi.regionuid
inner join astcdr.subregion s on s.uid=zi.subregionuid
inner join astcdr.tariff tr on tr.trunk=b.trunk
inner join astcdr.`tariff-data` td on td.tuid=tr.uid
inner join astcdr.`tariff-zones` tz on tz.zone=zi.uid and tz.tduid=td.uid
WHERE  b.number between c.start and c.end and b.trunk='Beeline' ORDER BY b.calldate DESC


Это сообщение отредактировал(а) alligator - 28.8.2015, 11:50
PM MAIL   Вверх
tzirechnoy
Дата 28.8.2015, 21:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1173
Регистрация: 30.1.2009

Репутация: 2
Всего: 16



Что-то у меня, как это часто бывает при встрече с MySQL (а последние 15 лет я стараюсь встречаться с ним поменьшэ, но всё-таки периодически приходится), цэнзурные слова закончились. Запрос -- тривиален, если бы не баги оптимизатора мыскля, оно бы из памяти за миллисекунды отрабатывало. Если бы хотя бы spatial index работали, можно было бы через них заставить, никуда бы не делось.

Я ещё подумаю, что можно предпринять. Наверняка что-нибудь придумается. В концэ концов можно попытаться codes в MyISAM выкинуть, и сделать spatial index и симитировать геометрию -- всё равно эта таблица статичная и в транзакцыях не участвует. В общем, подумаю.

Но можэт ты, раз только начал -- возьмёшь postgres? Я понимаю, опыта у окружающих мало, и понимаю, что если переносить в лоб большое приложэние MySQL -- то встретятся и замедления и всё, но он ужэ почти 20 лет в среднем лучшэ, и в нём добиться нетривиального результата в среднем проще, чем в MySQL примерно во всех областях, в которых MySQL применяется.
PM MAIL   Вверх
tzirechnoy
Дата 29.8.2015, 11:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1173
Регистрация: 30.1.2009

Репутация: 2
Всего: 16



Ага, нашёл. Кривизну оптимизатора можно нагнуть с помощью хранимки.
Создаю функцыю:
Код

delimiter //
CREATE FUNCTION get_cuid(prefix smallint(3), num int(7))
RETURNS int(15)
DETERMINISTIC
READS SQL DATA
BEGIN
  DECLARE maxstart int(7);
  DECLARE res int(15);
  SELECT max(cdir.start)
     FROM codes cdir
     WHERE cdir.abcdef = prefix AND cdir.start < num
  LIMIT 1
  INTO maxstart;
  SELECT uid
    FROM codes
    WHERE codes.abcdef = prefix AND codes.start = maxstart
  LIMIT 1
  INTO res;
  RETURN res;
END
//
delimiter ;


Потом таким запросом выбираю:
Код

SELECT b.calldate, b.src, CONCAT(b.prefix, b.number) AS dst, b.duration,
       b.billsec, b.billmin, b.dstchannel, t.numbertype, i.ispname,
       s.subregionname, r.regionname,
       (b.prefix=c.abcdef AND b.number BETWEEN c.start AND c.end)
           AS code_is_valid
   FROM
    (SELECT h.calldate, h.src,
            SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 1, 3 )
               AS prefix,
            SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 4, 7 )
               AS number,
            h.duration, h.billsec,
            ROUND((CASE h.billsec WHEN 0 THEN 1 ELSE h.billsec END)/60, 2)
               AS billmin,
            dstchannel,
            (SELECT get_cuid(prefix,number))
               AS codeuid
            FROM  cdr h
            WHERE  calldate BETWEEN  '2015-05-01 00:00:00'
                                     AND  '2015-05-31 23:59:59'
                   AND LENGTH( h.src ) <=3
                   AND LENGTH( h.dst ) >3
                   AND h.disposition='ANSWERED'
                   AND h.dstchannel LIKE '%/Beeline%'
     ) AS b
     INNER JOIN codes c ON b.codeuid=c.uid
     INNER JOIN zoneinfo zi ON zi.uid=c.zoneinfouid
     INNER JOIN isp i ON i.uid=zi.ispuid
     INNER JOIN numtype t ON t.uid=c.type
     INNER JOIN region r ON r.uid=zi.regionuid
     INNER JOIN subregion s ON s.uid=zi.subregionuid
   ORDER BY b.calldate DESC


И всё ужэ нормально работает. Ну, как нормально, без хранимой оно было бы в два раза быстрее, но хоть так.

Да, поскольку у меня сейчас нет MariaDB -- то сменил этот регексп на простой LIKE, это понятно как вернуть обратно. И добавил поле code_is_valid -- поскольку реально можэт оказаться, что номер не попадает в код. По хорошэму, ещё надо или в get_cuid получать какой-нибудь cuid дажэ если нет такого префикса ABC/DEF или сделать там LEFT OUTER JOIN вместо INNER JOIN на все остальные таблицы.

А, и привёл всё в одну базу, я ещё запощу по этому поводу когда соберусь с мыслями.

И да, тройной индэкс для abcdef, start, end безсмысленен -- можно оставить только первые два поля. То есть третье вообще ни в каких логичных случаях использоваться не будет.

И такое использование BETWEEN для времени не всегда корректно (зависит от СУБД) -- лучшэ писать >= 'сегодня 00:00' AND < 'завтра 00:00'

И то, что я использовал prefix и number в том жэ списке полей, в котором их определил -- это по-моему ANSI SQL standard не очень одобряет и не все базы понимают. Но переписывать как правильно мне лень, да и так вполне идиоматично получилось.

PM MAIL   Вверх
alligator
Дата 29.8.2015, 18:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 730
Регистрация: 28.1.2004

Репутация: нет
Всего: 1



Цитата(tzirechnoy @  29.8.2015,  12:17 Найти цитируемый пост)
И всё ужэ нормально работает. Ну, как нормально, без хранимой оно было бы в два раза быстрее, но хоть так.

сколько получилось время выполнения?

Цитата(tzirechnoy @  29.8.2015,  12:17 Найти цитируемый пост)
А, и привёл всё в одну базу, я ещё запощу по этому поводу когда соберусь с мыслями.

ок, выкладывай как будет возможность потестируем.

обновил архив с дампом.

потестил еще:
Цитата(tzirechnoy @  29.8.2015,  12:17 Найти цитируемый пост)
И такое использование BETWEEN для времени не всегда корректно (зависит от СУБД) -- лучшэ писать >= 'сегодня 00:00' AND < 'завтра 00:00'

Код

WHERE  b.number >= c.start and b.number <= c.end and b.trunk='Beeline' ORDER BY b.calldate DESC

на моем запросе во втором select получилось 11 секунд

Цитата(tzirechnoy @  29.8.2015,  12:17 Найти цитируемый пост)
И да, тройной индэкс для abcdef, start, end безсмысленен -- можно оставить только первые два поля. То есть третье вообще ни в каких логичных случаях использоваться не будет.

убрал третье поле время с 9 секунд сразу поднялось до 36, пока вернул обратно....

это выполнилось мгновенно
Код

SELECT h.calldate ,h.src,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 1, 3 ) AS prefix ,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 4, 7 ) AS number,h.duration,h.billsec , ROUND((CASE h.billsec WHEN 0 THEN 1 ELSE h.billsec END)/60, 2) as billmin,
REGEXP_REPLACE(h.dstchannel, "(?:SIP|OOH323|LOCAL|SCCP)/(?:FMPR|FMGL)?([a-zA-Z_]+)?.*", '\\1') as trunk
FROM  asteriskcdrdb.cdr h FORCE INDEX (calldate)
WHERE  h.calldate BETWEEN  DATE'2015-05-01' AND  DATE'2015-05-31' AND LENGTH( h.src ) <=3 AND LENGTH( h.dst ) >3 AND h.disposition='ANSWERED'


добавляю к запросу 

Код


inner join astcdr.codes  c on b.prefix=c.abcdef
inner join astcdr.zoneinfo zi on zi.uid=c.zoneinfouid

Код

SELECT SQL_NO_CACHE b.calldate,b.src, CONCAT(b.prefix, b.number) as dst , b.duration, b.billsec, b.billmin, b.trunk
FROM (SELECT h.calldate ,h.src,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 1, 3 ) AS prefix ,SUBSTRING( RIGHT( LPAD( h.dst, 12,  '9' ) , 10 ) , 4, 7 ) AS number,h.duration,h.billsec , ROUND((CASE h.billsec WHEN 0 THEN 1 ELSE h.billsec END)/60, 2) as billmin,
REGEXP_REPLACE(h.dstchannel, "(?:SIP|OOH323|LOCAL|SCCP)/(?:FMPR|FMGL)?([a-zA-Z_]+)?.*", '\\1') as trunk
FROM  asteriskcdrdb.cdr h FORCE INDEX (calldate)
WHERE  h.calldate BETWEEN  DATE'2015-05-01' AND  DATE'2015-05-31' AND LENGTH( h.src ) <=3 AND LENGTH( h.dst ) >3 AND h.disposition='ANSWERED') as b 
inner join astcdr.codes  c on b.prefix=c.abcdef
inner join astcdr.zoneinfo zi on zi.uid=c.zoneinfouid

получилось 9 сек

Это сообщение отредактировал(а) alligator - 29.8.2015, 18:15
PM MAIL   Вверх
Ответ в темуСоздание новой темы Создание опроса
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Составление SQL-запросов | Следующая тема »


 




[ Время генерации скрипта: 0.1231 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.