SQL SERVER With語法[轉]

今天在論壇上看到一個舉例,關於sql server 的示例。1/25/50/100美分,多少種可能拼湊成2美圓。node

    看了其中第一條語法,放在SQL SERVER中測試,發現真的列舉出全部組合成2美圓的方式。算法

   因而仔細研究語法,發現用了With關鍵字。sql

 

   發現很長時間沒有使用SQL SERVER數據庫,真的有點落後了。因而見到那學習了下 With關鍵字。數據庫

1.引言express

 

現實項目中常常遇到須要處理遞歸父子關係的問題,若是把層次關係分開,放在多個表裏經過主外鍵關係聯接,最明顯的問題就是擴展起來不方便,對於這種狀況,通常咱們會建立一個使用自鏈接的表來存放數據。例如存放會員地區數據表結構多是這樣:瀏覽器

 

列名 描述
location_id 地區編號
location_name 地區名稱
parentlocation_id 上級地區編號

 

或者某個部分的職員表結構可能以下所示:學習

 

列名 描述
employee_id 職員編號
employee_name 職員名稱
manager_id 職員的直接上級管理者,和employee_id進行自聯接

 

經過相似表結構,咱們就能夠經過一個表理論上管理無限級數的父/子關係,可是當咱們須要將這些數據讀取出來,不管是填充到一個樹中,或是使用級聯顯 示出來,須要花費必定的精力。傳統的作法,是作一個遞歸調用,首先鏈接數據庫將頂層數據(也就是parent_xxx爲null的記錄)讀取出來,再對每 一條數據進行遞歸訪問填充集合,這種作法須要鏈接數據庫屢次,顯然不是較好的解決方法,那麼咱們能不能經過一次數據庫訪問,將數據所有讀取出來,而且爲了 按照父子關係造成集合,使返回的數據知足某種格式。測試

2. 分析

 

理想狀況下,若是父/子關係數據時嚴格按照關係結構添加到數據庫中,亦即首先添加某條父記錄,接着添加該父記錄的子記錄,若是子記錄還包含子記錄的 話繼續添加,最終數據表中父/子關係按規則排列數據,咱們就可使用某種算法填充集合,可是正如咱們所說,這是理想狀況,實際狀況下數據常常會發生改變, 致使數據沒有規律可言,以下圖所示,這樣的話讀取數據填充集合就不太容易的。網站

clip_image001

因此咱們要作的就是經過查詢使數據庫返回的數據知足這種格式,那麼咱們的思路是首先查找頂層(0層)記錄,再查詢第1層記錄,接下來是第2層、第3層直到第n層。由於層數是不肯定的,因此仍然須要使用遞歸訪問。ui

SQL Server 2005中提供了新的with關鍵字,用於指定臨時命名的結果集,這些結果集稱爲公用表表達式(CTE)。該表達式源自簡單查詢,而且在SELECT、 INSERT、UPDATE或DELETE 語句的執行範圍內定義。該子句也可用在 CREATE VIEW 語句中,做爲該語句的 SELECT 定義語句的一部分。公用表表達式能夠包括對自身的引用。這種表達式稱爲遞歸公用表表達式。

其語法爲:

 [ WITH <common_table_expression> [ ,...n ] ]
<common_table_expression>::=
expression_name[ ( column_name [ ,...n ] ) ]
AS
( CTE_query_definition )

使用with關鍵子的一個簡單示例,如下代碼將tb_loc表中數據源樣輸出:

WITH locs(id,name,parent)
AS
(
    SELECT * FROM tb_loc
)
SELECT * FROM locs

爲了建立良好層次記錄結構集,使用with關鍵字首先讀取頂層記錄,而且針對每一條頂層記錄讀取其子記錄,直到讀取到最底層級記錄,最後將全部的記錄組合起來,這裏用到了UNION ALL關鍵字,用於將多個查詢結果組合到一個結果集中。

接下來就可使用該關鍵字建立存儲過程返回結果集,並附加每條記錄所位於的「層」數,以下圖所示:


最後須要在前臺界面將其顯示出來,因爲記錄已經按層次返回,須要作的就是按層次首其輸出,首先將第0層數據輸出,接下來將遍歷第0層數據,將第一層 數據添加到合適的父對象中,重複此過程直到填充結果。那麼這裏的難題就在於如何查找父對象,咱們固然能夠遍歷集合,可是這麼作的話若是數據量很大將致使效 率低下。既然能夠獲得當前對象所位於的層的信息,就也是這樹倒置的樹是一層一層向下填充的,咱們能夠定義一個臨時集合變量,存儲當前層上一層的全部父對 象,在插入當前層對象時遍歷集合變量以插入到合適的位置,同時咱們還必須保證在逐層讀取數據時臨時集合變量中持有的始終時當前層上一層全部的對象,程序流 程圖以下所示:

