select distinct p.code from elma.deals d join elma.deals_services siv on (d.__id=ANY(siv.deal)) join elma.service_parametrs p on (p.__id = ANY(siv.params)) join elma.companies k on (d._companies=k.__id) join elma.additional_agreements aa on (aa.__id = ANY(siv.additional_agreement)) join elma.aggrements a on (aa.contract_uid = a.__id) where p.type=3 AND aa.is_actual order by p.code qCode:#getTickCount()-request.startTickCount# select upper(trim(k.id_klienta)) as wz, k.__id::text as contragent_uid, k.__name as contragent, c.__name as responsible, c.email as responsible_email, c.__status_status as responsible_status from elma.companies k LEFT OUTER JOIN elma.comp_users_ext c on (k.responsible=c.__id) where k."__deletedAt" IS NULL AND k.id_klienta like qContragent:#getTickCount()-request.startTickCount# WITH status(status, status_id, probability_pc) AS (VALUES ('Закрыта неуспешно',7,0),('Закрыта успешно',6,100),('Договор подписан',5,100),('Договор на подписании',15,90),('Договор на согласовании',12,70), ('Тестирование',8,50),('Отправлено ТКП',10,20),('Внутреннее согласование',37,10),('Проработка решения',4,10),('Сбор потребностей',1,0) ) select upper(trim(k.id_klienta)) as wz, k.__id::text as contragent_uid, p.code, SUBSTRING(p.code FROM 1 FOR LENGTH(p.code) - 2) as sku_code, p.price, p.discount as component_discount, k.__name as contragent, a.__name as contract, a.__id::text as contract_uid, d.__name as deal, d.__id::text as deal_uid, siv.abstractive_service as abstract_service, siv.modifier, siv.user_description, siv.hash as line_key ,siv.date_nop as serviceStart, siv.date_end as serviceEnd, siv.discount as service_discount, siv.cnt as service_quantity /*,p.__name as line_key*/, p.ed_izm, p.name as component, p.type, p.cnt*coalesce(siv.cnt,1) as component_quantity, aa.__name as additional_agreement, aa.__id::text as additional_agreement_uid --, aa.is_actual ,siv.end_customer as end_customer_uid ,z.id_klienta as end_customer_wz ,z.__name as end_customer_name ,ft.free_tier ,s.probability_pc ,siv.__id::text as siv_uid from elma.deals d JOIN STATUS s ON (d.__status_status = s.status_id) join elma.deals_services siv on (d.__id=ANY(siv.deal)) --join elma.service_parametrs p on (p.__id = ANY(siv.params)) LEFT JOIN LATERAL unnest(siv.params) AS arr(param_id) ON TRUE JOIN elma.service_parametrs p ON (p.__id = arr.param_id) join elma.companies k on (d._companies=k.__id) join elma.additional_agreements aa on (aa.__id = ANY(siv.additional_agreement)) join elma.aggrements a on (aa.contract_uid = a.__id) left outer join elma.companies z on (siv.end_customer=z.__id) left outer join (values ('paas.s3.ceph.vol-m',1),('paas.s3.ceph.get-m',10),('paas.s3.ceph.put-m',1),('paas.s3.ceph.trf-m',100)) as ft(code,free_tier) ON (p.code=ft.code) where p.type IN (2,3) AND aa.is_actual AND siv.date_nop < AND (siv.date_end > OR siv.date_end IS NULL)/*****/ AND aa."__deletedAt" IS NULL AND a."__deletedAt" IS NULL AND d."__deletedAt" IS NULL AND siv."__deletedAt" IS NULL AND p."__deletedAt" IS NULL AND p.cnt <> 0 --похоже, только так можно отфильтровать паразитные строки с нулем AND k.id_klienta like AND p.code like --AND k.id_klienta='WZ01031' --AND k.id_klienta='WZ01112' order by k.id_klienta, p.code qSpec:#getTickCount()-request.startTickCount# select max(dt_load) as dt_load from elma.deals_services qSivAge:#getTickCount()-request.startTickCount# select upper(trim(m.clientid)) as wz, vdc.name as vdc, case WHEN vdc.name LIKE '%-v1cl1%' THEN 'iaas.ngc.i29' WHEN vdc.name LIKE '%-v1cl2%' THEN 'iaas.ngc.i31' WHEN vdc.name LIKE '%-v1cl4%' THEN 'iaas.ngc.a28' WHEN vdc.name LIKE '%-v1cl5%' THEN 'iaas.ngc.a45' WHEN vdc.name LIKE '%-v1cl6%' THEN 'iaas.ngc.i28' WHEN vdc.name LIKE '%-v1cl7%' THEN 'iaas.ngc.a40' -- *** смапить все кластера else '['||vdc.name||']' end as code, sum(cpu_used/cpu_speed)/4. as core_h, sum(mem_used)/1024/4. as gb_h from ngcloud_ru.capacity_resource join ngcloud_ru.vdc on capacity_resource.vdc_id=vdc.id join ngcloud_ru.tenant t on (t.id=vdc.tenant_id) LEFT JOIN LATERAL ( SELECT m.clientid FROM ngcloud_ru.tenant_metadata tm JOIN ngcloud_ru.metadata m ON tm.metadata_id = m.id WHERE tm.tenant_id = t.id ORDER BY tm.ts DESC, tm.metadata_id DESC LIMIT 1 ) m ON true where length(m.clientid) > 0 AND capacity_resource.timestamp >= AND capacity_resource.timestamp < AND m.clientid like group by m.clientid, vdc.name order by m.clientid, vdc.name; qComputing:#getTickCount()-request.startTickCount# select max(capacity_resource.timestamp) as dt_load from ngcloud_ru.capacity_resource qComputingAge:#getTickCount()-request.startTickCount# select upper(trim(m.clientid)) as wz, vdc.name as vdc, case WHEN vdc.name LIKE '%-v1cl1%' THEN 'iaas.ngc.i29' WHEN vdc.name LIKE '%-v1cl2%' THEN 'iaas.ngc.i31' WHEN vdc.name LIKE '%-v1cl4%' THEN 'iaas.ngc.a28' WHEN vdc.name LIKE '%-v1cl5%' THEN 'iaas.ngc.a45' WHEN vdc.name LIKE '%-v1cl6%' THEN 'iaas.ngc.i28' WHEN vdc.name LIKE '%-v1cl7%' THEN 'iaas.ngc.a40' -- *** смапить все кластера ELSE '['||vdc.name||']' END || CASE WHEN storage_profile_types.name ILIKE '%FASTSSD%' THEN '.fstssd' WHEN storage_profile_types.name ILIKE '%SSD%' THEN '.ssd' WHEN storage_profile_types.name ILIKE '%SAS%' THEN '.sas' WHEN storage_profile_types.name ILIKE '%SATA%' THEN '.sata' ELSE '.['|| storage_profile_types.name ||']' END as code, storage_profile_types.name as profile, sum("limit")/1024./4./ as gb_m_limit, -- 4 потому, что сбор раз в 15 минут sum(used)/1024./4./ as gb_m_used from ngcloud_ru.capacity_storage join ngcloud_ru.vdc on capacity_storage.vdc_id=vdc.id join ngcloud_ru.tenant t on t.id=vdc.tenant_id LEFT JOIN LATERAL ( SELECT m.clientid FROM ngcloud_ru.tenant_metadata tm JOIN ngcloud_ru.metadata m ON tm.metadata_id = m.id WHERE tm.tenant_id = t.id ORDER BY tm.ts DESC, tm.metadata_id DESC LIMIT 1 ) m ON true join ngcloud_ru.storage_profile_types on capacity_storage.storage_profile_types_id=storage_profile_types.id where length(m.clientid) > 0 AND capacity_storage.timestamp >= AND capacity_storage.timestamp < AND m.clientid like group by m.clientid, vdc.name, storage_profile_types.name order by m.clientid, vdc.name, storage_profile_types.name; qStorage:#getTickCount()-request.startTickCount# select max(capacity_storage.timestamp) as dt_load from ngcloud_ru.capacity_storage qStorageAge:#getTickCount()-request.startTickCount# select /* *** ниже дублирование кода агрегации - round ceil etc, обратить внимание при правке */ (sum(bucket_stat.usage_rgw_main_size_actual)/) as vol_B ,sum(bucket_stat.usage_rgw_main_size_actual)//1024/1024/1024 as vol_GB ,case when bucket_stat.placement_id = 1 then 'paas.s3.ceph' when bucket_stat.placement_id = 2 then 'paas.s3.cphc' else '['|| TO_CHAR(bucket_stat.placement_id,'FM9') ||']' end as code ,bucket_stat.placement_id ,'WZ'||lpad(bucket_stat.owner, 5, '0') as wz -- *** здесь не число, а строка, поэтому другая формула обратной сборки WZ ,bucket_info.name as bucket from s3billing.bucket_stat join s3billing.placement ON s3billing.placement.id=bucket_stat.placement_id join s3billing.bucket_info ON bucket_info.id=bucket_stat.bucket_id where bucket_stat.placement_id in (1,2) AND bucket_stat.timestamp_addition >= AND bucket_stat.timestamp_addition < AND 'WZ'||lpad(bucket_stat.owner, 5, '0') like /*выглядит очень неэффективным запросом*/ group by bucket_stat.owner, bucket_stat.placement_id, bucket_info.name order by bucket_stat.owner, bucket_stat.placement_id, bucket_info.name; qS3Vol:#getTickCount()-request.startTickCount# select max(timestamp_addition) as dt_load from s3billing.bucket_stat qS3VolAge:#getTickCount()-request.startTickCount# select sum( CASE WHEN (category.name LIKE 'get%' OR category.name LIKE 'head%' OR category.name LIKE 'options%' ) THEN ops ELSE 0 END ) as get ,(sum( CASE WHEN (category.name LIKE 'get%' OR category.name LIKE 'head%' OR category.name LIKE 'options%' ) THEN ops ELSE 0 END )/10000) as get_10k ,sum( CASE WHEN (category.name LIKE 'put%' OR category.name LIKE 'post%' OR category.name LIKE 'patch%' OR category.name LIKE 'list%' ) THEN ops ELSE 0 END ) as put ,(sum( CASE WHEN (category.name LIKE 'put%' OR category.name LIKE 'post%' OR category.name LIKE 'patch%' OR category.name LIKE 'list%' ) THEN ops ELSE 0 END )/10000) as put_10k ,(sum(bytes_sent)) as bytes_sent ,(sum(bytes_sent)/1024/1024/1024) as bytes_sent_GB ,case when bucket_info.placement_id = 1 then 'paas.s3.ceph' when bucket_info.placement_id = 2 then 'paas.s3.cphc' else '['|| TO_CHAR(bucket_info.placement_id,'FM9') ||']' end as code --*** зачем делать там ключ bigint ,bucket_info.placement_id --category.name , bucket_info.name as bucket ,'WZ'||lpad(user_info.name, 5, '0') as wz from s3billing.usage_bucket_by_user join s3billing.user_info on usage_bucket_by_user.user_id=user_info.id join s3billing.category on usage_bucket_by_user.category_id=category.id join s3billing.bucket_info on usage_bucket_by_user.bucketid=bucket_info.id join s3billing.placement on bucket_info.placement_id=placement.id where 1=1 AND usage_bucket_by_user.time >= AND usage_bucket_by_user.time < AND 'WZ'||lpad(user_info.name, 5, '0') like /*выглядит очень неэффективным запросом*/ group by user_info.name, bucket_info.placement_id, bucket_info.name order by user_info.name, bucket_info.placement_id, bucket_info.name; qS3OpsTrf:#getTickCount()-request.startTickCount# select max(time) as dt_load from s3billing.usage_bucket_by_user qS3OpsTrfAge:#getTickCount()-request.startTickCount# WITH card (model, cluster, code) as (VALUES ('A100', 'msk1-pub02-i31', 'iaas.ngc.i31.gpu80'), ('H100', 'msk1-pub07-a40', 'iaas.ngc.a40.gpuh100'), -- h200 нет в отчете ('RTX 4090', 'msk1-pcs05-a45', 'iaas.ngc.a45.gpu4090'), -- нет такого в каталоге ('RTX 5090', 'msk1-pcs05-a45', 'iaas.ngc.a45.gpu5090'), ('RTX 6000', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu6k'), ('nvidia_a16-16q', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu16'), ('nvidia_a16-16q', 'msk1-pub01-i29', 'iaas.ngc.i29.gpu16'), ('nvidia_a16-1b', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu1'), ('nvidia_a16-2q', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu2'), ('nvidia_a16-4q', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu4'), ('nvidia_rtx_pro_6000_blackwell_dc-4q', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu6k4') ) SELECT item.key AS gpu_model, c.code ,SUM(item.value::int*p.value)/12. AS gpu_h -- 5 minute sampling ,vm.obj_id, upper(trim(vm.client_id)) as WZ FROM gpu.vm CROSS JOIN LATERAL jsonb_each(vm.gpu) AS item(key, value) LEFT OUTER JOIN card c ON (item.key=c.model) JOIN gpu.vm_power_state p ON (vm.obj_uuid=p.obj_uuid) WHERE NOT vm.is_template AND p.ts >= AND p.ts < GROUP BY vm.client_id, item.key, c.code, vm.obj_id ORDER BY vm.client_id, item.key, c.code, vm.obj_id; qGpu:#getTickCount()-request.startTickCount# select max(dt_load) as dt_load from gpu.vm qGpuAge:#getTickCount()-request.startTickCount# select wz, code || '.vcpu' as code, core_h as raw_metric, core_h as metric, 'core-h' as unit, vdc as dimension1 from qComputing where code || '.vcpu' like union all select wz, code || '.ram', gb_h as raw_metric, gb_h as metric, 'GByte-h' as unit, vdc as dimension1 from qComputing where code || '.ram' like union all select wz, code, gb_m_used as raw_metric, gb_m_used as metric, 'GByte-m' as unit, vdc ||':'|| profile as dimension1 from qStorage where code like union all select wz, code || '.vol', vol_b as raw_metric, vol_gb as metric, 'GByte-m' as unit, bucket as dimension1 from qS3Vol where code || '.vol' like union all select wz, code || '.get', get as raw_metric, get_10k as metric, '10k' as unit, bucket as dimension1 from qS3OpsTrf where code || '.get' like union all select wz, code || '.put', put as raw_metric, put_10k as metric, '10k' as unit, bucket as dimension1 from qS3OpsTrf where code || '.put' like union all select wz, code || '.trf', bytes_sent as raw_metric, bytes_sent_gb as metric, 'GByte' as unit, bucket as dimension1 from qS3OpsTrf where code || '.trf' like union all select wz, code, gpu_h as raw_metric, gpu_h as metric, 'h' as unit, obj_id as dimension1 from qGpu where code like order by wz, code qUnifiedMetric:#getTickCount()-request.startTickCount# /*EXPLAIN PLAN FOR*/ SELECT coalesce(coalesce(c.wz, m.wz), s.wz) as wz s.wz as wzs c.wz as wzc m.wz as wzm s.end_customer_wz as wze coalesce(s.sku_code, m.code) as code s.code as s_code s.sku_code as sku_code m.code as m_code s.component_quantity s.service_quantity s.service_discount coalesce(s.free_tier,0) as free_tier m.dimension1 s.type m.unit s.ed_izm coalesce(s.service_quantity*(100.-s.service_discount)/100.*s.price*(100.-s.component_discount)/100., 0.) as discounted_price s.serviceStart s.serviceEnd s.line_key s.price s.component_discount m.raw_metric m.metric c.contragent c.contragent_uid c.responsible c.responsible_email c.responsible_status s.contract s.contract_uid s.additional_agreement s.additional_agreement_uid s.deal s.deal_uid s.abstract_service s.modifier s.component s.user_description CASE s.type WHEN 1 THEN 'install' WHEN 2 THEN 'fix' WHEN 3 THEN 'payg' ELSE NULL END as pricing_model FROM qUnifiedMetric m FULL JOIN qSpec s ON (m.wz=s.wz AND m.code=s.sku_code) -- сервис или компонент, но не цена с типом тарификации, суффикс типа тарификации отрезан LEFT JOIN qContragent c ON ((coalesce(s.wz,m.wz))=(c.wz)) -- пробелы в составе WZ стоили нескольких часов работы --(поскольку trim слева в джойне катастрофически замедляет работу, делаем чистку выше при формировании исходных резалтсетов) WHERE 1=1 AND EXISTS(select * from qSpec qs where qs.wz=m.wz AND qs.type=3) AND ( m.wz like OR s.wz like OR c.wz like ) --AND s.wz='WZ01031' --AND s.wz='WZ01762 ORDER BY coalesce(coalesce(c.wz, m.wz), s.wz), coalesce(s.code, m.code) qCharge:#getTickCount()-request.startTickCount# Отчет по PAYG Факт потребления по основной базе биллинга, спецификации из DWH. Без детализации по бакетам, VDC и т.д. Все запросы кэшированы на 25 минут.
Внимание! округление вверх при расчете метрик get, put S3. Расчетные суммы могут содержать погрешности округления, поэтому окончательный расчет стоимости (количество * цену) должен выполняться в бухгалтерской программе. Если у клиента больше 1 действующего допника с услугами PAYG, то данные в отчете могут быть замножены, в этом случае считать вручную
Даты актуальности пока проверять вручную. Обращать внимание на замножение строк.
Период с по Только PAYG checked/> WZ по вхождению, с регистром Код по вхождению
Часов в периоде: #hours# Строк в отчете: #qCharge.recordCount#
Актуальность данных: VCD Computing: class="err">>#dateFormat(qComputingAge.dt_load,'YYYY-MM-DD')# #timeFormat(qComputingAge.dt_load,'HH:mm:ss.lXX')# VCD Storage: class="err">>#dateFormat(qStorageAge.dt_load,'YYYY-MM-DD')# #timeFormat(qStorageAge.dt_load,'HH:mm:ss.lXX')# S3 хранение: class="err">>#dateFormat(qS3VolAge.dt_load,'YYYY-MM-DD')# #timeFormat(qS3VolAge.dt_load,'HH:mm:ss.lXX')# S3 операции и трафик: class="err">>#dateFormat(qS3OpsTrfAge.dt_load,'YYYY-MM-DD')# #timeFormat(qS3OpsTrfAge.dt_load,'HH:mm:ss.lXX')#
class="gray">
Услуга Артикул специф. Кол-во специф. Артикул метр. Детализация Кол-во метр. Free Tier. Опл. объем Ед.изм. Цена ₽ со скид., с НДС Стоимость ₽ с НДС     Компонент Польз. наим. GPL с НДС Скидка% Дата НОУ Дата КОУ Ключ строки Сырая метрика Приведен­ная метрика Цена ₽ до ск. с НДС Цена ₽ co ск. с НДС без окр. Service Quantity Service Discount Component Quantity Component Discount Договор Допник Сделка Конечник
#wz# #contragent# (сводка) ответственный: class="err">#responsible# #responsible_email#
#currentRow# #abstract_service#: #modifier# style="background:lightgreen;color:black;">#s_code# "err r""r">#component_quantity# #m_code# #dimension1# #request.roundSafe(metric,3)# #free_tier# #unit# [#ed_izm#] #gaussianRound(discounted_price,2)# #component# #user_description# #gaussianRoundSafe(price,2)# #component_discount# #dateFormat(serviceStart,'YYYY-MM-DD')# #dateFormat(serviceEnd,'YYYY-MM-DD')# #line_key# #request.roundSafe(raw_metric,3)# #request.roundSafe(metric,3)# #price# #discounted_price# #service_quantity# #service_discount# #component_quantity# #component_discount# #contract# #additional_agreement# #deal# #WZE#
Итого #code#: #round(final_metric_dimension_1,3)# #free_tier# #max(ceiling(final_metric_dimension_1) - free_tier, 0)# #unit# [#ed_izm#] #current_price# #gaussianRoundSafe(cost_dimension_1,2)#
Итого #wz# #contragent#: #accum_cost_svc#
string function gaussianRoundSafe(any num, numeric precision = 0, string default="") { return (isNumeric(arguments.num)) ? gaussianRound(arguments.num, arguments.precision) : arguments.default; } function gaussianRound(numeric number, numeric precision = 0) { var factor = 10 ^ precision; var scaledNumber = number * factor; var absScaled = abs(scaledNumber); var sign = sgn(scaledNumber); var floored = int(absScaled); // If it's not ending in exactly .5, use standard rounding or precision scaling if (absScaled - floored != 0.5) { return round(number * factor) / factor; } // If it is exactly .5, round towards the nearest even number if (floored % 2 != 0) { // Nearest even is UP return (floored + 1) * sign / factor; } else { // Nearest even is DOWN return floored * sign / factor; } }