الأطر الزمنية الرقيقة (العملات المشفرة ، الفوركس ، التبادلات)

منذ بعض الوقت ، كُلّفت بكتابة إجراء ينفذ من عروض أسعار سوق الفوركس (بشكل أدق ، بيانات الإطار الزمني).

بيان المشكلة: يتم إدخال البيانات على فاصل زمني من ثانية واحدة بهذا التنسيق:

  • اسم الأداة (رمز زوج USDEUR ، وما إلى ذلك) ،
  • التاريخ والوقت بتنسيق الوقت يونكس ،
  • القيمة المفتوحة (سعر المعاملة الأولى في الفترة) ،
  • قيمة عالية (السعر الأقصى) ،
  • قيمة منخفضة
  • قيمة الإغلاق (سعر آخر صفقة) ،
  • الحجم (الحجم أو حجم المعاملة).

من الضروري التأكد من إعادة حساب ومزامنة البيانات في الجداول: 5 ثوانٍ ، 15 ثانية ، دقيقة واحدة ، 5 دقائق ، 15 دقيقة ، إلخ.

يسمى تنسيق تخزين البيانات الموصوف OHLC أو OHLCV (مفتوح ، مرتفع ، منخفض ، إغلاق ، حجم). غالبًا ما يتم استخدامه ، يمكنك على الفور بناء مخطط "الشموع اليابانية" عليه.

الصورة

تحت الخفض ، وصفت جميع الخيارات التي يمكن أن أتوصل إليها ، وكيفية تخفيف (تكبير) البيانات المستلمة ، للتحليل ، على سبيل المثال ، القفزة الشتوية في سعر البيتكوين ، ووفقًا للبيانات المستلمة ، ستقوم على الفور بإنشاء مخطط "الشموع اليابانية" (في MS Excel هناك أيضًا مثل هذا المخطط ) في الصورة أعلاه ، تم إنشاء هذا الرسم البياني للإطار الزمني "شهر واحد" لأداة "bitstampUSD". يشير الجسم الأبيض للشمعة إلى زيادة في السعر في الفاصل الزمني ، أسود - انخفاض في السعر ، تشير الفتائل العلوية والسفلية إلى الحد الأقصى والحد الأدنى للأسعار التي تم الوصول إليها في الفاصل الزمني. الخلفية - حجم المعاملات. من الواضح أنه في ديسمبر 2017 اقترب السعر من 20 ألف.

سيتم تقديم الحل لمشغلي قاعدة بيانات ، لـ Oracle و MS SQL ، مما سيتيح ، بطريقة ما ، مقارنتهما لهذه المهمة المحددة (لن نعمم المقارنة بالمهام الأخرى).

ثم قمت بحل المشكلة بطريقة تافهة: حساب الترقق الصحيح في جدول مؤقت والمزامنة مع الجدول الهدف - حذف الصفوف الموجودة في الجدول الهدف ولكنها غير موجودة في الجدول المؤقت وإضافة صفوف موجودة في الجدول المؤقت ولكنها غير موجودة في الهدف. في ذلك الوقت ، كان العميل راضٍ عن الحل ، وأغلقت المهمة.

لكنني قررت الآن النظر في جميع الخيارات ، لأن الحل أعلاه يحتوي على ميزة واحدة - من الصعب تحسين حالتين في وقت واحد:

  • عندما يكون الجدول الهدف فارغًا وتحتاج إلى إضافة الكثير من البيانات ،
  • وعندما يكون الجدول المستهدف كبيرًا ، وتحتاج إلى إضافة البيانات في أجزاء صغيرة.

هذا يرجع إلى حقيقة أنه في الإجراء يجب عليك توصيل الجدول الهدف والجدول المؤقت ، وتحتاج إلى إرفاق الجدول الأكبر ، وليس العكس. في الحالتين أعلاه ، يتبادل الأكبر / الأصغر. سيقرر المُحسِّن ترتيب الاتصال بناءً على الإحصائيات ، وقد تكون الإحصائيات قديمة ، وقد يتم اتخاذ القرار بشكل غير صحيح ، مما سيؤدي إلى تدهور كبير في الأداء.

في هذه المقالة ، سأصف طرق التخفيف لمرة واحدة التي قد تكون مفيدة للقراء للتحليل ، على سبيل المثال ، قفزة الشتاء في سعر البيتكوين.

يمكن تنزيل إجراءات التخفيف على الإنترنت من github على الرابط الموجود أسفل المقالة.

إلى النقطة ... كانت مهمتي التخفيف من الإطار الزمني "ثانية واحدة" إلى المرحلة التالية ، ولكن هنا أفكر في التخفيف من مستوى المعاملة (في الجدول المصدر ، الحقول STOCK_NAME ، UT ، ID ، APRICE ، AVOLUME). لأن هذه البيانات صادرة عن bitcoincharts.com.
في الواقع ، يتم تنفيذ عملية الهلاك من مستوى المعاملة إلى مستوى "ثانية واحدة" بواسطة مثل هذا الأمر (يمكن بسهولة ترجمة عامل التشغيل إلى هلاك من مستوى "ثانية واحدة" إلى المستويات العليا):

على Oracle:

select 1 as STRIPE_ID , STOCK_NAME , TRUNC_UT (UT, 1) as UT , avg (APRICE) keep (dense_rank first order by UT, ID) as AOPEN , max (APRICE) as AHIGH , min (APRICE) as ALOW , avg (APRICE) keep (dense_rank last order by UT, ID) as ACLOSE , sum (AVOLUME) as AVOLUME , sum (APRICE * AVOLUME) as AAMOUNT , count (*) as ACOUNT from TRANSACTIONS_RAW group by STOCK_NAME, TRUNC_UT (UT, 1); 

تعمل الوظيفة avg () keep (dense_rank first order by UT، ID) على هذا النحو: نظرًا لأن الطلب هو GROUP BY ، يتم حساب كل مجموعة بشكل مستقل عن الآخرين. داخل كل مجموعة ، يتم فرز السلاسل حسب UT و ID ، مرقمة حسب dense_rank . نظرًا لأن الوظيفة الأولى تتبع ، يتم تحديد السطر حيث يُرجع dense_rank 1 (بمعنى آخر ، يتم تحديد الحد الأدنى) - يتم تحديد المعاملة الأولى خلال الفاصل الزمني. بالنسبة لهذا الحد الأدنى من UT ، ID ، إذا كان هناك عدة خطوط ، فسيتم النظر في المتوسط. ولكن في حالتنا ، سيكون هناك خط واحد مضمون (بسبب تفرد المعرف) ، وبالتالي يتم إرجاع القيمة الناتجة على الفور كـ AOPEN. من السهل ملاحظة أن الوظيفة الأولى تحل محل وظيفتين مجمعتين.

على MS SQL

لا توجد وظائف الأولى / الأخيرة (هناك first_value / last_value ، لكنها ليست كذلك). لذلك ، عليك توصيل الجدول بنفسه.