01

根據以上分析,咱們就能夠編寫實現代碼了(爲了方便,將本文中用到的數據表和建立記錄等SQL語句一併給出)。

3. 實現

3.1 打開SQL Server 2005 Management Studio,選擇某個數據庫輸入如下語句建立表結構:

CREATE TABLE [tb_loc](
    [id] [int],
    [name] [varchar](16),
    [parent] [int]
)

GO

3.2 建立測試數據:

INSERT tb_loc(id,name,parent) VALUES( 1,'河北省',NULL) 
INSERT tb_loc(id,name,parent) VALUES( 2,'石家莊',1) 
INSERT tb_loc(id,name,parent) VALUES( 3,'保定',1) 
INSERT tb_loc(id,name,parent) VALUES( 4,'山西省',NULL) 
INSERT tb_loc(id,name,parent) VALUES( 5,'太原',4) 
INSERT tb_loc(id,name,parent) VALUES( 6,'新華區',2) 
INSERT tb_loc(id,name,parent) VALUES( 7,'北焦村',6) 
INSERT tb_loc(id,name,parent) VALUES( 8,'大郭村',6) 
INSERT tb_loc(id,name,parent) VALUES( 9,'河南省',NULL) 
INSERT tb_loc(id,name,parent) VALUES( 10,'大郭村南',8) 
INSERT tb_loc(id,name,parent) VALUES( 11,'大郭村北',8) 
INSERT tb_loc(id,name,parent) VALUES( 12,'北焦村東',7) 
INSERT tb_loc(id,name,parent) VALUES( 13,'北焦村西',7) 
INSERT tb_loc(id,name,parent) VALUES( 14,'橋東區',3) 
INSERT tb_loc(id,name,parent) VALUES( 15,'橋西區',3) 

GO

3.3 建立pr_GetLocations存儲過程:

CREATE PROCEDURE pr_GetLocations
AS
BEGIN
    WITH locs(id,name,parent,loclevel)
    AS
    (
        SELECT id,name,parent,0 AS loclevel FROM tb_loc
        WHERE parent IS NULL
        UNION ALL
        SELECT l.id,l.name,l.parent,loclevel+1 FROM tb_loc l 
            INNER JOIN locs p ON l.parent=p.id
    )

    SELECT * FROM locs
END

3.4 在Visual Studio 2008裏建立解決方案並新建一個網站。

3.5 在網站中添加APP_Code目錄,並建立Location實體類,該類標識了所在地編號和名稱,而且保存了父級所在地編號和它所包含的全部子所在地的集合:

public class Location
{
    public int Id
    {
        get;
        set;
    }

    public string Name
    {
        get;
        set;
    }

    public LocationCollection SubLocations
    {
        get;
        set;
    }

    public int ParentId
    {
        get;
        set;
    }

    public Location()
    {
        Id = 0;
        Name = string.Empty;
        SubLocations = new LocationCollection();

        ParentId=0;
    }
}

3.5 以上代碼使用了LocationCollection集合類,使用泛型集合建立該類(一樣位於APP_Code目錄下):

using System.Collections.Generic;

public class LocationCollection:List<Location>
{
    
}

3.6 在APP_Code目錄下建立DAO類用於訪問數據庫,添加必要的命名空間引用:

using System;
using System.Data;
using System.Data.SqlClient;

public class DAO
{
}

3.7編寫GetLocations方法,返回所在地集合對象(請根據實際狀況修改數據庫鏈接字符串):

public LocationCollection GetLocations()
{
    LocationCollection locs = new LocationCollection();

    using (SqlConnection conn = new
        SqlConnection("server=.;uid=sa;pwd=00000000;database=temp;"))
    {
        conn.Open();

        SqlCommand cmd = new SqlCommand();
        cmd.CommandText = "pr_GetLocations";
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Connection = conn;

        SqlDataReader reader = cmd.ExecuteReader();
                    
        int level = 0;
        int oldlevel = 1;

        LocationCollection container=new LocationCollection();
        LocationCollection current = new LocationCollection();

        while (reader.Read())
        {
            Location loc = GetLocationFromReader(reader, out level);

            if (level == 0)
            {
                locs.Add(loc);
                container.Add(loc);                    
            }
            else
            {
                if (oldlevel != level)
                {
                    container.Clear();

                    foreach (Location l in current)
                        container.Add(l);

                    current.Clear();
                    oldlevel = level;
                }

                current.Add(loc);

                CreateLocation(container, loc);
            }                
        }
    }

    return locs;
}

