99999久久久久久亚洲,欧美人与禽猛交狂配,高清日韩av在线影院,一个人在线高清免费观看,啦啦啦在线视频免费观看www

熱線電話:13121318867

登錄
首頁精彩閱讀【每周一期-數(shù)據(jù)蔣堂】SQL的有序分組
【每周一期-數(shù)據(jù)蔣堂】SQL的有序分組
2018-01-12
收藏

【每周一期-數(shù)據(jù)蔣堂】SQL的有序分組

我們知道,SQL延用了數(shù)學上的無序集合概念,所以SQL的分組并不關注過待分組集合中成員的次序。我們在前面討論過的等值分組和非等值分組,也都沒有關注過這個問題,分組規(guī)則都是建立在成員取值本身上。但如果我們要拓展SQL,以有序集合為考慮對象時,那就必須考慮成員次序對分組的影響了,而且,現(xiàn)實業(yè)務中有大量的有序分組應用場景。

1. 序號分組

一個簡單的例子:將一個班的學生平均分成三份(假定人數(shù)能被3整除)。按我們在前面所說的分組定義,這也可以看成是一種分組,但這個運算在SQL中卻很難寫出來,因為分組依據(jù)和成員取值沒有關系。

如果使用我們在前面講有序遍歷語法時的#符號,這個問題就很容易解決了。