لن أعطي الطلب بشكل منفصل ، ولكن يمكنك رؤيته أدناه في إجراء dbo.THINNING_HABR_CALC . بالطبع ، بدون الأول / الأخير ، لن تكون أنيقة للغاية ، لكنها ستعمل.

كيف يمكن لمشغل واحد أن يحل هذه المشكلة؟ (هنا ، المصطلح "عامل واحد" لا يعني أن العامل سيكون واحدًا ، ولكن لن تكون هناك دورات "تسحب" البيانات على سطر واحد.)

سأذكر جميع الخيارات التي أعرفها لحل هذه المشكلة:

  1. SIMP (منتج ديكارت بسيط ، بسيط ،) ،
  2. CALC (حساب ، ترقق تكراري للمستويات العليا) ،
  3. الصينية (طريقة الصين ، طلب ضخم لجميع المستويات دفعة واحدة) ،
  4. UDAF (دالة التجميع المعرفة من قبل المستخدم) ،
  5. PPTF (دالة جدول متوازية ومتوازية ، حل إجرائي ، ولكن مع اثنين فقط من المؤشرات ، في الواقع ، عبارات SQL) ،
  6. MODE (نموذج ، عبارة MODEL) ،
  7. و IDEA (مثالي ، حل مثالي قد لا يعمل الآن).

في المستقبل ، سأقول أن هذه هي الحالة النادرة عندما يكون حل PPTF الإجرائي هو الأكثر فعالية في Oracle.

قم بتنزيل ملفات المعاملات من http://api.bitcoincharts.com/v1/csv
أوصي باختيار ملفات kraken *. ملفات localbtc * صاخبة للغاية - تحتوي على خطوط مشتتة وبأسعار غير واقعية. تحتوي جميع kraken * على حوالي 31 مليون معاملة ، أوصي باستبعاد krakenEUR من هناك ، ثم تصبح المعاملة 11M. هذا هو الحجم الأكثر ملاءمة للاختبار.

قم بتشغيل برنامج نصي في Powershell لإنشاء ملفات التحكم لـ SQLLDR لـ Oracle ولإنشاء طلب استيراد لـ MSSQL.

  # MODIFY PARAMETERS THERE $OracleConnectString = "THINNING/aaa@P-ORA11/ORCL" # For Oracle $PathToCSV = "Z:\10" # without trailing slash $filenames = Get-ChildItem -name *.csv Remove-Item *.ctl -ErrorAction SilentlyContinue Remove-Item *.log -ErrorAction SilentlyContinue Remove-Item *.bad -ErrorAction SilentlyContinue Remove-Item *.dsc -ErrorAction SilentlyContinue Remove-Item LoadData-Oracle.bat -ErrorAction SilentlyContinue Remove-Item LoadData-MSSQL.sql -ErrorAction SilentlyContinue ForEach ($FilenameExt in $Filenames) { Write-Host "Processing file: "$FilenameExt $StockName = $FilenameExt.substring(1, $FilenameExt.Length-5) $FilenameCtl = '.'+$Stockname+'.ctl' Add-Content -Path $FilenameCtl -Value "OPTIONS (DIRECT=TRUE, PARALLEL=FALSE, ROWS=1000000, SKIP_INDEX_MAINTENANCE=Y)" Add-Content -Path $FilenameCtl -Value "UNRECOVERABLE" Add-Content -Path $FilenameCtl -Value "LOAD DATA" Add-Content -Path $FilenameCtl -Value "INFILE '.$StockName.csv'" Add-Content -Path $FilenameCtl -Value "BADFILE '.$StockName.bad'" Add-Content -Path $FilenameCtl -Value "DISCARDFILE '.$StockName.dsc'" Add-Content -Path $FilenameCtl -Value "INTO TABLE TRANSACTIONS_RAW" Add-Content -Path $FilenameCtl -Value "APPEND" Add-Content -Path $FilenameCtl -Value "FIELDS TERMINATED BY ','" Add-Content -Path $FilenameCtl -Value "(ID SEQUENCE (0), STOCK_NAME constant '$StockName', UT, APRICE, AVOLUME)" Add-Content -Path LoadData-Oracle.bat -Value "sqlldr $OracleConnectString control=$FilenameCtl" Add-Content -Path LoadData-MSSQL.sql -Value "insert into TRANSACTIONS_RAW (STOCK_NAME, UT, APRICE, AVOLUME)" Add-Content -Path LoadData-MSSQL.sql -Value "select '$StockName' as STOCK_NAME, UT, APRICE, AVOLUME" Add-Content -Path LoadData-MSSQL.sql -Value "from openrowset (bulk '$PathToCSV\$FilenameExt', formatfile = '$PathToCSV\format_mssql.bcp') as T1;" Add-Content -Path LoadData-MSSQL.sql -Value "" } 

لنقم بإنشاء جدول معاملات على Oracle.

 create table TRANSACTIONS_RAW ( ID number not null , STOCK_NAME varchar2 (32) , UT number not null , APRICE number not null , AVOLUME number not null) pctfree 0 parallel 4 nologging; 

في Oracle ، قم بتشغيل ملف LoadData-Oracle.bat ، بعد إصلاح معلمات الاتصال مسبقًا في بداية البرنامج النصي Powershell.

أنا أعمل في جهاز افتراضي. استغرق تنزيل جميع ملفات المعاملات 11M في 8 ملفات kraken * (تخطيت ملف EUR) حوالي دقيقة واحدة.

وإنشاء وظائف ستقتطع التواريخ إلى حدود الفواصل:

 create or replace function TRUNC_UT (p_UT number, p_StripeTypeId number) return number deterministic is begin return case p_StripeTypeId when 1 then trunc (p_UT / 1) * 1 when 2 then trunc (p_UT / 10) * 10 when 3 then trunc (p_UT / 60) * 60 when 4 then trunc (p_UT / 600) * 600 when 5 then trunc (p_UT / 3600) * 3600 when 6 then trunc (p_UT / ( 4 * 3600)) * ( 4 * 3600) when 7 then trunc (p_UT / (24 * 3600)) * (24 * 3600) when 8 then trunc ((trunc (date '1970-01-01' + p_UT / 86400, 'Month') - date '1970-01-01') * 86400) when 9 then trunc ((trunc (date '1970-01-01' + p_UT / 86400, 'year') - date '1970-01-01') * 86400) when 10 then 0 when 11 then 0 end; end; create or replace function UT2DATESTR (p_UT number) return varchar2 deterministic is begin return to_char (date '1970-01-01' + p_UT / 86400, 'YYYY.MM.DD HH24:MI:SS'); end; 

فكر في الخيارات. أولاً ، يتم إعطاء رمز جميع الخيارات ، ثم النصوص البرمجية لبدء التشغيل والاختبار. أولاً ، تم وصف المهمة لـ Oracle ، ثم لـ MS SQL

الخيار 1 - SIMP (تافه)