在該方法按照如下步驟執行:

1. 使用命令對象對象執行pr_GetLocations存儲過程返回結果集

2. 若是數據閱讀器讀取了數據(reader.Read方法返回true)執行:

2.1.從數據閱讀器當前記錄中讀取Location對象,並返回層數信息(out level)

2.2.若是是第一層(level等於0)填充locs集合,並加入到container對象

2.3.若是不是第一層根據層標誌(oldlevel)判斷當前層是不是新的一層

2.4 若是當前層是新的一層清空container集合並將current集合中實體複製到container集合中,清空current集合並置層標誌(oldlevel)

2.5 將當前對象添加到current集合中

2.6 調用CreateLocation方法從container上層集合中匹配當前實體父級對象並加入父對象的子集合中

3. 重複第2步直到讀取徹底部數據

能夠看到container集合始終保存了當前層的上層全部的實體對象,而且爲了在更換層數後可以正確的更新container集合,使用current集合保存當前層的實體對象。

3.8 編寫GetLocationFromReader方法,用於從數據閱讀器中返回Location實體對象,並將層數信息使用out參數返回:

private Location GetLocationFromReader(SqlDataReader reader, out int level)
{
    Location loc = new Location();
    loc.Id = Convert.ToInt32(reader["id"]);
    loc.Name = Convert.ToString(reader["name"]);

    object o = reader["parent"];
    if (o != DBNull.Value)
        loc.ParentId = Convert.ToInt32(o);

    level = Convert.ToInt32(reader["loclevel"]);

    return loc;
}

3.9 編寫CreateLocation方法,該方法遍歷實體集合找到與當前實體對象的父級編號匹配的實體,並將當前實體加入到父級實體的子集合中:

private void CreateLocation(LocationCollection container, Location loc)
{
    foreach (Location location in container)
    {
        if (location.Id == loc.ParentId)
        {
            location.SubLocations.Add(loc);
            break;
        }
    }
}

3.10 向Default.aspx頁面上添加TreeView控件:

<asp:TreeView ID="trvLocation" runat="server" Font-Size="12px"
    ShowLines="True">
</asp:TreeView>

3.11 在Default.aspx頁面後置代碼中編寫BindData數據綁定方法:

private void BindData()
{
    DAO dao = new DAO();

    LocationCollection locs = dao.GetLocations();

    TreeNodeCollection nodes = CreateTreeNodes(locs);

    foreach (TreeNode node in nodes)
    {
        trvLocation.Nodes.Add(node);
    }
}

3.12 BindData方法調用了CreateTreeNode方法返回節點集合,該方法中遞歸調用自身以獲得所有所在地節點:

private TreeNodeCollection CreateTreeNodes(LocationCollection locs)
{
    TreeNodeCollection nodeColl = new TreeNodeCollection();

    foreach (Location loc in locs)
    {
        TreeNode node = new TreeNode(loc.Name, loc.Id.ToString());
        
        if (loc.SubLocations.Count > 0)
        {
            TreeNodeCollection subColl = CreateTreeNodes(loc.SubLocations);

            foreach (TreeNode subNode in subColl)
                node.ChildNodes.Add(subNode);
        }

        nodeColl.Add(node);
    }

    return nodeColl;
}

3.13 最後在頁面加載事件裏執行數據綁定:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        this.BindData();
    }
}

3.14 在瀏覽器中預覽結果:

clip_image001[6]

4. 總結

原來在處理相似父子關係時老是找不到好的解決辦法,如今經過SQL Server 2005裏的新特性能夠較爲合理的解決該類問題,在這裏主要用到了with關鍵字實現遞歸訪問,而且在輸出數據時一樣使用了遞歸的方法。若是各位有更好的實現方式,請不不吝賜教。

本文示例代碼下載:示例代碼

 


 

感謝各位支持,在SQL Server Management Studio中抓取了查詢單表和以上算法的執行計劃,供你們參考吧

1.select * from tb_loc

2. exec pr_GetLocations

相關文章
相關標籤/搜索