A.group( (#-1)*3\A.len() )   // 按序號分成前1/3,中1/3,后1/3

A.group( (#-1)%3 )             // 還可以按序號每三個中取一個構成分組子集

SQL實現(xiàn)這個運算就麻煩很多,需要先用子查詢造出一個序號,然后再執(zhí)行類似的分組規(guī)則。

上面這個例子中其實還沒有真正關注成員的次序,只是說明了序號的作用,待分組集合的成員是其它次序時也可以得到可用的結果。

我們再看更多例子。

處理文本日志時,有些日志的基本單位不是1行,而可能是3行,即每個事件總是寫出3行文本,這并不是多罕見的情況。對付這種日志時,就需要把文本每3行拆成一個分組子集,然后針對每個分組再進行詳細的分析處理。這時要正確的分組運算就必須依賴于待分組集合中成員(文本日志的行)的次序了。

入學考試之后,把學生按成績排序蛇行分拆成兩個班,即名次1,4,5,8,...在一個,而2,3,6,7,...在另一個班,這樣能保證兩個班的平均名次是相同的。這個分組也可以用序號做出來:

A.sort@z(score).group(#%4<2)

這里用的分組值不再是常見的普通數(shù)值,而是一個布爾量,相當于按“真“值和“假”值分成兩個組,真值對應第一個班,假值對應另一個班。本質上講,這還是個等值分組,只是用到的分組值可以是任意泛型。

顯然,這個分組的正確性也嚴重依賴于待分組集成的成員次序。

順便說一句,這又是一個只關注分組子集而不關心聚合值的例子。按序號分組在很多情況下就是用序號來計算出分組依據(jù),然后就變成普通的等值分組了。那么有沒有不能簡單地轉換成等值分組的情況呢?

2. 值變化分組

有一組嬰兒出生記錄,是按出生次序排序的,我們現(xiàn)在關心連續(xù)出生的同性別嬰兒數(shù)量超過5的有多少批?

簡單想,這就是先GROUP,計算每組COUNT值,然后數(shù)出有幾個大于5的。后兩步很簡單,問題是怎么GROUP?

直接按嬰兒性別分組當然是不對的,必須考慮次序,依次掃描記錄,當嬰兒性別發(fā)生變化時則產生一個新組。這種分組顯然沒法直接用等值分組做出來了。

我們可以提供一個有序分組方法來實現(xiàn)這種分組:當考察值發(fā)生變化時就產生一個新的分組。

A.group@o(gender).count(~.len()>5)   // @o選項表示分組值變化時將產生新分組。

SQL就麻煩很多,需要先造成中間標志和變量來生成組的序號,大概是這樣

SELECT COUNT(*) FROM

   (SELECT ChangeNumber FROM

        (SELECT SUM(ChangeFlag) OVER (ORDER BY birthday) ChangeNumber FROM

            (SELECT CASE WHEN gender=LAG(gender) OVER ( ORDER BY birthday) THEN 0 ELSE 1 END ChangeFlag FROM A))

    GROUP ChangeNumber HAVING COUNT(*)>5)

這樣的SQL,看懂都不是很容易的。而且必須借助birthday這種字段來形成次序,而前述的有序分組寫法在原數(shù)據(jù)有序時根本用不著這個信息。

這種場景同樣可能出現(xiàn)在文本分析中。每個用戶的事件日志可能多行,而且行數(shù)不確定,但寫日志時會在每個行開始處寫上用戶號。這樣我們可以按這個用戶號進行有序分組,它變化時就說明是另一個用戶的事件了。

即使是普通的等值分組,如果事先知道原集合對分組字段有序,也可以使用這種方案來實施,這將獲得更高的性能,比數(shù)據(jù)庫常用的HASH分組方案要快得多,而且特別適合大數(shù)據(jù)遍歷的情況。

3. 條件變化分組

再看一個著名的問題:一支股票最長連續(xù)上漲了多少天?

這個問題當然可以直接遍歷去解決,不過我們現(xiàn)在用分組的思路來處理,至少在SQL體系下只能這么做(嚴格些說,這是目前找到的最簡單可行的辦法)。

將股票收盤價按日期排序,然后將連續(xù)上漲的日期分到同一組,這樣只要考慮哪一組成員數(shù)最多即可。更明確地說,就是當某天上漲了,就把這一天和前一天分到一個組中,某天下跌了,則產生一個新組。

SQL實現(xiàn)這個思路,同樣需要用中間標志和變量來生成組序號:

SELECT MAX(ContinuousDays) FROM

    (SELECT COUNT(*) ContinuousDays FROM

        (SELECT SUM(RisingFlag) OVER (ORDER BY TradingDate ) NoRisingDays FROM

            (SELECT TradingDate,

                CASE WHEN ClosingPrice>LAG(ClosingPrice) OVER (ORDER BY TradingDate THEN 0 ELSE 1 END) RisingFlag

            FROM A))

    GROUP BY NoRisingDays)

如果有專門的有序分組方法以及以前說過的有序遍歷語法,這個運算就很簡單了:

A.sort(TradingDate).group@i(ClosingPrice

SQL不同,雖然實現(xiàn)思路完全一樣,但寫出來是分步的,而不是一個多層嵌套語句,書寫和理解都要容易得多。

同樣地,這種場景也會在文本分析中有用。不確定行數(shù)的日志中,有時會在事件分始時寫一個標志串,當掃描到這個標志串的時候就產生一個新的分組,有序分析的條件可設定為當前掃描行和指定文字相同,這樣就能保證同一事件的日志信息在同一個組中。

后兩種有序分組的情況,理論上當然也可以轉換成等值分組來處理(用SQL就要這么做,這也能從另一個側面說明SQL運算體系的完備性),但確實是相當麻煩的,所以我們一般不把它再當成等值分組來處理了。

到目前為止的分組討論,都是假定待分組集合已經準備好,其成員可以被隨機訪問到。但如果數(shù)據(jù)量巨大而不能全部讀入時,如果繼續(xù)做這種假定,會導致頻繁的外存交換而性能極差,這時需要再設計以流方式邊讀入邊分組并且邊聚合的運算體系。事實上日志分析中更常見的是這種情況,這些問題我們將再撰文研究,但基本方法思路仍然離不開上面這些內容。


數(shù)據(jù)分析咨詢請掃描二維碼

若不方便掃碼,搜微信號:CDAshujufenxi

數(shù)據(jù)分析師考試動態(tài)
數(shù)據(jù)分析師資訊
更多

OK
客服在線
立即咨詢
客服在線
立即咨詢
') } function initGt() { var handler = function (captchaObj) { captchaObj.appendTo('#captcha'); captchaObj.onReady(function () { $("#wait").hide(); }).onSuccess(function(){ $('.getcheckcode').removeClass('dis'); $('.getcheckcode').trigger('click'); }); window.captchaObj = captchaObj; }; $('#captcha').show(); $.ajax({ url: "/login/gtstart?t=" + (new Date()).getTime(), // 加隨機數(shù)防止緩存 type: "get", dataType: "json", success: function (data) { $('#text').hide(); $('#wait').show(); // 調用 initGeetest 進行初始化 // 參數(shù)1:配置參數(shù) // 參數(shù)2:回調,回調的第一個參數(shù)驗證碼對象,之后可以使用它調用相應的接口 initGeetest({ // 以下 4 個配置參數(shù)為必須,不能缺少 gt: data.gt, challenge: data.challenge, offline: !data.success, // 表示用戶后臺檢測極驗服務器是否宕機 new_captcha: data.new_captcha, // 用于宕機時表示是新驗證碼的宕機 product: "float", // 產品形式,包括:float,popup width: "280px", https: true // 更多配置參數(shù)說明請參見:http://docs.geetest.com/install/client/web-front/ }, handler); } }); } function codeCutdown() { if(_wait == 0){ //倒計時完成 $(".getcheckcode").removeClass('dis').html("重新獲取"); }else{ $(".getcheckcode").addClass('dis').html("重新獲取("+_wait+"s)"); _wait--; setTimeout(function () { codeCutdown(); },1000); } } function inputValidate(ele,telInput) { var oInput = ele; var inputVal = oInput.val(); var oType = ele.attr('data-type'); var oEtag = $('#etag').val(); var oErr = oInput.closest('.form_box').next('.err_txt'); var empTxt = '請輸入'+oInput.attr('placeholder')+'!'; var errTxt = '請輸入正確的'+oInput.attr('placeholder')+'!'; var pattern; if(inputVal==""){ if(!telInput){ errFun(oErr,empTxt); } return false; }else { switch (oType){ case 'login_mobile': pattern = /^1[3456789]\d{9}$/; if(inputVal.length==11) { $.ajax({ url: '/login/checkmobile', type: "post", dataType: "json", data: { mobile: inputVal, etag: oEtag, page_ur: window.location.href, page_referer: document.referrer }, success: function (data) { } }); } break; case 'login_yzm': pattern = /^\d{6}$/; break; } if(oType=='login_mobile'){ } if(!!validateFun(pattern,inputVal)){ errFun(oErr,'') if(telInput){ $('.getcheckcode').removeClass('dis'); } }else { if(!telInput) { errFun(oErr, errTxt); }else { $('.getcheckcode').addClass('dis'); } return false; } } return true; } function errFun(obj,msg) { obj.html(msg); if(msg==''){ $('.login_submit').removeClass('dis'); }else { $('.login_submit').addClass('dis'); } } function validateFun(pat,val) { return pat.test(val); }