يتم ضرب مجموعة المعاملات بالكامل في المنتج الديكارتي في مجموعة من 10 أسطر بأرقام من 1 إلى 10. وهذا ضروري للحصول على 10 أسطر من خط معاملة واحد مع اقتطاع التواريخ إلى حدود 10 فواصل زمنية.

بعد ذلك ، يتم تجميع الخطوط حسب رقم الفاصل الزمني والتاريخ المقتطع ، ويتم تنفيذ الطلب أعلاه.

إنشاء عرض:

 create or replace view THINNING_HABR_SIMP_V as select STRIPE_ID , STOCK_NAME , TRUNC_UT (UT, STRIPE_ID) as UT , avg (APRICE) keep (dense_rank first order by UT, ID) as AOPEN , max (APRICE) as AHIGH , min (APRICE) as ALOW , avg (APRICE) keep (dense_rank last order by UT, ID) as ACLOSE , sum (AVOLUME) as AVOLUME , sum (APRICE * AVOLUME) as AAMOUNT , count (*) as ACOUNT from TRANSACTIONS_RAW , (select rownum as STRIPE_ID from dual connect by level <= 10) group by STRIPE_ID, STOCK_NAME, TRUNC_UT (UT, STRIPE_ID); 

الخيار 2 - CALC (محسوب بشكل متكرر)


في هذا الخيار ، ننفذ بشكل متكرر من المعاملات إلى المستوى 1 ، من المستوى 1 إلى المستوى 2 ، وما إلى ذلك.

إنشاء جدول:

 create table QUOTES_CALC ( STRIPE_ID number not null , STOCK_NAME varchar2 (128) not null , UT number not null , AOPEN number not null , AHIGH number not null , ALOW number not null , ACLOSE number not null , AVOLUME number not null , AAMOUNT number not null , ACOUNT number not null ) /*partition by list (STRIPE_ID) ( partition P01 values (1) , partition P02 values (2) , partition P03 values (3) , partition P04 values (4) , partition P05 values (5) , partition P06 values (6) , partition P07 values (7) , partition P08 values (8) , partition P09 values (9) , partition P10 values (10) )*/ parallel 4 pctfree 0 nologging; 

يمكنك إنشاء فهرس باستخدام حقل STRIPE_ID ، ولكن ثبت بشكل تجريبي أنه أكثر ربحية لمعاملات 11 مليون بدون فهرس. بالنسبة للكميات الأكبر ، قد يتغير الوضع. أو يمكنك تقسيم الجدول عن طريق إلغاء تعليق الكتلة في الاستعلام.

قم بإنشاء إجراء:

 create or replace procedure THINNING_HABR_CALC_T is begin rollback; execute immediate 'truncate table QUOTES_CALC'; insert --+ append into QUOTES_CALC select 1 as STRIPE_ID , STOCK_NAME , UT , avg (APRICE) keep (dense_rank first order by ID) , max (APRICE) , min (APRICE) , avg (APRICE) keep (dense_rank last order by ID) , sum (AVOLUME) , sum (APRICE * AVOLUME) , count (*) from TRANSACTIONS_RAW a group by STOCK_NAME, UT; commit; for i in 1..9 loop insert --+ append into QUOTES_CALC select --+ parallel(4) STRIPE_ID + 1 , STOCK_NAME , TRUNC_UT (UT, i + 1) , avg (AOPEN) keep (dense_rank first order by UT) , max (AHIGH) , min (ALOW) , avg (ACLOSE) keep (dense_rank last order by UT) , sum (AVOLUME) , sum (AAMOUNT) , sum (ACOUNT) from QUOTES_CALC a where STRIPE_ID = i group by STRIPE_ID, STOCK_NAME, TRUNC_UT (UT, i + 1); commit; end loop; end; / 

للتناظر ، أنشئ عرضًا بسيطًا:

 create view THINNING_HABR_CALC_V as select * from QUOTES_CALC; 

الخيار 3 - CHIN (الرمز الصيني)


تختلف الطريقة عن الوضوح الواضح الذي تتسم به المقاربة ، وهي رفض مبدأ "لا تكرر نفسك". في هذه الحالة ، رفض الدورات.

يتم توفير الخيار هنا فقط للاكتمال.

بالنظر إلى المستقبل ، سأقول أنه من حيث الأداء في هذه المهمة بالذات ، فإنها تحتل المركز الثاني.

طلب كبير
 create or replace view THINNING_HABR_CHIN_V as with T01 (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select 1 , STOCK_NAME , UT , avg (APRICE) keep (dense_rank first order by ID) , max (APRICE) , min (APRICE) , avg (APRICE) keep (dense_rank last order by ID) , sum (AVOLUME) , sum (APRICE * AVOLUME) , count (*) from TRANSACTIONS_RAW group by STOCK_NAME, UT) , T02 (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select STRIPE_ID + 1 , STOCK_NAME , TRUNC_UT (UT, STRIPE_ID + 1) , avg (AOPEN) keep (dense_rank first order by UT) , max (AHIGH) , min (ALOW) , avg (ACLOSE) keep (dense_rank last order by UT) , sum (AVOLUME) , sum (AAMOUNT) , sum (ACOUNT) from T01 group by STRIPE_ID, STOCK_NAME, TRUNC_UT (UT, STRIPE_ID + 1)) , T03 (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select STRIPE_ID + 1 , STOCK_NAME , TRUNC_UT (UT, STRIPE_ID + 1) , avg (AOPEN) keep (dense_rank first order by UT) , max (AHIGH) , min (ALOW) , avg (ACLOSE) keep (dense_rank last order by UT) , sum (AVOLUME) , sum (AAMOUNT) , sum (ACOUNT) from T02 group by STRIPE_ID, STOCK_NAME, TRUNC_UT (UT, STRIPE_ID + 1)) , T04 (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select STRIPE_ID + 1 , STOCK_NAME , TRUNC_UT (UT, STRIPE_ID + 1) , avg (AOPEN) keep (dense_rank first order by UT) , max (AHIGH) , min (ALOW) , avg (ACLOSE) keep (dense_rank last order by UT) , sum (AVOLUME) , sum (AAMOUNT) , sum (ACOUNT) from T03 group by STRIPE_ID, STOCK_NAME, TRUNC_UT (UT, STRIPE_ID + 1)) , T05 (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select STRIPE_ID + 1 , STOCK_NAME , TRUNC_UT (UT, STRIPE_ID + 1) , avg (AOPEN) keep (dense_rank first order by UT) , max (AHIGH) , min (ALOW) , avg (ACLOSE) keep (dense_rank last order by UT) , sum (AVOLUME) , sum (AAMOUNT) , sum (ACOUNT) from T04 group by STRIPE_ID, STOCK_NAME, TRUNC_UT (UT, STRIPE_ID + 1)) , T06 (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select STRIPE_ID + 1 , STOCK_NAME , TRUNC_UT (UT, STRIPE_ID + 1) , avg (AOPEN) keep (dense_rank first order by UT) , max (AHIGH) , min (ALOW) , avg (ACLOSE) keep (dense_rank last order by UT) , sum (AVOLUME) , sum (AAMOUNT) , sum (ACOUNT) from T05 group by STRIPE_ID, STOCK_NAME, TRUNC_UT (UT, STRIPE_ID + 1)) , T07 (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select STRIPE_ID + 1 , STOCK_NAME , TRUNC_UT (UT, STRIPE_ID + 1) , avg (AOPEN) keep (dense_rank first order by UT) , max (AHIGH) , min (ALOW) , avg (ACLOSE) keep (dense_rank last order by UT) , sum (AVOLUME) , sum (AAMOUNT) , sum (ACOUNT) from T06 group by STRIPE_ID, STOCK_NAME, TRUNC_UT (UT, STRIPE_ID + 1)) , T08 (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select STRIPE_ID + 1 , STOCK_NAME , TRUNC_UT (UT, STRIPE_ID + 1) , avg (AOPEN) keep (dense_rank first order by UT) , max (AHIGH) , min (ALOW) , avg (ACLOSE) keep (dense_rank last order by UT) , sum (AVOLUME) , sum (AAMOUNT) , sum (ACOUNT) from T07 group by STRIPE_ID, STOCK_NAME, TRUNC_UT (UT, STRIPE_ID + 1)) , T09 (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select STRIPE_ID + 1 , STOCK_NAME , TRUNC_UT (UT, STRIPE_ID + 1) , avg (AOPEN) keep (dense_rank first order by UT) , max (AHIGH) , min (ALOW) , avg (ACLOSE) keep (dense_rank last order by UT) , sum (AVOLUME) , sum (AAMOUNT) , sum (ACOUNT) from T08 group by STRIPE_ID, STOCK_NAME, TRUNC_UT (UT, STRIPE_ID + 1)) , T10 (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select STRIPE_ID + 1 , STOCK_NAME , TRUNC_UT (UT, STRIPE_ID + 1) , avg (AOPEN) keep (dense_rank first order by UT) , max (AHIGH) , min (ALOW) , avg (ACLOSE) keep (dense_rank last order by UT) , sum (AVOLUME) , sum (AAMOUNT) , sum (ACOUNT) from T09 group by STRIPE_ID, STOCK_NAME, TRUNC_UT (UT, STRIPE_ID + 1)) select * from T01 union all select * from T02 union all select * from T03 union all select * from T04 union all select * from T05 union all select * from T06 union all select * from T07 union all select * from T08 union all select * from T09 union all select * from T10; 


