<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SQL開源替代品,誕生了!

          共 13541字,需瀏覽 28分鐘

           ·

          2023-10-26 20:22

          發(fā)明 SQL 的初衷之一顯然是為了降低人們實施數(shù)據(jù)查詢計算的難度。SQL 中用了不少類英語的詞匯和語法,這是希望非技術(shù)人員也能掌握。確實,簡單的 SQL 可以當作英語閱讀,即使沒有程序設(shè)計經(jīng)驗的人也能運用。
          然而,面對稍稍復(fù)雜的查詢計算需求,SQL 就會顯得力不從心,經(jīng)常寫出幾百行有多層嵌套的語句。這種 SQL,不要說非技術(shù)人員難以完成,即使對于專業(yè)程序員也不是件容易的事,常常成為很多軟件企業(yè)應(yīng)聘考試的重頭戲。三行五行的 SQL 僅存在教科書和培訓班,現(xiàn)實中用于報表查詢的 SQL 通常是以“K”計的。

          SQL 困難的分析探討

          這是為什么呢?我們通過一個很簡單的例子來考察 SQL 在計算方面的缺點。
          設(shè)有一個由三個字段構(gòu)成的銷售業(yè)績表(為了簡化問題,省去日期信息):
          sales_amount 銷售業(yè)績表
          sales 銷售員姓名,假定無重名
          product 銷售的產(chǎn)品
          amount 該銷售員在該產(chǎn)品上的銷售額

          現(xiàn)在我們想知道出空調(diào)和電視銷售額都在前 10 名的銷售員名單。

          這個問題并不難,人們會很自然地設(shè)計出如下計算過程:

          1. 按空調(diào)銷售額排序,找出前 10 名;

          2. 按電視銷售額排序,找出前 10 名;

          3. 對 1、2 的結(jié)果取交集,得到答案;
          我們現(xiàn)在來用 SQL 做。
          早期的 SQL 不支持步驟化,要把前兩步寫進子查詢,顯得有點繁瑣:
          select * from     ( select top 10 sales from sales_amount where product='AC' order by amount desc )intersect     ( select top 10 sales from sales_amount where product='TV' order by amount desc )
          后來,SQL 也發(fā)現(xiàn)了不分步會很麻煩,就提供了 CTE 語法,可以用 with 關(guān)鍵字,可以把前面步驟中的查詢結(jié)果命名后在后面的步驟中使用:
          with A as       select top 10 sales from sales_amount where product='AC' order by amount desc     B as       select top 10 sales from sales_amount where product='TV' order by amount descselect * from A intersect B

          句子沒有更短,但分步后思路確實變清晰了。

          現(xiàn)在,我們把問題稍復(fù)雜化一點,改為計算所有產(chǎn)品銷售額都在前 10 名的銷售員,試想一下應(yīng)當如何計算,延用上述的思路很容易想到:

          1.   列出所有產(chǎn)品;

          2.將每種產(chǎn)品的前 10 名取出,分別保存;

          3.將所有的前 10 名取交集;
          但是,使用 CTE 語法只能針對確定個數(shù)的中間結(jié)果做進一步的計算。而我們事先不知道總共有多個產(chǎn)品,這會導(dǎo)致 WITH 中子句個數(shù)是不確定的,這就寫不出來了。
          換一種思路:

          1.將數(shù)據(jù)按產(chǎn)品分組,將每組排序,取出前 10 名;

          2.將所有的前 10 名取交集;
          但這樣需要把第一步的分組結(jié)果保存起來,而這個中間結(jié)果是一個表,其中有個字段要存儲對應(yīng)的分組成員中的前 10 名,也就是字段的取值將是個集合,SQL 不支持這種數(shù)據(jù)類型,還是寫不出來。
          如果有窗口函數(shù)的支持,可以再轉(zhuǎn)換思路,按產(chǎn)品分組后,計算每個銷售員在所有分組的前 10 名中出現(xiàn)的次數(shù),若與產(chǎn)品總數(shù)相同,則表示該銷售員在所有產(chǎn)品銷售額中均在前 10 名內(nèi)。
          select salesfrom ( select sales,     from ( select sales,                   rank() over (partition by product order by amount desc ) ranking            from sales_amount)     where ranking <=10 )group by saleshaving count(*)=(select count(distinct product) from sales_amount)

          這是能寫出來,但這樣復(fù)雜的 SQL,有多少人會寫呢?

          前兩種簡單的思路無法用 SQL 實現(xiàn),只能采用第三種迂回的思路。這里的原因在于 SQL 的一個重要缺點:集合化不徹底
          雖然 SQL 有集合概念,但并未把集合作為一種基礎(chǔ)數(shù)據(jù)類型提供,不能讓變量或字段的取值是個集合,除了表之外也沒有其它集合形式的數(shù)據(jù)類型,這使得大量集合運算在思維和書寫時都需要繞路。
          我們在上面的計算中使用了關(guān)鍵字 top,事實上關(guān)系代數(shù)理論中沒有這個東西(它可以被別的計算組合出來),這不是 SQL 的標準寫法。
          我們來看一下沒有 top 時找前 10 名會有多困難?
          大體思路是這樣:找出比自己大的成員個數(shù)作為是名次,然后取出名次不超過 10 的成員,寫出的 SQL 如下:
          select salesfrom ( select A.sales sales, A.product product,             (select count(*)+1 from sales_amount              where A.product=product AND A.amount<=amount) ranking       from sales_amount A )where product='AC' AND ranking<=10
          select salesfrom ( select A.sales sales, A.product product, count(*)+1 ranking       from sales_amount A, sales_amount B       where A.sales=B.sales and A.product=B.product AND A.amount<=B.amount       group by A.sales,A.product )where product='AC' AND ranking<=10

          這樣的 SQL 語句,專業(yè)程序員寫出來也未必容易吧!而僅僅是計算了一個前 10 名。

          退一步講,即使有 top,那也只是使取出前一部分輕松了。如果我們把問題改成取第 6 至 10 名,或者找比下一名銷售額超過 10% 的銷售員,這些困難仍然存在,還是要采用迂回的思路才能用 SQL 完成。
          造成這個現(xiàn)象的原因就是 SQL 的另一個重要缺點:缺乏有序支持。SQL 繼承了數(shù)學上的無序集合,這直接導(dǎo)致與次序有關(guān)的計算相當困難,而可想而知,與次序有關(guān)的計算會有多么普遍(諸如比上月、比去年同期、前 20%、排名等)。
          SQL2003 標準中增加的窗口函數(shù)提供了一些與次序有關(guān)的計算能力,這使得上述某些問題可以有較簡單的解法,在一定程度上緩解 SQL 的這個問題。但窗口函數(shù)的使用經(jīng)常伴隨著子查詢,而不能讓用戶直接使用次序訪問集合成員,還是會有許多有序運算難以解決。
          我們現(xiàn)在想關(guān)注一下上面計算出來的“好”銷售員的性別比例,即男女各有多少。一般情況下,銷售員的性別信息會記在花名冊上而不是業(yè)績表上,簡化如下:
          employee 員工表
          name 員工姓名,假定無重名
          gender 員工性別

          我們已經(jīng)計算出“好”銷售員的名單,比較自然的想法,是用名單到花名冊時找出其性別,再計一下數(shù)。但在 SQL 中要跨表獲得信息需要用表間連接,這樣,接著最初的結(jié)果,SQL 就會寫成:

          select employee.gender,count(*)from employee,    ( ( select top 10 sales from sales_amount where product='AC' order by amount desc )    intersect    ( select top 10 sales from sales_amount where product='TV' order by amount desc ) ) Awhere A.sales=employee.namegroup by employee.gender
          僅僅多了一個關(guān)聯(lián)表就會導(dǎo)致如此繁瑣,而現(xiàn)實中信息跨表存儲的情況相當多,且經(jīng)常有多層。比如銷售員有所在部門,部門有經(jīng)理,現(xiàn)在我們想知道“好”銷售員歸哪些經(jīng)理管,那就要有三個表連接了,想把這個計算中的 where 和 group 寫清楚實在不是個輕松的活兒了。
          這就是我們要說的 SQL 的下一個重要困難:缺乏對象引用機制,關(guān)系代數(shù)中對象之間的關(guān)系完全靠相同的外鍵值來維持,這不僅在尋找時效率很低,而且無法將外鍵指向的記錄成員直接當作本記錄的屬性對待,試想,上面的句子可否被寫成這樣:
          select sales.gender,count(*)from (…) // …是前面計算“好”銷售員的SQLgroup by sales.gender

          顯然,這個句子不僅更清晰,同時計算效率也會更高(沒有連接計算)。

          我們通過一個簡單的例子分析了 SQL 的幾個重要困難,這也是 SQL 難寫或要寫得很長的主要原因。基于一種計算體系解決業(yè)務(wù)問題的過程,也就是將業(yè)務(wù)問題的解法翻譯成形式化計算語法的過程(類似小學生解應(yīng)用題,將題目翻譯成形式化的四則運算)。SQL 的上述困難會造成問題解法翻譯的極大障礙,極端情況就會發(fā)生這樣一種怪現(xiàn)象:將問題解法形式化成計算語法的難度要遠遠大于解決問題本身
          再打個程序員易于理解的比方,用 SQL 做數(shù)據(jù)計算,類似于用匯編語言完成四則運算。我們很容易寫出 3+5*7 這樣的算式,但如果用匯編語言(以 X86 為例),就要寫成
          mov ax,3mov bx,5mul bx,7add ax,bx

          這樣的代碼無論書寫還是閱讀都遠不如 3+5*7 了(要是碰到小數(shù)就更要命了)。雖然對于熟練的程序員也算不了太大的麻煩,但對于大多數(shù)人而言,這種寫法還是過于晦澀難懂了,從這個意義上講,F(xiàn)ORTRAN 確實是個偉大的發(fā)明。

          為了理解方便,我們舉的例子還是非常簡單的任務(wù)。現(xiàn)實中的任務(wù)要遠遠比這些例子復(fù)雜,過程中會面臨諸多大大小小的困難。這個問題多寫幾行,那個問題多寫幾行,一個稍復(fù)雜的任務(wù)寫出幾百行多層嵌套的 SQL 也就不奇怪了。而且這個幾百行常常是一個語句,由于工程上的原因,SQL 又很難調(diào)試,這又進一步加劇了復(fù)雜查詢分析的難度。

          更多例子

          我們再舉幾個例子來分別說明這幾個方面的問題。
          為了讓例子中的 SQL 盡量簡捷,這里大量使用了窗口函數(shù),故而采用了對窗口函數(shù)支持較好的 ORACLE 數(shù)據(jù)庫語法,采用其它數(shù)據(jù)庫的語法編寫這些 SQL 一般將會更復(fù)雜。
          這些問題本身應(yīng)該也算不上很復(fù)雜,都是在日常數(shù)據(jù)分析中經(jīng)常會出現(xiàn)的,但已經(jīng)很難為 SQL 了。

          集合無序

          有序計算在批量數(shù)據(jù)計算中非常普遍(取前 3 名 / 第 3 名、比上期等),但 SQL 延用了數(shù)學上的無序集合概念,有序計算無法直接進行,只能調(diào)整思路變換方法。
           任務(wù) 1   公司中年齡居中的員工
             
             
          select name, birthdayfrom (select name, birthday, row_number() over (order by birthday) ranking      from employee )where ranking=(select floor((count(*)+1)/2) from employee)
          中位數(shù)是個常見的計算,本來只要很簡單地在排序后的集合中取出位置居中的成員。但 SQL 的無序集合機制不提供直接用位置訪問成員的機制,必須人為造出一個序號字段,再用條件查詢方法將其選出,導(dǎo)致必須采用子查詢才能完成。
           任務(wù) 2   某支股票最長連續(xù)漲了多少交易日
             
             
          select max (consecutive_day)from (select count(*) (consecutive_day      from (select sum(rise_mark) over(order by trade_date) days_no_gain            from (select trade_date,                         case when                              closing_price>lag(closing_price) over(order by trade_date)                         then 0 else 1 END rise_mark                from stock_price) )     group by days_no_gain)
          無序的集合也會導(dǎo)致思路變形。
          常規(guī)的計算連漲日數(shù)思路:設(shè)定一初始為 0 的臨時變量記錄連漲日期,然后和上一日比較,如果未漲則將其清 0,漲了再加 1,循環(huán)結(jié)束看該值出現(xiàn)的最大值。
          使用 SQL 時無法描述此過程,需要轉(zhuǎn)換思路,計算從初始日期到當日的累計不漲日數(shù),不漲日數(shù)相同者即是連續(xù)上漲的交易日,針對其分組即可拆出連續(xù)上漲的區(qū)間,再求其最大計數(shù)。這句 SQL 讀懂已經(jīng)不易,寫出來則更困難了。

          集合化不徹底

          毫無疑問,集合是批量數(shù)據(jù)計算的基礎(chǔ)。SQL 雖然有集合概念,但只限于描述簡單的結(jié)果集,沒有將集合作為一種基本的數(shù)據(jù)類型以擴大其應(yīng)用范圍。
           任務(wù) 3   公司中與其他人生日相同的員工
             
             
          select * from employeewhere to_char (birthday, ‘MMDD’) in    ( select to_char(birthday, 'MMDD') from employee      group by to_char(birthday, 'MMDD')      having count(*)>1 )
          分組的本意是將源集合分拆成的多個子集合,其返回值也應(yīng)當是這些子集。但 SQL 無法表示這種“由集合構(gòu)成的集合”,因而強迫進行下一步針對這些子集的匯總計算而形成常規(guī)的結(jié)果集。
          但有時我們想得到的并非針對子集的匯總值而是子集本身。這時就必須從源集合中使用分組得到的條件再次查詢,子查詢又不可避免地出現(xiàn)。
           任務(wù) 4   找出各科成績都在前 10 名的學生
             
             
          select namefrom (select name      from (select name,                   rank() over(partition by subject order by score DESC) ranking            from score_table)      where ranking<=10)group by namehaving count(*)=(select count(distinct subject) from score_table)
          用集合化的思路,針對科目分組后的子集進行排序和過濾選出各個科目的前 10 名,然后再將這些子集做交集即可完成任務(wù)。但 SQL 無法表達“集合的集合”,也沒有針對不定數(shù)量集合的交運算,這時需要改變思路,利用窗口函數(shù)找出各科目前 10 名后再按學生分組找出出現(xiàn)次數(shù)等于科目數(shù)量的學生,造成理解困難。

          缺乏對象引用

          在 SQL 中,數(shù)據(jù)表之間的引用關(guān)系依靠同值外鍵來維系,無法將外鍵指向的記錄直接用作本記錄的屬性,在查詢時需要借助多表連接或子查詢才能完成,不僅書寫繁瑣而且運算效率低下。
           任務(wù) 5   女經(jīng)理的男員工們
          用多表連接
             
             
          select A.*from employee A, department B, employee Cwhere A.department=B.department and B.manager=C.name and      A.gender='male' and C.gender='female'
          用子查詢
             
             
          select * from employeewhere gender='male' and department in    (select department from department     where manager in          (select name from employee where gender='female'))
          如果員工表中的部門字段是指向部門表中的記錄,而部門表中的經(jīng)理字段是指向員工表的記錄,那么這個查詢條件只要簡單地寫成這種直觀高效的形式:
             
             
          where gender='male' and department.manager.gender='female'
          但在 SQL 中則只能使用多表連接或子查詢,寫出上面那兩種明顯晦澀的語句。
           任務(wù) 6   員工的首份工作公司
          用多表連接
             
             
          select name, company, first_companyfrom (select employee.name name, resume.company company,             row_number() over(partition by resume. name                               order by resume.start_date) work_seq      from employee, resume where employee.name = resume.name)where work_seq=1
          用子查詢
             
             
          select name,    (select company from resume     where name=A.name and           start date=(select min(start_date) from resume                       where name=A.name)) first_companyfrom employee A
          沒有對象引用機制和徹底集合化的 SQL,也不能將子表作主表的屬性(字段值)處理。針對子表的查詢要么使用多表連接,增加語句的復(fù)雜度,還要將結(jié)果集用過濾或分組轉(zhuǎn)成與主表記錄一一對應(yīng)的情況(連接后的記錄與子表一一對應(yīng));要么采用子查詢,每次臨時計算出與主表記錄相關(guān)的子表記錄子集,增加整體計算量(子查詢不能用 with 子句了)和書寫繁瑣度。

          SPL 的引入

          問題說完,該說解決方案了。
          其實在分析問題時也就一定程度地指明了解決方案,重新設(shè)計計算語言,克服掉 SQL 的這幾個難點,問題也就解決了。
          這就是發(fā)明 SPL 的初衷!
          SPL 是個開源的程序語言,其全名是 Structured Process Language,和 SQL 只差一個詞。目的在于更好的解決結(jié)構(gòu)化數(shù)據(jù)的運算。SPL 中強調(diào)了有序性、支持對象引用機制、從而得到徹底的集合化,這些都會大幅降低前面說的“解法翻譯”難度。
          這里的篇幅不合適詳細介紹 SPL 了,我們只把上一節(jié)中的例子的 SPL 代碼羅列出來感受一下:
           任務(wù) 1   

          A
          1 =employee.sort(birthday)
          2 =A1((A1.len()+1)/2)

          對于以有序集合為基礎(chǔ)的 SPL 來說,按位置取值是個很簡單的任務(wù)。

           任務(wù) 2   


          A
          1 =stock_price.sort(trade_date)
          2 =0
          3 =A1.max(A2=if(close_price>close_price[-1],A2+1,0))

          SPL 按自然的思路過程編寫計算代碼即可。

           任務(wù) 3   


          A
          1 =employee.group(month(birthday),day(birthday))
          2 =A1.select(~.len()>1).conj()

          SPL 可以保存分組結(jié)果集,繼續(xù)處理就和常規(guī)集合一樣。

           任務(wù) 4   


          A
          1 =score_table.group(subject)
          2 =A1.(~.rank(score).pselect@a(~<=10))
          3 =A1.(~(A2(#)).(name)).isect()

          使用 SPL 只要按思路過程寫出計算代碼即可。

           任務(wù) 5   


          A
          1 =employee.select(gender=="male" && department.manager.gender=="female")

          支持對象引用的 SPL 可以簡單地將外鍵指向記錄的字段當作自己的屬性訪問。

           任務(wù) 6   


          A
          1 =employee.new(name,resume.minp(start_date).company:first_company)

          SPL 支持將子表集合作為主表字段,就如同訪問其它字段一樣,子表無需重復(fù)計算。

          SPL 有直觀的 IDE,提供了方便的調(diào)試功能,可以單步跟蹤代碼,進一步降低代碼的編寫復(fù)雜度。
          對于應(yīng)用程序中的計算,SPL 提供了標準的 JDBC 驅(qū)動,可以像 SQL 一樣集成到 Java 應(yīng)用程序中:
             
             
          Class.forName("com.esproc.jdbc.InternalDriver");Connection conn =DriverManager.getConnection("jdbc:esproc:local://");Statement st = connection.();CallableStatement st = conn.prepareCall("{call xxxx(?,?)}");st.setObject(1, 3000);st.setObject(2, 5000);ResultSet result=st.execute();...


          GitHub:https://github.com/SPLWare/esProc

          重磅!開源SPL交流群成立了

          簡單好用的SPL開源啦!

          為了給感興趣的小伙伴們提供一個相互交流的平臺,

          特地開通了交流群(群完全免費,不廣告不賣課)

          需要進群的朋友,可長按掃描下方二維碼

          本文感興趣的朋友,請到閱讀原文去收藏 ^_^

          瀏覽 1821
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操逼动视频 | 中文字幕+乱码+中文乱码91 | 人人擦人人 | 青青无码免费播放网址 | 色77777|