posts - 5, comments - 0, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          2012年8月10日

          SQL 不同于與其他編程語言的最明顯特征是處理代碼的順序。在大數編程語言中,代碼按編碼順序被處理,但是在SQL語言中,第一個被處理的子句是FROM子句,盡管SELECT語句第一個出現,但是幾乎總是最后被處理。

                每個步驟都會產生一個虛擬表,該虛擬表被用作下一個步驟的輸入。這些虛擬表對調用者(客戶端應用程序或者外部查詢)不可用。只是最后一步生成的表才會返回 給調用者。如果沒有在查詢中指定某一子句,將跳過相應的步驟。下面是對應用于SQL server 2000和SQL Server 2005的各個邏輯步驟的簡單描述。


          復制代碼
          (8)SELECT (9)DISTINCT  (11)<Top Num> <select list>
          (
          1)FROM [left_table]
          (
          3)<join_type> JOIN <right_table>
          (
          2)ON <join_condition>
          (
          4)WHERE <where_condition>
          (
          5)GROUP BY <group_by_list>
          (
          6)WITH <CUBE | RollUP>
          (
          7)HAVING <having_condition>
          (
          10)ORDER BY <order_by_list>
          復制代碼

          邏輯查詢處理階段簡介

          1. FROM:對FROM子句中的前兩個表執行笛卡爾積(Cartesian product)(交叉聯接),生成虛擬表VT1
          2. ON:對VT1應用ON篩選器。只有那些使<join_condition>為真的行才被插入VT2。
          3. OUTER(JOIN):如 果指定了OUTER JOIN(相對于CROSS JOIN 或(INNER JOIN),保留表(preserved table:左外部聯接把左表標記為保留表,右外部聯接把右表標記為保留表,完全外部聯接把兩個表都標記為保留表)中未找到匹配的行將作為外部行添加到 VT2,生成VT3.如果FROM子句包含兩個以上的表,則對上一個聯接生成的結果表和下一個表重復執行步驟1到步驟3,直到處理完所有的表為止。
          4. WHERE:對VT3應用WHERE篩選器。只有使<where_condition>為true的行才被插入VT4.
          5. GROUP BY:按GROUP BY子句中的列列表對VT4中的行分組,生成VT5.
          6. CUBE|ROLLUP:把超組(Suppergroups)插入VT5,生成VT6.
          7. HAVING:對VT6應用HAVING篩選器。只有使<having_condition>為true的組才會被插入VT7.
          8. SELECT:處理SELECT列表,產生VT8.
          9. DISTINCT:將重復的行從VT8中移除,產生VT9.
          10. ORDER BY:將VT9中的行按ORDER BY 子句中的列列表排序,生成游標(VC10).
          11. TOP:從VC10的開始處選擇指定數量或比例的行,生成表VT11,并返回調用者。

          注:步驟10,按ORDER BY子句中的列列表排序上步返回的行,返回游標VC10.這一步是第一步也是唯一一步可以使用SELECT列表中的列別名的步驟。這一步不同于其它步驟的 是,它不返回有效的表,而是返回一個游標。SQL是基于集合理論的。集合不會預先對它的行排序,它只是成員的邏輯集合,成員的順序無關緊要。對表進行排序 的查詢可以返回一個對象,包含按特定物理順序組織的行。ANSI把這種對象稱為游標。理解這一步是正確理解SQL的基礎。

          因為這一步不返回表(而是返回游標),使用了ORDER BY子句的查詢不能用作表表達式。表表達式包括:視圖、內聯表值函數、子查詢、派生表和共用表達式。它的結果必須返回給期望得到物理記錄的客戶端應用程序。例如,下面的派生表查詢無效,并產生一個錯誤:

          select * 
          from(select orderid,customerid from orders order by orderid)
          as d

          下面的視圖也會產生錯誤

          create view my_view
          as
          select
          *
          from orders
          order by orderid

                在SQL中,表表達式中不允許使用帶有ORDER BY子句的查詢,而在T—SQL中卻有一個例外(應用TOP選項)。

                所以要記住,不要為表中的行假設任何特定的順序。換句話說,除非你確定要有序行,否則不要指定ORDER BY 子句。排序是需要成本的,SQL Server需要執行有序索引掃描或使用排序運行符。

          posted @ 2012-08-10 15:58 火炎炎 閱讀(171) | 評論 (0)編輯 收藏

          2012年7月19日

          CMPP2.0開發

          一、CMPP協議簡介
          中國移動通信互聯網短信網關接口協議(China Mobile Peer to Peer CMPP),是中國移動夢網內部各SMS參與節點相互交換SMS的官方協議。作為夢網的參與方,移動夢網的增值服務商(Service Provider SP )要按照此協議規范實現SP的部分,才可以將自己的短信通過移動的GSM網絡的數據通道傳輸到最終手機用戶上。
          實際上,協議規范了3個方面的內容:
          。SP與移動的互聯網短信網關(Internet Short Message Gateway,ISMG)之間的接口協議
          。ISMG之間的接口協議(譬如移動各省、市之間的短信息交換通過ISMG之間進行)
          。ISMG與匯接網關(Gateway Name Server GNS,類似互聯網上的DNS服務器)之間的接口協議,譬如跨省之類的短信需要GNS的幫助指出當前ISMG該如何傳遞短信。
          其中,后二方面屬于移動短信息系統內部實現,對于SP來講大概可以“透明”來看待,只要實現了SP同ISMG的正確交互,就可以實現接入移動夢網短信系統。我們關心的只是SP端的開發細節。
          二、CMPP交互模式
          從手機用戶角度講,按短信的發起/接收路徑來講,有兩個叫法:
          MT(Short Message Mobile Terminated, SMMT),短信接收,短信從SP發送到手機用戶。
          MO (Short Message Mobile Originate,SMMO),短信發送,短信從手機用戶端發送到目標SP。
          這兩類短信交互,從SP端來看,都是屬于Socket傳輸應用,CMPP的協議是以TCP/IP協議作為底層承載協議的,屬于TCP/IP協議棧之上的應用。
          SP同ISMG的交互連接分長連接短連接
          所謂短連接,就是一次連接,傳輸一個消息,然后等待回復后拆除連接,顯然,效率很低,所以,基本上不被考慮(實際應用移動也不允許SP采用短連接,只是不明白移動為什么還要寫入文檔? ISMG間會需要?)
          所謂長連接,就是SP建立同ISMG連接,然后不斷將數據包(一個個CMPP消息)發送到ISMG,此處發送不必等待某條消息的ISMG回應消息返回,就接著發送下一個消息。同時,等待ISMG返回信息或者等待ISMG發送給SP的消息。發送同接收消息不是一定要同步的,實際采用異步(同時也時雙工)模式。從效率上,顯然,必須全雙工的異步模式才能夠滿足實際應用需求。
          如下圖(摘自CMPP2.0官方文檔)所示,演示了長連接模式數據傳輸過程:
           
           
           
           
           
           
           

          三、SP端開發
          1.         消息分類
          首先,圖中的CMPP消息有很多種,SP同ISMG之間交流這些消息。大體上這些消息發出后,對方往往需要回復一個應答(RESP)類消息。注意,這些消息大多具有方向性,也就是說只能夠從一端到另一端,而不可反方向進行,有些(少數)則可兩端都能夠發出。以下信息主要來源于移動的文檔,但針對大家易混淆或源文檔解釋不夠詳細做了明確和補充。具體見下表:
                 消息名
          傳遞方向
          解釋說明
          CMPP­_CONNECT
          SP---àISMG
          CMPP_CONNECT操作的目的是SP向ISMG注冊作為一個合法SP身份,此消息需要向ISMG發出驗證信息,驗證方式采用md5加密密碼方式,若注冊成功后即建立了應用層的連接(否則ISMG會立即斷開Socket),此后SP可以通過此ISMG接收和發送短信。
          ISMG以CMPP_CONNECT_RESP消息響應SP的請求。具體的算法實現參考CMPP2.0文檔和本文附件代碼。
          CMPP_CONNECT_RESP
          SPß---ISMG
          ISMG對CMPP_CONNECT消息的回復(無論是否驗證成功);如果未通過,會在消息中包含參考信息,但ISMG會立即斷開連接。
          CMPP­_ACTIVE_TEST
          SPßàISMG
          這個消息通信雙方都可以發出,目的是在沒有其他消息發送時,保持雙方的通信鏈路的連接,避免系統認為通信通道已經關閉。每一個收到此消息的實體應當返回CMPP_ACTIVE_TEST_RESP消息,以“禮節性”表示自己的還在通信,維持數據連接有效性。
          不過,據網友交流,有些廠家實現的ISMG,僅僅靠自己發出此消息等待SP回答CMPP_ACTIVE_TEST_RESP來確定數據鏈路的有效性,而忽略SP的CMPP_ACTIVE_TEST消息(有些霸道吧?)這個值得注意,不要僅僅實現發送而不響應此消息,避免數據連接失效。
          CMPP_ACTIVE_TEST_RESP
          SPßàISMG
          對通信的另一端的CMPP_ACTIVE_TEST消息的回復。作用參考CMPP_ACTIVE_TEST的解釋。
          CMPP­_SUBMIT
          SP---àISMG
          在正確建立了數據連接后,SP向ISMG發送一個SMS數據包。本消息需要仔細研究。接收到此消息后,ISMG需要以CMPP_SUBMIT_RESP消息作為回答。如果在一定時間時間內(移動給出的參考值60秒)內未得到消息回應,那么SP需要重新發送此數據包,以確保消息得到投遞。如果重發達到3次后仍然得不到回應,SP端應該考慮可能ISMG已經失效,應當停止發送此短消息。
          CMPP­_SUBMIT_RESP
          SPß---ISMG
          該消息由ISMG發送給SP,同時返回一個“收條”(源CMPP_SUBMIT消息的ISMG端的標示MSGID)給SP,表示“我ISMG已經確認收到你這條消息了”。收到此消息后,SP需要保留此“收條”,因為后面ISMG會最終報告本消息是否正確發送到用戶手機。那個報告就是以此消息的“收條”作為確認那一條消息的。
          CMPP_QUERY
          SP---àISMG
          這個查詢不是查詢單條消息的,是查詢SP發送給ISMG的短信的業務情況。可以查總計數,還可以分類查詢。(基本就是發起對移動sms業務數據庫的查詢統計)
          CMPP_QUERY_RESP
          SPß---ISMG
          ISMG將查詢的數據返回給SP。
          CMPP_CANCEL
          SP---àISMG
          SP發起的取消某條消息的命令消息,其中包含了之前已經發送給ISMG消息的“收條”以便ISMG可以確定是那一條消息。如果消息已經發送給用戶了,那么此消息/命令會無效,ISMG返回失敗。
          CMPP_CANCEL_RES
          SPß---ISMG
          ISMG返回的對CMPP_CANCEL的回復,并告知是否刪除成功。
          CMPP_DELIVER
          SPß---ISMG
          當有MO或者狀態報告時,ISMG發送此消息。注意,此消息的數據可以是用戶手機發送給SP的消息,也可是對于之前SP發送到ISMG的短信的最終狀態的回復,報告短信的最終狀態。
          CMPP_DELIVER_RESP
          SP---àISMG
          SP禮節性的回復告知收到CMPP_DELIVER消息。要指出SP報告的CMPP_DELIVER消息的MSGID,以便ISMG知道那一條消息SP已經確認收到。
          CMPP_TERMINAT
          SPßàISMG
          SP和ISMG都可以主動發消息給對方,自己這端由于某種原因需要終止當前的數據連接。終止后,要經過重新Connection(驗證)之后才可以(進入事務階段)發送SMS數據消息。
          CMPP­_TERMINATE_RES
          SPßàISMG
          通知對方,本端已經最好撤除連接的準備。
                              
          2.         交互階段
          整個CMPP協議交互分為驗證事務兩個階段。驗證階段,發送CMPP_CONNECTION消息進行驗證,通過驗證后(必須要通過才)進入CMPP事務階段,可以發送短信數據了。上表中的CMPP_CONNECTION以下的消息都屬于事務階段的消息。
           
          3.         消息數據結構
          每一個消息包含 消息頭消息體兩個部分,頭固定長度為12字節,其他消息長度各異,但是同一類型消息的長度是固定的。所有消息的各個字段基本上僅有3種類型:Unsigned Integer (無符號整型)  、Integer(整型)、Octet String(字符串),每種類型具體長度不定,網絡字節順序。
          1、  消息頭(3個Unsigned Integer字段組成):
          4字節的Total_Length (Unsigned Integer),包含了此消息的總計(包括了頭部分)長度。
          4字節的Command_Id(Unsigned Integer),指明了此消息到底是什么消息,就是上表中消息的枚舉值。應用程序根據此值確定本數據包到底是什么消息,從而可以按照確定的消息類型,解析余下的消息體。
          4字節的Sequence_Id(Unsigned Integer),指明了此數據包在發送此消息端的唯一編號。這個唯一編號,實際上可以看作流水操作編號。因為分析到交互模式我們看到,SP發送數據到ISMG,不是每發送一個就停下來等待ISMG的回復,而是“一下子”發送多個數據包過去,然后等待ISMG的回應。然而,怎么知道回應的消息是到底對應之前發送過去的消息中的那一條呢?本字段就是解決此難題。SP按照編號發送消息過去,等待ISMG的回應—一般情形下回應消息數據結構都有表明本消息回應的是SP發出的哪一條消息,這個對應就是依靠Sequence_Id。它并不要求一定要嚴格唯一,但是在給定的一段時間內,必須唯一(基本上只要SP發送過去的消息中沒有重復就行了)。如果是需要SP回答的消息,SP也必須將ISMG發送過來的消息的Sequence_Id填入相應字段,表明這是某個消息的回應。SP端和ISMG端Sequence_ID都沒有確定具體的算法。SP可以(但不推薦)采用數據庫的唯一Id作為此值。
           
          2、消息體。消息體長度根據消息不同,長度不一。其他的參考移動的文檔《中國移動通信互聯網短信網關接口協議(China Mobile Peer to Peer, CMPP)(V2.0)》,這里著重講講2個重要消息的消息體數據結構:
                 CMPP­_SUBMIT的消息體:
          字段名
          長度(byte)
          類型
          描述
          Msg_Id
          8
          Unsigned Integer
          信息標識,應該由SP側ISMG本身產生,本處填空,供ISMG傳輸時使用。SP提交時候應當留空。
          Pk_total
          1
          Unsigned Integer
          相同Msg_Id的信息總條數,從1開始。如果一條消息長度超多一條短信,可能需要分解成多條消息,那么實際上這多條消息屬于一條完整消息,所以可以根據此給分解得到的多條短信進行編號,那么總計需要編成多少條短信,此處就填寫多少。
           
          Pk_number
          1
          Unsigned Integer
          相同Msg_Id的信息序號,從1開始。編號決定消息的相對位置。
          Registered_Delivery
          1
          Unsigned Integer
          是否要求返回狀態確認報告:
          0:不需要
          1:需要
          2:產生SMC話單(該類型短信僅供網關計費使用,不發送給目的終端)。
          一般情況下,都需要確認報告。SMC話單也需要返回是否成功的報告。這條消息用于包月SMC時,當你發送消息給移動的ISMG,移動的計費系統會一次性扣除用戶的信息費,但是此消息不會送到用戶手機。但是注意,有的ISMG廠商(很可能是移動要求)實現此消息時候,如果你并沒有發送任何此包月類型的消息給用戶手機,是不發生扣費行為的。移動會認為這是屬于違規的“代收費”行為,會影響同移動的合作關系。
          Msg_level
          1
          Unsigned Integer
          信息級別,信息的優先級。不過實際當中,感覺ISMG端并沒有區分優先級。
          Service_Id
          10
          Octet String
          業務類型,是數字、字母和符號的組合。這個表示業務的字符串可以給發出的短信分類。通過此字段大約可以知道每個服務項目的業務量,有利于統計和計費以及結算。
          Fee_UserType
          1
          Unsigned Integer
          計費用戶類型字段
          0:對目的終端MSISDN計費;
          1:對源終端MSISDN計費;
          2:對SP計費;
          3:表示本字段無效,對誰計費參見Fee_terminal_Id字段。
          Fee_terminal_Id
          21
          Unsigned Integer
          被計費用戶的號碼(如本字節填空,則表示本字段無效,對誰計費參見Fee_UserType字段,本字段與Fee_UserType字段取0、1、2時互斥)
          TP_pId
          1
          Unsigned Integer
          GSM協議類型。詳細是解釋請參考GSM03.40中的9.2.3.9
          TP_udhi
          1
          Unsigned Integer
          GSM協議類型。詳細是解釋請參考GSM03.40中的9.2.3.23,僅使用1位,右對齊
          Msg_Fmt
          1
          Unsigned Integer
          信息格式
            0:ASCII串
            3:短信寫卡操作
            4:二進制信息
            8:UCS2編碼
          15:含GB漢字  
          這個決定了Msg_Content字段的字節內容應該按照什么編碼來解碼/編碼。
          Msg_src
          6
          Octet String
          信息內容來源(SP的企業代碼),例如919000。
          FeeType
          2
          Octet String
          資費類別
          01:對“計費用戶號碼”免費
          02:對“計費用戶號碼”按條計信息費
          03:對“計費用戶號碼”按包月收信息費
          04:對“計費用戶號碼”的信息費封頂
          05:對“計費用戶號碼”的收費是由SP實現。
          通常值為02,注意這是一個字符串,并非整型。
          FeeCode
          6
          Octet String
          資費代碼(以分為單位),如:“0050”代表人民幣0.50元。
          ValId_Time
          17
          Octet String
          存活有效期,格式遵循SMPP3.3協議
          At_Time
          17
          Octet String
          定時發送時間,格式遵循SMPP3.3協議。這個字段可以讓短信在規定的時間給手機用戶。一般情況下不填,保留為空字符串。
          Src_Id
          21
          Octet String
          源號碼
          SP的服務代碼或前綴為服務代碼的長號碼, 網關將該號碼完整的填到SMPP協議Submit_SM消息相應的source_addr字段,該號碼最終在用戶手機上顯示為短消息的主叫號碼。實際上就是服務代碼,可以是長號碼
          DestUsr_tl
          1
          Unsigned Integer
          接收信息的用戶數量(小于100個用戶),通常是1。移動是忌諱一條消息發給多個用戶的。
          Dest_terminal_Id
          21*DestUsr_tl
          Octet String
          接收短信的MSISDN號碼,一個類似字符串數組的結構。受DestUsr_tl的約束,決定了本字段的長度。
          Msg_Length
          1
          Unsigned Integer
          信息長度(Msg_Fmt值為0時:<160個字節;其它<=140個字節)。如果是ASCII碼,可以達到160個英文字母。原因是因為英文字母僅占用7bit,而中文等雙字節代碼需要16位,同時每一個字節最高為都占用,所以最多140個字節,也就是70個漢字。
          Msg_Content
          Msg_length
          Octet String
          信息內容
          Reserve
          8
          Octet String
          保留
          CMPP_SUBMIT消息長度是可變的,將SP端的消息發送給ISMG,ISMG將返回一個MSGID給SP標示此消息,之后(48小時以內,但一般最多幾分鐘內就可),ISMG返回關于此消息的遞送報告。遞送報告同MO短消息是通過另外一個重要消息CMPP­_DELIVER來提交給SP的:
          CMPP­_DELIVER的各個字段:
          字段名
          字節數
          屬性
          描述
          Msg_Id
          8
          Unsigned Integer
          信息標識
          生成算法如下:
          采用64位(8字節)的整數:
          (1)時間(格式為MMDDHHMMSS,即月日時分秒):bit64~bit39,其中
          bit64~bit61:月份的二進制表示;
          bit60~bit56:日的二進制表示;
          bit55~bit51:小時的二進制表示;
          bit50~bit45:分的二進制表示;
          bit44~bit39:秒的二進制表示;
          (2)短信網關代碼:bit38~bit17,把短信網關的代碼轉換為整數填寫到該字段中。
          (3)序列號:bit16~bit1,順序增加,步長為1,循環使用。
          各部分如不能填滿,左補零,右對齊。
          Dest_Id
          21
          Octet String
          目的號碼
          SP的服務代碼,一般4--6位,或者是前綴為服務代碼的長號碼;該號碼是手機用戶短消息的被叫號碼。
          Service_Id
          10
          Octet String
          業務類型,是數字、字母和符號的組合。
          TP_pid
          1
          Unsigned Integer
          GSM協議類型。詳細解釋請參考GSM03.40中的9.2.3.9
          TP_udhi
          1
          Unsigned Integer
          GSM協議類型。詳細解釋請參考GSM03.40中的9.2.3.23,僅使用1位,右對齊
          Msg_Fmt
          1
          Unsigned Integer
          信息格式
            0:ASCII串
            3:短信寫卡操作
            4:二進制信息
            8:UCS2編碼
          15:含GB漢字   
          Src_terminal_Id
          21
          Octet String
          源終端MSISDN號碼(狀態報告時填為CMPP_SUBMIT消息的目的終端號碼)
          Registered_Delivery
          1
          Unsigned Integer
          是否為狀態報告
          0:非狀態報告(MO SMS)
          1:狀態報告
          此字段決定了CMPP­_DELIVER消息到底是手機上行一條消息到SP還是ISMG向SP報告之前發送的消息最終遞送狀態。
          Msg_Length
          1
          Unsigned Integer
          消息長度。是指Msg_Content字段的長度。
          Msg_Content
          Msg_length
          Octet String
          消息內容。如果消息不是狀態報告,那么按照Msg_Fmt指示解碼為特定編碼的字符串內容。
          Reserved
          8
          Octet String
          保留項
          如果是報告,那么Msg_Content將按照狀態報告結構來解釋:
          字段名
          字節數
          屬性
          描述
          Msg_Id
          8
          Unsigned Integer
          信息標識
          SP提交短信(CMPP_SUBMIT)操作時,與SP相連的ISMG產生的Msg_Id。
          這個MSGID實際上就是SP之前發送一個CMPP_SUBMIT消息之后的CMPP_SUBMIT_RESP消息中返回的關于CMPP_SUBMIT消息的ISMG編號.,根據此MSGID可以知道那條消息最終確定的遞送狀態。
          Stat
          7
          Octet String
          發送短信的應答結果,含義與SMPP協議要求中stat字段定義相同,詳見下面。SP根據該字段確定被報告的CMPP_SUBMIT消息的處理狀態。
          Submit_time
          10
          Octet String
          YYMMDDHHMM(YY為年的后兩位00-99,MM:01-12,DD:01-31,HH:00-23,MM:00-59)
          Done_time
          10
          Octet String
          YYMMDDHHMM
          Dest_terminal_Id
          21
          Octet String
          目的終端MSISDN號碼(SP發送CMPP_SUBMIT消息的目標終端)
          SMSC_sequence
          4
          Unsigned Integer
          取自SMSC發送狀態報告的消息體中的消息標識。
          關于State字段,如下解釋:
          消息狀態名
          最終狀態
          描述
          DELIVERED
          DELIVRD
          消息到達目標
          EXPIRED
          EXPIRED
          消息過期
          DELETED
          DELETED
          消息被刪除
          UNDELIVERABLE
          UNDELIV
          消息未被送達
          ACCEPTED
          ACCEPTD
          消息被認可
          UNKNOWN
          UNKNOWN
          未知狀態
          REJECTED
          REJECTD
          消息被彈回
          其他消息結構,具體說明見中移動的CMPP協議。
           
          4.         安全驗證
          CMPP協議在CMPP_CONNECT中傳遞驗證消息。驗證消息為9字節的0+移動給出的密碼+當前時間戳字節數組的MD5算法后的字節。時間戳為 月日時分秒,10位。代碼算法如下:
          private byte[] getMd5Code()
          {
                 byte[] buf=new byte[6+9+_Password.Length+10] ;   
                 byte[] s_a=Encoding.ASCII.GetBytes(_SystemID); //就是企業代碼
                 byte[] s_0={0,0,0,0,0,0,0,0,0};     //9字節的0,此處當作右補0
                 byte[] s_p=Encoding.ASCII.GetBytes(_Password); //密碼
                 this._timestamp =getTimestamp();    //取得認證碼時賦值字符串
                 byte[] s_t=Encoding.ASCII.GetBytes(_timestamp); //10位字符串字節數組
                 s_a.CopyTo(buf,0);   
                 s_0.CopyTo(buf,6);  
                 s_p.CopyTo(buf,6+9);  
                 s_t.CopyTo(buf,6+9+_Password.Length);
                 MD5 md5= new MD5CryptoServiceProvider(); //創建MD5類別
                 return(md5.ComputeHash(buf,0,buf.Length));
          }   
          其中getTimestamp函數為返回例如“0710125959”(7月10號12點59分59秒)這樣的字符串,詳細代碼略過,有興趣請查看本文的附件代碼。
           
          5.         廠商API問題
          筆者公司所處廣東,廣東移動提供了華為的以C 形式的API(SMEIDLL.dll),來幫助大家初期熟悉CMPP協議。但是,經過開發測試,發現華為的API至少存在幾個問題:
          1、  封裝成幾個API函數,但是由于CMPP自身的復雜性,導致這些函數丑陋無比,參數多,而且難以明晰含義。華為的API,內部將CMPP的驗證、事務階段分成幾個函數實現,其中將發送SMS到ISMG功能以函數提供,竟然出現SubmitAExExEx之類的函數說明。
          2、  CMPP的交互是異步的,需要多線程實現一邊發送,一邊接收反饋信息。此API應當是內部維護一個線程進行CMPP_SUBMIT消息發送,但是華為API卻通過空循環之類的操作等待ISMG返回CMPP_SUBMIT_RESP得到相應的MSGID再返回(從而實現消息同步返回)。經過測試,大約需要200毫秒,這個在實際SP的高性能需求場合根本無法滿足系統要求。
          3、  接收短信必須依靠程序主動先發出函數HasDeliverMessage調用 ,得到有消息才可通過GetDeliverSMEx函數獲取消息,顯然,這種方式是低效率的,而且容易產生消息數據包丟失,表現為有些MO消息,SP接收不到。而且,令人疑惑的是,你還不能夠新開一個線程專門來做判斷并接收MO的動作,實際開發中一旦采用線程來做就回發生內存保護錯誤(大概屬于同API自身的線程有沖突)。
          4、  返回錯誤碼,往往又是華為自己定的一套錯誤碼(大概華為設計此API為了適應SMGP CMPP等多個協議),而且經常變動,很是傷腦筋。
          基于以上理由,我認為自己按照CMPP協議開發一個SP端程序,比較能夠滿足一般SP的需求。
           
          四、C#實現
          1、CMPP協議實現類CMPPClient
          通過研究,筆者用C#寫了一組類實現自己的CMPP SP端程序(CMPPClient)。為了實現相關類,還需要編寫一些輔助類,并且首先要解決CMPP協議的數據結構同C#的數據之間的轉換問題。
          CMPP的Octet String 實際上相當于C#中的byte[],所有CMPP消息的Octet String字段出了CMPP_SUBMIT和CMPP_DELIVER的msg_content字段外,其他的都可以認為是ASCII編碼,所以全部可以采用System.Text.Encoding.ASCII進行編碼和解碼;對于Msg_Content字段,由于一般情況下存在漢字信息傳輸.,所以默認的編/解碼應該為Encoding.Default,實際是什么編碼還要考察MSG_Fmt字段指示正文到底是什么編碼。
          對于Unsigned Integer 和Interger字段,需要按照網絡字節順序和x86機器的字節編碼順序對照關系進行轉換,具體我設計了一個工具類提一些轉換方法使用:
          public class BIConvert  //字節 整形 轉換類 網絡格式轉換為內存格式
                 {
                        public static byte[] Int2Bytes(uint i)  //轉換整形數據的網絡次序字節數組
                        {
                               byte[] t=BitConverter.GetBytes(i) ;
                               byte b=t[0];
                               t[0]=t[3];
                               t[3]=b;
                               b=t[1];
                               t[1]=t[2];
                               t[2]=b;
                               return(t);
                        }
           
                        public static uint Bytes2UInt(byte[] bs,int startIndex) //返回字節數組代表的整數數字,4個字節長度的數組
                        {
                               byte[] t=new byte[4];
                               for(int i=0;i<4 && i< bs.Length-startIndex ;i++)
                               {
                                      t[i]=bs[startIndex+i];
                               } 
                               byte b=t[0];
                               t[0]=t[3];
                               t[3]=b;
                               b=t[1];
                               t[1]=t[2];
                               t[2]=b;
                               return(BitConverter.ToUInt32(t,0));
                        }
           
                        public static uint Bytes2UInt(byte[] bs)  //沒有指定起始索引
                        {
                               return( Bytes2UInt(bs,0));
                        }
                 }
           
          其次,為了實現收發數據的“全雙工”,需要設計至少兩個線程處理socket的讀取和數據包寫入。另外,為了自動實現對數據鏈路的保持,以及自動實現數據包重發機值,我還增加了一個值守線程,自動處理以上問題。詳細見后代碼。另外,消息中有很多同時間有關的字段,但是這些時間相關字段并非按照統一規格編碼的,這個需要仔細研究協議或者實現代碼。
          其三,為了解析/編碼數據包方便,我將SP端涉及到的消息以類的形式實現,根據具體的消息類型,將數據包字節解析還原為特定的消息;另一方面,當需要發送一些消息時,將消息的各個字段,根據類型和編碼類型“組裝”成字節數組,以便Socket能夠發送出去。
          其四,為了達到及時處理短消息的收發,我大量采用了C#事件觸發來達到消息通知目的,而且,也設計一組事件參數,供事件的具體監聽者可以掌握需要的信息。
          其五,需要注意多線程間的同步問題。
          其六,填寫CMPP_SUBMIT消息需要注意內容編碼、計費字段正確填寫
           
          2、事件模型
          大體上實現了十多個事件,這些事件具體為:
          當CMPP_DELIVER消息送來的是短消息送達報告時,發生消息送達報告事件:
          public delegate void ReportEventHandler(object sender, ReportEventArgs e); 
                 當CMPP_DELIVER消息送來的是用戶手機MO短消息時,發生短信到達事件,其他程序可以在處理此事件獲得消息的正文、手機號碼、SP服務號碼等信息:
          public delegate void SMSEventHandler(object sender, SMSEventArgs e);  
                 當ISMG發出CMPP_TERMINATE消息時,發生,具體的回應,我在具體實現中先自動進行了回復,產生此事件僅僅向外部程序(此事件的截取者)表達收到此消息,需要進行“善后清場”操作:
                 public delegate void TerminateEventHandler(object sender,TerminateEventArgs e);  
                        當SP主動終止連接時,發出CMPP_TERMINATE消息,ISMG會響應CMPP_TERMINATE_RESP消息,此事件表示收到此回應
                 public delegate void TerminateRespEventHandler(object sender,TerminateRespEventArgs e); 
                 以下兩個事件針對鏈路保持消息CMPP_ACTIVE_TEST及CMPP_ACTIVE_TEST_RESP發生:
                 public delegate void TestEventHandler(object sender,TestEventArgs e);
                 public delegate void TestRespEventHandler(object sender,TestRespEventArgs e);
                 SP發出CMPP_CONNECT消息后,ISMG驗證,然后發出CMPP_CONNECT_RESP消息,此時激活此事件:
                 public delegate void ConnectRespEventHandler(object sender,ConnectRespEventArgs e);
                 SP取消某條端消息,發出CMPP_CANCEL后,ISMG響應此消息返回CMPP_CANCEL_RESP消息時,激活事件:
                 public delegate void CancelRespEventHandler(object sender,CancelRespEventArgs e);
                 SP提交短信后,ISMG返回一個CMPP_SUBMIT_RESP 消息,包含“收條”(MSG_ID)在內,觸發此事件:
                 public delegate void SubmitRespEventHandler(object sender,SubmitRespEventArgs e);
                 查詢ISMG返回消息后,發生:
                 public delegate void QueryRespEventHandler(object sender,QueryRespEventArgs e);
                 當SP驗證通過后,作為應用邏輯需要得到通知,我特此加了此事件:
                 public delegate void LogonSuccEventHandler(object sender,EventArgs e); //當成功登錄系統
                 以下事件,不是基于CMPP消息,而是根據SP同ISMG消息隊列掃描后判斷觸發事件:
                 public delegate void SocketClosedEventHandler(object sender,EventArgs e); //當套接字被檢測到關閉
                 public delegate void FailedItemDeletedEventHandler(object sender,WaitingQueueItemEventArgs e); //當一條存在于等待隊列的消息超過60秒沒有回應
          以上這些事件缺省實現保證了SP端CMPP客戶對于ISMG的響應自動化,提供觸發事件保證調用此客戶端類的系統可以通過事件發生準確的控制SP端的內部狀態,獲取交互信息。
          另一方面,由于這些大多數事件發生于數據包達到后的處理,實際上需要處理事件程序一定要“迅速”解決,或者干脆將消息轉換為可以暫存的消息形式,由其他程序進一步處理。CMPP的SP端要滿足大量短信息應用需求,就必須嚴格控制消息交互處理邏輯不要太復雜,特別不要設計大量I/O處理;如果實在要處理,最好采用異步線程的方式來處理。
           
          3、為了提高效率,開了3個線程:
                 RecvISMGMsgThread   用于處理接收ISMG發送過來的消息,并根據消息、消息解析后的字段內容觸發相應的事件。
                 SendSPMsgThread        用于處理向ISMG發送數據包。注意,有些消息(譬如CMPP_ACTIVE_TEST)是系統自己產生的。另外,有些消息是收到ISMG的消息后需要立即回應給ISMG的,那么這些消息,全部進入內部維護的消息隊列(_outSeqQueue)。該隊列會自動排序消息,所有需要發送的消息,進入此隊列,本線程不斷從隊列取出需要發送的消息,轉換成數據包,通過Socket發送到ISMG.
                 DeamonThread      用于監測數據連接socket是否可用,是否需要發出維持數據連接的測試數據包;有些消息發送過去了,過了協議規定的時間仍然沒有收到RESP消息,那么需要將消息從已經發送的隊列中提取,重新加入到發送隊列中,排隊后等待送出。
                 可以仔細分析提供的代碼,研究其中的具體實現。
           
          五、問題小結
          根據自己的經驗,覺得以下幾點對于整個系統開發較為重要:
          1、一定要正確理解協議。
          很多網友交流時候,總抱怨協議濫,搞不定,其實很多原因屬于自己沒有清楚理解協議。從我的接觸的移動ISMG來看,應該說實現協議還是很嚴格遵守CMPP的描述。倒是,一些網友自己開發的模擬器不是很規范(不是批評,鄭重聲明),需要自己在開發時候引起注意。
          2、多線程互斥問題。
          多個線程之間涉及一些隊列的操作,需要進行同步鎖定,否則容易引起問題。出現異常也需要及時捕獲,并紀錄作為錯誤信息參考,便于排除bug。
          3、自己控制數據包流向和處理時間。
          由于設計目標是高性能,所以在處理socket數據讀寫時候要注意對于一些事件處理不要過多消耗系統資源,避免引起數據包來不及處理而導致數據丟失。特別在數據繁忙時刻往往會使ISMG的吞吐性能下降,需要考慮對這種情況下的流量控制。有時,你不能夠指望ISMG如你所愿及時回應你,更為常見的是ISMG根本不返回RESP類型的數據包。另外,本協議處理數據收發采用阻塞Socket,有網友建議我采用異步非阻塞Socket,我想可能異步非阻塞Socket會更好。
          4、服務監控問題。
          由于一些意外,往往會導致數據連接被中斷,這是,需要建立超時重建連接的機制。我給出的例子并未很好解決,希望其他方家指正。
          5、字節順序問題。
          這個問題,對于初接觸socket編程的人士往往造成很大麻煩。不過,CMPP協議設計的基本數據類型很簡單,僅需要按照本例子參考即可解決。
          6、具體協議應用問題。
          本例僅僅是一個按照協議要求實現CMPP協議的類,完整的SP端方案需要結合自己公司的實際要求,改造或者重用本例。限于篇幅,文中僅僅能夠列出重點片斷,詳細細節清參考供下載的代碼(附注釋)。本文僅僅是針對CMPP2.0協議進行討論開發,協議的詳情請從移動夢網(http://www.monternet.com/moneditor/cs/SP/cmcc/)下載。其他技術參考可以到如下處獲取,也可察看網友的貼字獲取進一步詳細說明:
                 天堂鳥交流論壇(http://www.spzone.net/bbs/index.asp
                 CSDN社區 移動平臺
          (http://community.csdn.net/expert/forum.asp?url=/Expert/ForumsList.asp?roomid=63&typenum=1&whichpage=1)
                 SP論壇(http://www.spforum.net/jishu/Index.asp)
          如果你發現本人的實例存在問題(我想那簡直是一定的了),請不吝賜教myjobsdk@yahoo.com.cn。
           
           后記:現在CMPP3。0也就是移動的MISC平臺版本的需要訂購關系確定才可以計費。不過不是本文討論的重點。

          posted @ 2012-07-19 01:46 火炎炎 閱讀(1181) | 評論 (0)編輯 收藏

          2011年11月24日

          首先明確閉包的概念:一個代碼段被用來做為方法的參數.
          java中沒有直接使用某個方法做為另一個方法的參數的,java使用匿名內部類來模擬這種情況。

          匿名內部類往往是做為一個內部類(接口)的具體實現。在一些平臺類(platform class)中有一些模板方法。模板方法的包含了固定流程。其中某些步驟是調用了內部類(接口)中的某些方法。但是平臺類將這些方法的具體實現延遲到了業務類中。業務類調用平臺類的模板方法,但是傳入匿名內部類的實現做為模板方法的參數。

          示例:

           

          package callback;

          import java.sql.Connection;
          import java.sql.DriverManager;
          import java.sql.SQLException;

          public class AnonymousBusinessTemplateExample2 {

          // 內部接口也是回調接口,只定義抽象方法。
          private interface Callback {
          Object doIt(Connection conn)
          throws SQLException;
          }

          // 模板方法(抽象)
          private Object execute(Callback callback) throws SQLException {
          Connection conn
          = openConnection();
          try {
          return callback.doIt(conn);
          }
          finally {
          closeConnection(conn);
          }
          }

          // 業務方法(具體)
          public Object sqlQuery(final String sql) throws SQLException {
          //匿名內部類做為模板方法的參數來模擬閉包
          return execute(new Callback() {
          public Object doIt(Connection conn) throws SQLException {
          return conn.createStatement().executeQuery(sql);
          }
          });
          }

          public Connection openConnection() throws SQLException {
          return DriverManager.getConnection("", null);
          }

          public void closeConnection(Connection conn) throws SQLException {
          if (conn != null && !conn.isClosed()) {
          conn.close();
          }
          }
          }

           

           

           

          一般內部接口比內部類用的更多。內部類中的方法可以有默認的實現。匿名內部類做為業務方法的參數傳入時,會override默認的方法。內部接口的話,沒有默認的實現。完全將具體的實現交給了匿名內部類。

          匿名內部類的思想是回調,即好茉塢原則。回調的一個好處是decouple。 客戶端只需要關心業務(比如匿名內部類的具體實現)而不用再關心一些資源的連接釋放什么的,這些交給平臺類中的模板方法。ruby的閉包還支持對數組中的每個元素,文件中的每行,結果集中的每個記錄的操作。而用java實現這樣的迭代并操作其中元素非常麻煩。感覺java中用的多的偏模板方法,即邏輯中固定一些流程,初始化及釋放某些資源。






          動態回調函數、匿名內部類和spring中的excute方法

              公司目前采用了spring框架來構建和管理整個web項目。對于持久層的處理,使用了由spring框架提供的對hibernate3的封裝。這樣做無非是為了使用spring提供的對事務的統一管理。當我們用到由spring所封裝的hibernate的時候一定會用到一個類:HibernateTemplate.這是一個對持久層處理封裝的非常完整的類,包括對session的管理(事實上session的獲取于釋放是一個令人頭疼的問題)等等,我們通常會使用HibernateTemplate的excute方法來進行數據庫操作(即使我們調用的也許是別的類似于find、get之類的方法,但是實質上最終還是轉變為了調用excute方法)。對于第一次使用這個方法一定會存在困擾。因為excute方法所需要的參數一個HibernateCallback類型的參數。而在excute方法體內部回調了HibernateCallback類型的doInHibernate方法。這是一個典型的對象回調。就到目前為止也許一切都很清晰。但是實際上如果閱讀了HibernateTemplate的內部代碼就會發現,對于像get、find這樣的方法最終都回調用excute來完成數據庫操作但是調用形式看起來卻很奇怪:

          public Object get(final Class entityClass, final Serializable id, final LockMode lockMode)

                  throws DataAccessException

              {

                  return execute(new HibernateCallback() {

                      public Object doInHibernate(Session session)

                          throws HibernateException

                      {

                          if(lockMode != null)

                              return session.get(entityClass, id, lockMode);

                          else

                              return session.get(entityClass, id);

                      }

                  }, true);

              }

              Excute方法的參數是一種匿名類的方式。為什么要采用匿名類呢(不管怎么說匿名類看起來總是讓人覺得不舒服)?這個地方是否必須采用匿名類呢?

              首先我們來想一想這段代碼涉及到幾個關鍵點:1、回調:excute方法會回調HibernateCallback類型的doInHibernate方法;2、匿名類參數:我們為excute方法提供的參數并不是一個真正生命出來的HibernateCallback實例。3、動態創建回調方法:如果我們打開HibernateCallback類就會發現,其實這是一個abstract類型的類,而他聲明了唯一的一個ie抽象方法就是doInHibernate。問題似乎已經明朗了,如果不采用匿名類,我們需要做的是為HibernateCallback創建一個實現類,并且實現doInHibernate方法。但是最要命的問題是doInHibernate方法的實現對于我們的實際需求來說每一次調用可能都是不一樣的(在doInHibernate方法中我們使用session進行數據庫操作,對于不同的業務邏輯,方法實現必定是不一樣的),采用了匿名類我們不用在代碼重創建新的類型,而且可以動態的創建我們所需要的回調函數。

              總結一下,我們上面所講的并非是如何使用HibernateTemplate這個類。我們得到的結論是:當我們需要動態的創建回調函數的時候,匿名內部類是一個好的方式。注:這種需要動態創建的回調方法通常是一個interface中的接口或者abstract class中的抽象方法。


          posted @ 2011-11-24 16:44 火炎炎 閱讀(889) | 評論 (0)編輯 收藏

          2011年9月14日

            使用過mina的同學應該都遇到到過,在解碼時少包、多包的問題,查閱了很多資料還是迷迷糊糊的,經過

          不懈努力,終于解決了。原來解決方法是那樣的簡單。廢話少說,請看列子。
            
             另外建了一個交流群:19702042,大家可以在線交流

             問題:我發送的是xml字符串數據,在發送數據后,接收方在解碼的時候可能接到1條,也可能是多條,還

          可能是半條或一條半,解決方法就是使用CumulativeProtocolDecoder

             首先,在編碼的時候要把前4位設成標志位,標志消息內容的長度。里面的重點是doDecode的返回值,一

          定要繼承CumulativeProtocolDecoder 哦。

             清看decode的寫法:
          Java代碼 復制代碼 收藏代碼
          1. public class AsResponseDecoder extends CumulativeProtocolDecoder {   
          2.     private static Logger LOG = LoggerFactory.getLogger(AsResponseDecoder.class);   
          3.     private final Charset charset;   
          4.        
          5.     public AsResponseDecoder(Charset charset){   
          6.         this.charset = charset;   
          7.     }   
          8.        
          9.   
          10.     /**  
          11.      * 這個方法的返回值是重點:  
          12.      * 1、當內容剛好時,返回false,告知父類接收下一批內容  
          13.      * 2、內容不夠時需要下一批發過來的內容,此時返回false,這樣父類  
          14.  
          15. CumulativeProtocolDecoder  
          16.      *    會將內容放進IoSession中,等下次來數據后就自動拼裝再交給本類的doDecode  
          17.      * 3、當內容多時,返回true,因為需要再將本批數據進行讀取,父類會將剩余的數據再次推送本  
          18.  
          19. 類的doDecode  
          20.      */  
          21.     public boolean doDecode(IoSession session, IoBuffer in,   
          22.             ProtocolDecoderOutput out) throws Exception {   
          23.            
          24.         CharsetDecoder cd = charset.newDecoder();   
          25.         if(in.remaining() > 0){//有數據時,讀取4字節判斷消息長度   
          26.             byte [] sizeBytes = new byte[4];   
          27.             in.mark();//標記當前位置,以便reset   
          28.             in.get(sizeBytes);//讀取前4字節   
          29.                         //NumberUtil是自己寫的一個int轉byte[]的一個工具類   
          30.             int size = NumberUtil.byteArrayToInt(sizeBytes);   
          31.             //如果消息內容的長度不夠則直接返回true   
          32.             if(size > in.remaining()){//如果消息內容不夠,則重置,相當于不讀取size   
          33.                 in.reset();   
          34.                 return false;//接收新數據,以拼湊成完整數據   
          35.             } else{   
          36.                 byte[] bytes = new byte[size];    
          37.                 in.get(bytes, 0, size);   
          38.                 String xmlStr = new String(bytes,"UTF-8");   
          39.                 System.out.println("------------"+xmlStr);   
          40.                 if(null != xmlStr && xmlStr.length() > 0){   
          41.                     AsResponse resCmd = new AsResponse();   
          42.                     AsXmlPacker.parse(resCmd, xmlStr);   
          43.                     if(resCmd != null){   
          44.                         out.write(resCmd);   
          45.                     }   
          46.                 }   
          47.                 if(in.remaining() > 0){//如果讀取內容后還粘了包,就讓父類再給俺   
          48.   
          49. 一次,進行下一次解析   
          50.                     return true;   
          51.                 }   
          52.             }   
          53.         }   
          54.         return false;//處理成功,讓父類進行接收下個包   
          55.     }   
          56.   
          57.   
          58. }  

          下面附上Encode類
          Java代碼 復制代碼 收藏代碼
          1. public class AsResponseEncoder extends ProtocolEncoderAdapter {   
          2.     private final Charset charset;   
          3.        
          4.     public AsResponseEncoder(Charset charset){   
          5.         this.charset = charset;   
          6.     }   
          7.        
          8.     public void encode(IoSession session, Object message,   
          9.         ProtocolEncoderOutput out) throws Exception {   
          10.         CharsetEncoder ce = charset.newEncoder();   
          11.         IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true);   
          12.            
          13.         AsResponse respCmd = (AsResponse) message;   
          14.            
          15.         String xml = AsXmlPacker.pack(respCmd);//將對象轉成xml   
          16.         byte[] bytes = xml.getBytes();   
          17.         byte[] sizeBytes = NumberUtil.intToByteArray(bytes.length);   
          18.            
          19.         buffer.put(sizeBytes);//將前4位設置成數據體的字節長度   
          20.         buffer.put(bytes);//消息內容   
          21.         buffer.flip();   
          22.         out.write(buffer);   
          23.     }   
          24.   
          25.   
          26. }  

            JDK   ByteBuffer

             

            屬性:

            Mark

            上次position的快照

            Position

            當前讀寫索引未知

            Limit

            緩沖區限制

            Capacity

            緩沖區能力

            Offset

            偏移量

             

            說明:

            • Position(Mark)<=limit<=capacity
            • position==limit時就沒有字節可讀寫了
            • 每次getput都將增加position
            • 重置mark就是設置mark=-1

             

             

            方法:

            Limit(int)

            如果position>limit, position = limit,如果mark>limit, 重置mark

            Mark()

            取當前的position的快照標記mark

            Reset()

            恢復position到先前標記的mark

            Clear()

            limit=capacity , position=0,重置mark,但是不清空數據,為了從頭開始put做準備,其實就是清空數據,因為你put就覆蓋了原來的數據

            Rewind()

            position=0,重置mark,一系列寫操作后,為了從頭開始get做準備,和clear()有用途上的區別,他大部分是用來從頭開始讀取,而clear是大部分用來重頭開始填充,就是清理的意思

            Flip()

            limit=position , position=0,重置mask,為了將buf寫出做好準備,一般是結束buf操作,將buf寫入輸出流時調用,這個必須要調用,否則極有可能position!=limit,導致position后面沒有數據,每次寫入數據到輸出流時,必須確保position=limit

            Remaining()

            返回limit-position,返回緩沖器中的剩余字節

            Wrap(byte[])

            組裝到新的buffercapacity=limit=byte[].lengthposition=0 重置mark

            Slice()

            分割緩沖器,將remaining的空間形成一個新的buffer,新的position=0limit=capacity=remaining,重置mark,和主緩沖區內容共享,其它都獨立

            Duplicate()

            復制緩沖區,內容共享,其它都獨立

            asReadOnlyBuffer()

            和duplicate一樣,只是不可寫

            Compact()

            positionlimit之間的字節移到最前面,position=limit-position,這就是這里的壓縮的意思,一般是結束buf操作,將buf寫入輸出流時調用

            Position(int)

            position=newPosition,如果position<mark,重置mark

            Remaining()

            返回positionlimit之間的字節數

           

           

          JDK ByteBuffer

          Mina IoBuffer

          動態擴展capacity

          支持String讀寫

          線程安全

          可主動釋放緩沖區占用內存

          posted @ 2011-09-14 12:38 火炎炎 閱讀(1815) | 評論 (0)編輯 收藏

          2011年6月27日

          • 如果存在就更新,不存在就插入用一個語句實現
          MERGE  INTO  t_mg a
          USING (
          SELECT   ' the code '  code,  ' the name '  NAME  FROM
           dual) b
          ON  (a.code  =
           b.code)
          WHEN  MATCHED  THEN

          UPDATE   SET  a.NAME  =  b.NAME
          WHEN   NOT  MATCHED  THEN

          INSERT  (code, NAME)  VALUES  (b.code, b.NAME);
          • 分頁算法

          SELECT   *
          FROM  ( SELECT  a. * , ROWNUM rn
          FROM  ( SELECT   *   FROM  t_employees  ORDER   BY
           first_name) a
          WHERE  ROWNUM  <=   500
          )
          WHERE  rn  >   480  ;

          • 抽取/刪除重復記錄

          1) 部分字段重復數據的刪除

          delete   from  表名 a 
          where  a.rowid  !=
           
          (
          select   max (b.rowid)  from
           表名 b 
          where  a.字段1  =  b.字段1  and
           
          a.字段2 
          =
           b.字段2 
          )

          上面語句的執行效率是很低的,可以考慮建立臨時表,講需要判斷重復的字段、rowid插入臨時表中,然后刪除的時候在進行比較。

          create   table  臨時表  as  
          select  a.字段1,a.字段2, MAX (a.ROWID) dataid  from  正式表 a  GROUP   BY
           a.字段1,a.字段2;
          delete   from
           表名 a 
          where  a.rowid  !=
           
          (
          select  b.dataid  from
           臨時表 b 
          where  a.字段1  =  b.字段1  and
           
          a.字段2 
          =
           b.字段2 
          )

          2) 完全重復記錄的刪除

          用下面語句獲取到去掉重復數據后的記錄:

          select   distinct   *   from  表名

          可以將查詢的記錄放到臨時表中,然后再將原來的表記錄刪除,最后將臨時表的數據導回原來的表中。如下:

          CREATE   TABLE  臨時表  AS  ( select   distinct   *   from  表名);
          drop   table
           正式表;
          insert   into  正式表 ( select   *   from
           臨時表);
          drop   table  臨時表;

          執行上記SQL語句,可以查尋到數據庫中的鎖的情報.

          SELECT   S.SID SESSION_ID, S.USERNAME, DECODE(LMODE,  0 ' None ' 1 ' Null ' 2 ' Row-S (SS) ' 3 ' Row-X (SX) ' ,   4 ' Share ' 5 ' S/Row-X (SSX) ' ,   6 ' Exclusive ' , TO_CHAR(LMODE)) MODE_HELD, DECODE(REQUEST,  0 ' None ' 1 ' Null ' 2 ' Row-S (SS) ' 3 ' Row-X (SX) ' 4 ' Share ' 5 ' S/Row-X (SSX) ' 6 ' Exclusive ' , TO_CHAR(REQUEST)) MODE_REQUESTED, O.OWNER || ' . ' || O. OBJECT_NAME || '  ( ' || O.OBJECT_TYPE || ' ) ' , S.TYPE LOCK_TYPE, L.ID1 LOCK_ID1, L.ID2 LOCK_ID2 
          FROM
           V$LOCK L, SYS.DBA_OBJECTS O, V$SESSION S 
          WHERE  L.SID  =  S.SID  AND   L.ID1  =  O. OBJECT_ID
          SESSION_ID, USERNAME,  MODE_HELD,  MODE_REQUESTED, OBJECT_NAME, LOCK_TYPE, LOCK_ID
          分別是 擁有鎖的SESSION_ID,擁有鎖的USERNAME,鎖的執行模式MODE_HELD,鎖的請求MODE_REQUESTED,鎖所在的數據庫對象名
          ,鎖的類型,鎖的ID

          posted @ 2011-06-27 00:31 火炎炎 閱讀(189) | 評論 (0)編輯 收藏

          主站蜘蛛池模板: 岳阳市| 甘孜县| 泰和县| 潜山县| 长丰县| 乌鲁木齐县| 辉县市| 唐山市| 五华县| 梁山县| 兴宁市| 高台县| 青海省| 宁武县| 西青区| 轮台县| 兴海县| 灌阳县| 湖北省| 灯塔市| 临西县| 仁寿县| 内乡县| 西乌珠穆沁旗| 黑龙江省| 淳化县| 宣城市| 永平县| 彩票| 冷水江市| 万荣县| 田东县| 陈巴尔虎旗| 石景山区| 当涂县| 云梦县| 博兴县| 锡林郭勒盟| 东乡族自治县| 武宁县| 荥阳市|