الخيار 4 - UDAF


لن يتم عرض خيار الوظيفة المجمعة المعرفة من قبل المستخدم هنا ، ولكن يمكن عرضه على github.

الخيار 5 - PPTF (وظيفة الجدول الموازي والأنابيب)


إنشاء وظيفة (في الحزمة):

 create or replace package THINNING_PPTF_P is type TRANSACTION_RECORD_T is record (STOCK_NAME varchar2(128), UT number, SEQ_NUM number, APRICE number, AVOLUME number); type CUR_RECORD_T is ref cursor return TRANSACTION_RECORD_T; type QUOTE_T is record (STRIPE_ID number, STOCK_NAME varchar2(128), UT number , AOPEN number, AHIGH number, ALOW number, ACLOSE number, AVOLUME number , AAMOUNT number, ACOUNT number); type QUOTE_LIST_T is table of QUOTE_T; function F (p_cursor CUR_RECORD_T) return QUOTE_LIST_T pipelined order p_cursor by (STOCK_NAME, UT, SEQ_NUM) parallel_enable (partition p_cursor by hash (STOCK_NAME)); end; / create or replace package body THINNING_PPTF_P is function F (p_cursor CUR_RECORD_T) return QUOTE_LIST_T pipelined order p_cursor by (STOCK_NAME, UT, SEQ_NUM) parallel_enable (partition p_cursor by hash (STOCK_NAME)) is QuoteTail QUOTE_LIST_T := QUOTE_LIST_T() ; rec TRANSACTION_RECORD_T; rec_prev TRANSACTION_RECORD_T; type ut_T is table of number index by pls_integer; ut number; begin QuoteTail.extend(10); loop fetch p_cursor into rec; exit when p_cursor%notfound; if rec_prev.STOCK_NAME = rec.STOCK_NAME then if (rec.STOCK_NAME = rec_prev.STOCK_NAME and rec.UT < rec_prev.UT) or (rec.STOCK_NAME = rec_prev.STOCK_NAME and rec.UT = rec_prev.UT and rec.SEQ_NUM < rec_prev.SEQ_NUM) then raise_application_error (-20010, 'Rowset must be ordered, ('||rec_prev.STOCK_NAME||','||rec_prev.UT||','||rec_prev.SEQ_NUM||') > ('||rec.STOCK_NAME||','||rec.UT||','||rec.SEQ_NUM||')'); end if; end if; if rec.STOCK_NAME <> rec_prev.STOCK_NAME or rec_prev.STOCK_NAME is null then for j in 1 .. 10 loop if QuoteTail(j).UT is not null then pipe row (QuoteTail(j)); QuoteTail(j) := null; end if; end loop; end if; for i in reverse 1..10 loop ut := TRUNC_UT (rec.UT, i); if QuoteTail(i).UT <> ut then for j in 1..i loop pipe row (QuoteTail(j)); QuoteTail(j) := null; end loop; end if; if QuoteTail(i).UT is null then QuoteTail(i).STRIPE_ID := i; QuoteTail(i).STOCK_NAME := rec.STOCK_NAME; QuoteTail(i).UT := ut; QuoteTail(i).AOPEN := rec.APRICE; end if; if rec.APRICE < QuoteTail(i).ALOW or QuoteTail(i).ALOW is null then QuoteTail(i).ALOW := rec.APRICE; end if; if rec.APRICE > QuoteTail(i).AHIGH or QuoteTail(i).AHIGH is null then QuoteTail(i).AHIGH := rec.APRICE; end if; QuoteTail(i).AVOLUME := nvl (QuoteTail(i).AVOLUME, 0) + rec.AVOLUME; QuoteTail(i).AAMOUNT := nvl (QuoteTail(i).AAMOUNT, 0) + rec.AVOLUME * rec.APRICE; QuoteTail(i).ACOUNT := nvl (QuoteTail(i).ACOUNT, 0) + 1; QuoteTail(i).ACLOSE := rec.APRICE; end loop; rec_prev := rec; end loop; for j in 1 .. 10 loop if QuoteTail(j).UT is not null then pipe row (QuoteTail(j)); end if; end loop; exception when no_data_needed then null; end; end; / 

إنشاء عرض:

 create or replace view THINNING_HABR_PPTF_V as select * from table (THINNING_PPTF_P.F (cursor (select STOCK_NAME, UT, ID, APRICE, AVOLUME from TRANSACTIONS_RAW))); 

الخيار 6 - الوضع (نموذج النموذج)


يحسب الخيار بشكل متكرر الهلاك لجميع المستويات العشرة باستخدام عبارة MODEL مع العبارة ITERATE .

الخيار غير عملي لأنه بطيء. في بيئتي ، يتم احتساب 1000 معاملة لـ 8 أدوات في دقيقة واحدة. يتم إنفاق معظم الوقت في حساب عبارة MODEL .

هنا أعطي هذا الخيار فقط من أجل الاكتمال وتأكيدًا على حقيقة أنه في Oracle يمكن إجراء جميع الحسابات المعقدة بشكل تعسفي تقريبًا باستخدام استعلام واحد ، دون استخدام PL / SQL.

أحد أسباب الأداء المنخفض لعبارة MODEL في هذا الاستعلام هو أن البحث على اليمين يتم إجراؤه لكل قاعدة ، والتي لدينا 6. يتم احتساب أول قاعدتين بسرعة كبيرة ، لأن هناك عنونة صريحة مباشرة ، بدون مهرجين. في القواعد الأربعة المتبقية هناك كلمة أي - هناك حسابات أبطأ.

الصعوبة الثانية هي أنه يجب عليك حساب النموذج المرجعي. هناك حاجة لأن قائمة الأبعاد يجب أن تكون معروفة قبل حساب عبارة MODEL ، لا يمكننا حساب أبعاد جديدة داخل هذه العبارة. ربما يمكن التحايل على ذلك بمساعدة عبارتين من طراز MODEL ، لكنني لم أفعل ذلك بسبب الأداء المنخفض لعدد كبير من القواعد.

أضفت أنه قد لا يكون من الممكن حساب UT_OPEN و UT_CLOSE في النموذج المرجعي ، ولكن استخدام نفس الوظائف avg () الاحتفاظ بـ (dense_rank أولاً / آخر ترتيب بواسطة) مباشرة في عبارة MODEL . لكن ذلك كان سيحدث ببطء أكثر.
نظرًا لقيود الأداء ، لن أقوم بتضمين هذا الخيار في إجراء الاختبار.

 with --       SOURCETRANS (STRIPE_ID, STOCK_NAME, PARENT_UT, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select 1, STOCK_NAME, TRUNC_UT (UT, 2), UT , avg (APRICE) keep (dense_rank first order by ID) , max (APRICE) , min (APRICE) , avg (APRICE) keep (dense_rank last order by ID) , sum (AVOLUME) , sum (AVOLUME * APRICE) , count (*) from TRANSACTIONS_RAW where ID <= 1000 --       group by STOCK_NAME, UT) --   PARENT_UT, UT  2...10    UT_OPEN, UT_CLOSE --    , REFMOD (STRIPE_ID, STOCK_NAME, PARENT_UT, UT, UT_OPEN, UT_CLOSE) as (select b.STRIPE_ID , a.STOCK_NAME , TRUNC_UT (UT, b.STRIPE_ID + 1) , TRUNC_UT (UT, b.STRIPE_ID) , min (TRUNC_UT (UT, b.STRIPE_ID - 1)) , max (TRUNC_UT (UT, b.STRIPE_ID - 1)) from SOURCETRANS a , (select rownum + 1 as STRIPE_ID from dual connect by level <= 9) b group by b.STRIPE_ID , a.STOCK_NAME , TRUNC_UT (UT, b.STRIPE_ID + 1) , TRUNC_UT (UT, b.STRIPE_ID)) --        , MAINTAB as ( select STRIPE_ID, STOCK_NAME, PARENT_UT, UT, AOPEN , AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT, null, null from SOURCETRANS union all select STRIPE_ID, STOCK_NAME, PARENT_UT, UT, null , null, null, null, null, null, null, UT_OPEN, UT_CLOSE from REFMOD) select STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT from MAINTAB model return all rows --      2...10 reference RM on (select * from REFMOD) dimension by (STRIPE_ID, STOCK_NAME, UT) measures (UT_OPEN, UT_CLOSE) main MM partition by (STOCK_NAME) dimension by (STRIPE_ID, PARENT_UT, UT) measures (AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) rules iterate (9) ( AOPEN [iteration_number + 2, any, any] = AOPEN [cv (STRIPE_ID) - 1, cv (UT) , rm.UT_OPEN [cv (STRIPE_ID), cv (STOCK_NAME), cv (UT)]] , ACLOSE [iteration_number + 2, any, any] = ACLOSE [cv (STRIPE_ID) - 1, cv (UT) , rm.UT_CLOSE[cv (STRIPE_ID), cv (STOCK_NAME), cv (UT)]] , AHIGH [iteration_number + 2, any, any] = max (AHIGH)[cv (STRIPE_ID) - 1, cv (UT), any] , ALOW [iteration_number + 2, any, any] = min (ALOW)[cv (STRIPE_ID) - 1, cv (UT), any] , AVOLUME [iteration_number + 2, any, any] = sum (AVOLUME)[cv (STRIPE_ID) - 1, cv (UT), any] , AAMOUNT [iteration_number + 2, any, any] = sum (AAMOUNT)[cv (STRIPE_ID) - 1, cv (UT), any] , ACOUNT [iteration_number + 2, any, any] = sum (ACOUNT)[cv (STRIPE_ID) - 1, cv (UT), any] ) order by 1, 2, 3, 4; 

الخيار 6 - IDEA (مثالي ، مثالي ، ولكن لا يعمل)


من المحتمل أن يكون الطلب الموضح أدناه هو الأكثر كفاءة ويستهلك كمية الموارد التي تساوي الحد الأدنى النظري.

ولكن لا Oracle أو MS SQL يسمح لك بكتابة استعلام في هذا النموذج. أعتقد أن هذا تمليه المعايير.

 with QUOTES_S1 as (select 1 as STRIPE_ID , STOCK_NAME , TRUNC_UT (UT, 1) as UT , avg (APRICE) keep (dense_rank first order by ID) as AOPEN , max (APRICE) as AHIGH , min (APRICE) as ALOW , avg (APRICE) keep (dense_rank last order by ID) as ACLOSE , sum (AVOLUME) as AVOLUME , sum (APRICE * AVOLUME) as AAMOUNT , count (*) as ACOUNT from TRANSACTIONS_RAW -- where rownum <= 100 group by STOCK_NAME, TRUNC_UT (UT, 1)) , T1 (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) as (select 1, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT from QUOTES_S1 union all select STRIPE_ID + 1 , STOCK_NAME , TRUNC_UT (UT, STRIPE_ID + 1) , avg (AOPEN) keep (dense_rank first order by UT) , max (AHIGH) , min (ALOW) , avg (ACLOSE) keep (dense_rank last order by UT) , sum (AVOLUME) , sum (AAMOUNT) , sum (ACOUNT) from T1 where STRIPE_ID < 10 group by STRIPE_ID + 1, STOCK_NAME, TRUNC_UT (UT, STRIPE_ID + 1) ) select * from T1 

يطابق هذا الاستعلام الجزء التالي من وثائق Oracle:

إذا كان subquery_factoring_clause يشير إلى اسم_الطلب الخاص به في الاستعلام الفرعي الذي يعرّفه ، عندئذٍ يُقال أن subquery_factoring_clause عودي. يجب أن يحتوي subquery_factoring_clause العودي على كتلتين للاستعلام: الأول هو عضو الارتساء والثاني هو العضو العودي. يجب أن يظهر عضو الارتساء قبل العضو العودي ، ولا يمكنه الرجوع إلى query_name. يمكن أن يتكون عضو الارتساء من كتلة استعلام واحدة أو أكثر مدمجة بواسطة عوامل التشغيل المحددة: UNION ALL أو UNION أو INTERSECT أو MINUS. يجب أن يتبع العضو العودي عضو الارتساء ويجب أن يشير إلى query_name مرة واحدة بالضبط. يجب الجمع بين العضو العودي وعضو الارتساء باستخدام عامل تشغيل UNION ALL.

لكنه يتعارض مع الفقرة التالية من الوثائق:

لا يمكن أن يحتوي العضو العودي على أي من العناصر التالية:
الكلمة الأساسية DISTINCT أو جملة GROUP BY
دالة تجميعية. ومع ذلك ، يسمح بالوظائف التحليلية في قائمة التحديد.

وبالتالي ، في العضو العودي ، لا يُسمح بالركام والتجميع.

الاختبار



دعونا نفعل ذلك أولاً لـ Oracle .

نفذ إجراء الحساب لطريقة CALC وسجل وقت تنفيذها:

 exec THINNING_HABR_CALC_T 

نتائج حساب الطرق الأربع في أربع طرق عرض:

  • THINNING_HABR_SIMP_V (سيتم إجراء الحساب ، مما يتسبب في تحديد معقد ، لذلك سيستغرق وقتًا طويلاً) ،
  • THINNING_HABR_CALC_V (سيتم عرض البيانات من جدول QUOTES_CALC ، لذا سيتم تنفيذها بسرعة)
  • THINNING_HABR_CHIN_V (سيجري الحساب أيضًا ، مما يتسبب في تحديد معقد ، لذلك سيستغرق وقتًا طويلاً) ،
  • THINNING_HABR_PPTF_V (سيتم تنفيذ الوظيفة THINNING_HABR_PPTF).

لقد تم بالفعل قياس المهلة الزمنية لجميع الطرق من قبل وأنا واردة في الجدول في نهاية المقالة.

بالنسبة لبقية VIEW ، نقوم بتنفيذ الطلبات وكتابة وقت التنفيذ:

 select count (*) as CNT , sum (STRIPE_ID) as S_STRIPE_ID, sum (UT) as S_UT , sum (AOPEN) as S_AOPEN, sum (AHIGH) as S_AHIGH, sum (ALOW) as S_ALOW , sum (ACLOSE) as S_ACLOSE, sum (AVOLUME) as S_AVOLUME , sum (AAMOUNT) as S_AAMOUNT, sum (ACOUNT) as S_ACOUNT from THINNING_HABR_XXXX_V 

حيث XXXX هو SIMP ، CHIN ، PPTF.

تحسب وجهات النظر هذه ملخص التوظيف. لحساب الملخص ، تحتاج إلى جلب جميع الخطوط ، وباستخدام الملخص يمكنك مقارنة المجموعات مع بعضها البعض.

يمكنك أيضًا مقارنة المجموعات باستخدام حزمة dbms_sqlhash ، ولكن هذا أبطأ كثيرًا لأنك تحتاج إلى فرز المجموعة الأصلية ، وحساب التجزئة ليس سريعًا.
أيضا في 12c هناك حزمة DBMS_COMPARISON.

يمكنك التحقق من صحة جميع الخوارزميات في نفس الوقت. نحن نعتبر الملخصات مثل هذا الطلب (مع 11 مليون إدخال في جهاز افتراضي ، سيكون هذا طويلًا نسبيًا ، حوالي 15 دقيقة):

 with T1 as (select 'SIMP' as ALG_NAME, a.* from THINNING_HABR_SIMP_V a union all select 'CALC', a.* from THINNING_HABR_CALC_V a union all select 'CHIN', a.* from THINNING_HABR_CHIN_V a union all select 'PPTF', a.* from THINNING_HABR_PPTF_V a) select ALG_NAME , count (*) as CNT , sum (STRIPE_ID) as S_STRIPE_ID, sum (UT) as S_UT , sum (AOPEN) as S_AOPEN, sum (AHIGH) as S_AHIGH, sum (ALOW) as S_ALOW , sum (ACLOSE) as S_ACLOSE, sum (AVOLUME) as S_AVOLUME , sum (AAMOUNT) as S_AAMOUNT, sum (ACOUNT) as S_ACOUNT from T1 group by ALG_NAME; 

نرى أن الملخصات متشابهة ، لذا أعطت جميع الخوارزميات نفس النتائج.

الآن سنقوم بإعادة إنتاج كل نفس في MS SQL . اختبرت في الإصدار 2016.

قم أولاً بإنشاء قاعدة بيانات DBTEST ، ثم قم بإنشاء جدول معاملات فيها:

 use DBTEST go create table TRANSACTIONS_RAW ( STOCK_NAME varchar (32) not null , UT int not null , APRICE numeric (22, 12) not null , AVOLUME numeric (22, 12) not null , ID bigint identity not null ); 

قم بتنزيل البيانات التي تم تنزيلها.

في MSSQL ، قم بإنشاء الملف format_mssql.bcp:

 12.0 3 1 SQLCHAR 0 0 "," 3 UT "" 2 SQLCHAR 0 0 "," 4 APRICE "" 3 SQLCHAR 0 0 "\n" 5 AVOLUME "" 

وقم بتشغيل البرنامج النصي LoadData-MSSQL.sql في SSMS (تم إنشاء هذا البرنامج النصي من خلال البرنامج النصي powerhell الوحيد المقدم في قسم هذه المقالة لـ Oracle).

لنقم بإنشاء وظيفتين:

 use DBTEST go create or alter function TRUNC_UT (@p_UT bigint, @p_StripeTypeId int) returns bigint as begin return case @p_StripeTypeId when 1 then @p_UT when 2 then @p_UT / 10 * 10 when 3 then @p_UT / 60 * 60 when 4 then @p_UT / 600 * 600 when 5 then @p_UT / 3600 * 3600 when 6 then @p_UT / 14400 * 14400 when 7 then @p_UT / 86400 * 86400 when 8 then datediff (second, cast ('1970-01-01 00:00:00' as datetime), dateadd(m, datediff (m, 0, dateadd (second, @p_UT, cast ('1970-01-01 00:00:00' as datetime))), 0)) when 9 then datediff (second, cast ('1970-01-01 00:00:00' as datetime), dateadd(yy, datediff (yy, 0, dateadd (second, @p_UT, cast ('1970-01-01 00:00:00' as datetime))), 0)) when 10 then 0 when 11 then 0 end; end; go create or alter function UT2DATESTR (@p_UT bigint) returns datetime as begin return dateadd(s, @p_UT, cast ('1970-01-01 00:00:00' as datetime)); end; go 

نشرع في تنفيذ الخيارات:

الخيار 1 - SIMP


الجري:

 use DBTEST go create or alter view dbo.THINNING_HABR_SIMP_V as with T1 (STRIPE_ID) as (select 1 union all select STRIPE_ID + 1 from T1 where STRIPE_ID < 10) , T2 as (select STRIPE_ID , STOCK_NAME , dbo.TRUNC_UT (UT, STRIPE_ID) as UT , min (1000000 * cast (UT as bigint) + ID) as AOPEN_UT , max (APRICE) as AHIGH , min (APRICE) as ALOW , max (1000000 * cast (UT as bigint) + ID) as ACLOSE_UT , sum (AVOLUME) as AVOLUME , sum (APRICE * AVOLUME) as AAMOUNT , count (*) as ACOUNT from TRANSACTIONS_RAW, T1 group by STRIPE_ID, STOCK_NAME, dbo.TRUNC_UT (UT, STRIPE_ID)) select t.STRIPE_ID, t.STOCK_NAME, t.UT, t_op.APRICE as AOPEN, t.AHIGH , t.ALOW, t_cl.APRICE as ACLOSE, t.AVOLUME, t.AAMOUNT, t.ACOUNT from T2 t join TRANSACTIONS_RAW t_op on (t.STOCK_NAME = t_op.STOCK_NAME and t.AOPEN_UT / 1000000 = t_op.UT and t.AOPEN_UT % 1000000 = t_op.ID) join TRANSACTIONS_RAW t_cl on (t.STOCK_NAME = t_cl.STOCK_NAME and t.ACLOSE_UT / 1000000 = t_cl.UT and t.ACLOSE_UT % 1000000 = t_cl.ID); 

يتم تنفيذ الوظائف الأولى / الأخيرة المفقودة عن طريق الانضمام الذاتي لجدول مزدوج.

الخيار 2 - CALC


إنشاء جدول وإجراء وعرض:

 use DBTEST go create table dbo.QUOTES_CALC ( STRIPE_ID int not null , STOCK_NAME varchar(32) not null , UT bigint not null , AOPEN numeric (22, 12) not null , AHIGH numeric (22, 12) not null , ALOW numeric (22, 12) not null , ACLOSE numeric (22, 12) not null , AVOLUME numeric (38, 12) not null , AAMOUNT numeric (38, 12) not null , ACOUNT int not null ); go create or alter procedure dbo.THINNING_HABR_CALC as begin set nocount on; truncate table QUOTES_CALC; declare @StripeId int; with T1 as (select STOCK_NAME , UT , min (ID) as AOPEN_ID , max (APRICE) as AHIGH , min (APRICE) as ALOW , max (ID) as ACLOSE_ID , sum (AVOLUME) as AVOLUME , sum (APRICE * AVOLUME) as AAMOUNT , count (*) as ACOUNT from TRANSACTIONS_RAW group by STOCK_NAME, UT) insert into QUOTES_CALC (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) select 1, t.STOCK_NAME, t.UT, t_op.APRICE, t.AHIGH, t.ALOW, t_cl.APRICE, t.AVOLUME, t.AAMOUNT, t.ACOUNT from T1 t join TRANSACTIONS_RAW t_op on (t.STOCK_NAME = t_op.STOCK_NAME and t.UT = t_op.UT and t.AOPEN_ID = t_op.ID) join TRANSACTIONS_RAW t_cl on (t.STOCK_NAME = t_cl.STOCK_NAME and t.UT = t_cl.UT and t.ACLOSE_ID = t_cl.ID); set @StripeId = 1; while (@StripeId <= 9) begin with T1 as (select STOCK_NAME , dbo.TRUNC_UT (UT, @StripeId + 1) as UT , min (UT) as AOPEN_UT , max (AHIGH) as AHIGH , min (ALOW) as ALOW , max (UT) as ACLOSE_UT , sum (AVOLUME) as AVOLUME , sum (AAMOUNT) as AAMOUNT , sum (ACOUNT) as ACOUNT from QUOTES_CALC where STRIPE_ID = @StripeId group by STOCK_NAME, dbo.TRUNC_UT (UT, @StripeId + 1)) insert into QUOTES_CALC (STRIPE_ID, STOCK_NAME, UT, AOPEN, AHIGH, ALOW, ACLOSE, AVOLUME, AAMOUNT, ACOUNT) select @StripeId + 1, t.STOCK_NAME, t.UT, t_op.AOPEN, t.AHIGH, t.ALOW, t_cl.ACLOSE, t.AVOLUME, t.AAMOUNT, t.ACOUNT from T1 t join QUOTES_CALC t_op on (t.STOCK_NAME = t_op.STOCK_NAME and t.AOPEN_UT = t_op.UT) join QUOTES_CALC t_cl on (t.STOCK_NAME = t_cl.STOCK_NAME and t.ACLOSE_UT = t_cl.UT) where t_op.STRIPE_ID = @StripeId and t_cl.STRIPE_ID = @StripeId; set @StripeId = @StripeId + 1; end; end; go create or alter view dbo.THINNING_HABR_CALC_V as select * from dbo.QUOTES_CALC; go 

لم أقم بتطبيق الخيارين 3 (CHIN) و 4 (UDAF) على MS SQL.

الخيار 5 - PPTF


إنشاء وظيفة وعرض الجدول. هذه الوظيفة هي مجرد وظيفة جدول ، وليست وظيفة جدول موازية للأنابيب ، فقط الخيار احتفظ باسمه التاريخي من Oracle:

 use DBTEST go create or alter function dbo.THINNING_HABR_PPTF () returns @rettab table ( STRIPE_ID bigint not null , STOCK_NAME varchar(32) not null , UT bigint not null , AOPEN numeric (22, 12) not null , AHIGH numeric (22, 12) not null , ALOW numeric (22, 12) not null , ACLOSE numeric (22, 12) not null , AVOLUME numeric (38, 12) not null , AAMOUNT numeric (38, 12) not null , ACOUNT bigint not null) as begin declare @i tinyint; declare @tut int; declare @trans_STOCK_NAME varchar(32); declare @trans_UT int; declare @trans_ID int; declare @trans_APRICE numeric (22,12); declare @trans_AVOLUME numeric (22,12); declare @trans_prev_STOCK_NAME varchar(32); declare @trans_prev_UT int; declare @trans_prev_ID int; declare @trans_prev_APRICE numeric (22,12); declare @trans_prev_AVOLUME numeric (22,12); declare @QuoteTail table ( STRIPE_ID bigint not null primary key clustered , STOCK_NAME varchar(32) not null , UT bigint not null , AOPEN numeric (22, 12) not null , AHIGH numeric (22, 12) , ALOW numeric (22, 12) , ACLOSE numeric (22, 12) , AVOLUME numeric (38, 12) not null , AAMOUNT numeric (38, 12) not null , ACOUNT bigint not null); declare c cursor fast_forward for select STOCK_NAME, UT, ID, APRICE, AVOLUME from TRANSACTIONS_RAW order by STOCK_NAME, UT, ID; -- THIS ORDERING (STOCK_NAME, UT, ID) IS MANDATORY open c; fetch next from c into @trans_STOCK_NAME, @trans_UT, @trans_ID, @trans_APRICE, @trans_AVOLUME; while @@fetch_status = 0 begin if @trans_STOCK_NAME <> @trans_prev_STOCK_NAME or @trans_prev_STOCK_NAME is null begin insert into @rettab select * from @QuoteTail; delete @QuoteTail; end; set @i = 10; while @i >= 1 begin set @tut = dbo.TRUNC_UT (@trans_UT, @i); if @tut <> (select UT from @QuoteTail where STRIPE_ID = @i) begin insert into @rettab select * from @QuoteTail where STRIPE_ID <= @i; delete @QuoteTail where STRIPE_ID <= @i; end; if (select count (*) from @QuoteTail where STRIPE_ID = @i) = 0 begin insert into @QuoteTail (STRIPE_ID, STOCK_NAME, UT, AOPEN, AVOLUME, AAMOUNT, ACOUNT) values (@i, @trans_STOCK_NAME, @tut, @trans_APRICE, 0, 0, 0); end; update @QuoteTail set AHIGH = case when AHIGH < @trans_APRICE or AHIGH is null then @trans_APRICE else AHIGH end , ALOW = case when ALOW > @trans_APRICE or ALOW is null then @trans_APRICE else ALOW end , ACLOSE = @trans_APRICE, AVOLUME = AVOLUME + @trans_AVOLUME , AAMOUNT = AAMOUNT + @trans_APRICE * @trans_AVOLUME , ACOUNT = ACOUNT + 1 where STRIPE_ID = @i; set @i = @i - 1; end; set @trans_prev_STOCK_NAME = @trans_STOCK_NAME; set @trans_prev_UT = @trans_UT; set @trans_prev_ID = @trans_ID; set @trans_prev_APRICE = @trans_APRICE; set @trans_prev_AVOLUME = @trans_AVOLUME; fetch next from c into @trans_STOCK_NAME, @trans_UT, @trans_ID, @trans_APRICE, @trans_AVOLUME; end; close c; deallocate c; insert into @rettab select * from @QuoteTail; return; end go create or alter view dbo.THINNING_HABR_PPTF_V as select * from dbo.THINNING_HABR_PPTF (); 

دعنا نحسب جدول QUOTES_CALC لطريقة CALC ونكتب وقت التنفيذ:
 use DBTEST go exec dbo.THINNING_HABR_CALC 

نتائج حساب الطرق الثلاث في ثلاث طرق عرض:

  • THINNING_HABR_SIMP_V (سيتم إجراء الحساب ، مما يتسبب في تحديد معقد ، لذلك سيستغرق وقتًا طويلاً) ،
  • THINNING_HABR_CALC_V (سيتم عرض البيانات من جدول QUOTES_CALC ، لذا سيتم تنفيذها بسرعة)
  • THINNING_HABR_PPTF_V (سيتم تنفيذ الوظيفة THINNING_HABR_PPTF).

لوجهين ، نقوم بتنفيذ الطلبات ونكتب وقت التنفيذ:

 select count (*) as CNT , sum (STRIPE_ID) as S_STRIPE_ID, sum (UT) as S_UT , sum (AOPEN) as S_AOPEN, sum (AHIGH) as S_AHIGH, sum (ALOW) as S_ALOW , sum (ACLOSE) as S_ACLOSE, sum (AVOLUME) as S_AVOLUME , sum (AAMOUNT) as S_AAMOUNT, sum (ACOUNT) as S_ACOUNT from THINNING_HABR_XXXX_V 

حيث XXXX هو SIMP ، PPTF.

يمكنك الآن مقارنة نتائج العمليات الحسابية للطرق الثلاثة لـ MS SQL. يمكن القيام بذلك في طلب واحد. الجري:

 use DBTEST go with T1 as (select 'SIMP' as ALG_NAME, a.* from THINNING_HABR_SIMP_V a union all select 'CALC', a.* from THINNING_HABR_CALC_V a union all select 'PPTF', a.* from THINNING_HABR_PPTF_V a) select ALG_NAME , count (*) as CNT, sum (cast (STRIPE_ID as bigint)) as STRIPE_ID , sum (cast (UT as bigint)) as UT, sum (AOPEN) as AOPEN , sum (AHIGH) as AHIGH, sum (ALOW) as ALOW, sum (ACLOSE) as ACLOSE, sum (AVOLUME) as AVOLUME , sum (AAMOUNT) as AAMOUNT, sum (cast (ACOUNT as bigint)) as ACOUNT from T1 group by ALG_NAME; 

إذا تزامنت ثلاثة خطوط عبر جميع الحقول ، فإن نتيجة الحساب باستخدام الطرق الثلاثة متطابقة.

أنصحك بشدة باستخدام مجموعة صغيرة في مرحلة الاختبار ، لأن أداء هذه المهمة في MS SQL منخفض.

إذا كان لديك محرك MS SQL فقط وتريد حساب كمية أكبر من البيانات ، فيمكنك تجربة طريقة التحسين التالية: يمكنك إنشاء الفهارس:

 create unique clustered index TRANSACTIONS_RAW_I1 on TRANSACTIONS_RAW (STOCK_NAME, UT, ID); create unique clustered index QUOTES_CALC_I1 on QUOTES_CALC (STRIPE_ID, STOCK_NAME, UT); 

فيما يلي نتائج قياس الأداء على جهازي الافتراضي:

الصورة

يمكن تنزيل البرامج النصية من github : Oracle ونظام THINNING - نصوص هذه المقالة ونظام THINNING_LIVE - تنزيل البيانات عبر الإنترنت من bitcoincharts.com وترقيق الإنترنت (ولكن هذا الموقع يرسل البيانات فقط لآخر 5 أيام في وضع الاتصال) والبرنامج النصي لـ MS SQL أيضًا في هذه المقالة.

الخلاصة:

يتم حل هذه المهمة بشكل أسرع على Oracle من MS SQL. مع زيادة عدد المعاملات ، أصبحت الفجوة أكثر أهمية.

في Oracle ، كان PPTF هو الخيار الأفضل. هنا اتضح أن النهج الإجرائي أكثر ربحية ، وهذا يحدث بشكل غير منتظم. أظهرت الطرق الأخرى أيضًا نتيجة مقبولة - حتى أنني اختبرت حجم 367 مليون معاملة في جهاز افتراضي (طريقة PPTF المحسوبة ترقق في ساعة ونصف).

في MS SQL ، تبين أن طريقة الحساب التكراري (CALC) هي الأكثر إنتاجية.

لماذا تعتبر طريقة PPTF من Oracle رائدة؟ نظرًا للتوافق والمعمارية ، يتم تضمين دالة تم إنشاؤها كدالة جدول موازية للأنابيب في منتصف خطة الاستعلام:

الصورة

Source: https://habr.com/ru/post/ar418757/


All Articles