การพับคิวรีพื้นฐาน TripPin ส่วนที่ — 10

บทช่วยสอนแบบหลายส่วนนี้ครอบคลุมถึงการสร้างส่วนขยายแหล่งข้อมูลใหม่Power Queryแหล่งข้อมูล บทช่วยสอนมีไว้เพื่อจะจัดการแต่ละบทเรียนตามลวตาของตัวเชื่อมต่อที่สร้างขึ้นในบทเรียนก่อนหน้า — โดยเพิ่มความสามารถใหม่ให้กับตัวเชื่อมต่อของคุณแบบเพิ่มหน่วย

ในบทเรียนนี้ คุณจะต้อง:

  • เรียนรู้พื้นฐานของการพับคิวรี
  • เรียนรู้เกี่ยวกับ Table.View ฟังก์ชัน
  • ใช้ตัวจัดการการพับคิวรี OData เป็น:
  • $top
  • $skip
  • $count
  • $select
  • $orderby

คุณลักษณะหนึ่งของภาษา M ที่มีประสิทธิภาพคือความสามารถในการผลักดันให้การแปลงข้อมูลสามารถเข้าไปยังแหล่งข้อมูลที่เกี่ยวข้องได้ ความสามารถนี้เรียกว่า Query Folding (เครื่องมือ/เทคโนโลยีอื่น ๆ ยังอ้างอิงถึงฟังก์ชันที่คล้ายกันเช่น Predicate Pushdown หรือการมอบหมายแบบสอบถาม) เมื่อสร้างตัวเชื่อมต่อแบบเองที่ใช้ฟังก์ชัน M ด้วยความสามารถในการพับคิวรีที่มีอยู่แล้วภายใน เช่น หรือ OData.Feed ตัวเชื่อมต่อ Odbc.DataSource ของคุณจะสืบทอดความสามารถนี้ฟรีโดยอัตโนมัติ

บทช่วยสอนนี้จะเลียนแบบลักษณะการพับคิวรีที่มีอยู่ภายใน OData โดยใช้ตัวจัดการฟังก์ชัน Table.View เพื่อฟังก์ชัน ส่วนนี้ของบทช่วยสอนจะใช้ ตัวจัดการที่ ง่ายกว่าบางตัว (ซึ่งก็คือ เครื่องมือที่ไม่ต้องแยกวิเคราะห์นิพจน์และการติดตามสถานะ)

ในการเข้าใจเพิ่มเติมเกี่ยวกับความสามารถของคิวรีที่บริการ OData อาจมี ให้ดู แบบแผนURL OData v4

หมายเหตุ

ตามที่ระบุไว้ด้านบน ฟังก์ชัน OData.Feed จะมีความสามารถการพับคิวรีโดยอัตโนมัติ เนื่องจากชุด TripPin จะถือว่าบริการ OData เป็นREST APIทั่วไป แทนที่จะเป็นคุณจะต้องใช้ตัวจัดการการ Web.Contents OData.Feed พับคิวรีด้วยตัวคุณเอง เพื่อการใช้งานจริง เราขอแนะให้ คุณใช้ OData.Feed เมื่อใดก็ตามที่เป็นไปได้

โปรดดู เอกสารประกอบ Table.View (ตาราง) โปรดดูข้อมูลเพิ่มเติมเกี่ยวกับ query folding ใน M

การใช้ Table.View

ฟังก์ชัน Table.View อนุญาตให้ตัวเชื่อมต่อแบบปรับแต่งเองสามารถแทนที่ตัวจัดการการแปลงเริ่มต้นให้กับแหล่งข้อมูลของคุณ การ Table.View ดําเนินการนี้จะมีฟังก์ชันอย่างน้อยหนึ่งตัวจัดการที่สนับสนุน ถ้าไม่ได้ระบุตัวจัดการ หรือส่งกลับในระหว่างการประเมินผล โปรแกรม M error จะถอยกลับไปยังตัวจัดการค่าเริ่มต้น

เมื่อตัวเชื่อมต่อแบบปรับแต่งเองใช้ฟังก์ชันที่ไม่รองรับการพับคิวรีโดยนัย Web.Contents เช่น ตัวจัดการการแปลงข้อมูลเริ่มต้นจะใช้ในการพับภายในเครื่องเสมอ ถ้าREST APIการเชื่อมต่อสนับสนุนพารามิเตอร์คิวรีเป็นส่วนหนึ่งของคิวรี จะช่วยให้คุณเพิ่มการปรับให้เหมาะสมที่อนุญาตให้การแปลงงานถูกผลัก Table.View ไปยังบริการ

ฟังก์ชัน Table.View มีลายเซ็นต่อไปนี้:

Table.View(table as nullable table, handlers as record) as table

การใช้งานของคุณจะตัดฟังก์ชันแหล่งข้อมูลหลักของคุณ มีตัวจัดการที่ต้องใช้สองแบบใน Table.View :

  • GetType—ส่งกลับผลลัพธ์ของ table type คิวรีที่คาดหวังไว้
  • GetRows—ส่งกลับผลลัพธ์ table ที่แท้จริงของฟังก์ชันแหล่งข้อมูลของคุณ

การใช้งานที่ง่ายที่สุดจะคล้ายกับรายการต่อไปนี้:

TripPin.SuperSimpleView = (url as text, entity as text) as table =>
    Table.View(null, [
        GetType = () => Value.Type(GetRows()),
        GetRows = () => GetEntity(url, entity)
    ]);

อัปเดต TripPinNavTable ฟังก์ชันเพื่อเรียกใช้ TripPin.SuperSimpleView แทนที่จะเป็น GetEntity :

withData = Table.AddColumn(rename, "Data", each TripPin.SuperSimpleView(url, [Name]), type table),

หากคุณเรียกใช้การทดสอบหน่วยใหม่ คุณจะเห็นว่าลักษณะการงานของฟังก์ชันของคุณไม่ได้เปลี่ยนแปลง ในกรณีนี้ การดําเนินการ Table.View ของคุณจะถูกส่งผ่านการเรียก GetEntity ไปยัง เนื่องจากยังไม่ได้ใช้ตัวจัดการการแปลงใด ๆ (ยัง) พารามิเตอร์ต้นฉบับยังคง url ไม่ส555

การเริ่มใช้งาน Table.View

การใช้งานข้างต้น Table.View นั้นง่าย แต่มีประโยชน์มาก การดําเนินการต่อไปนี้จะถูกใช้เป็นพื้นฐานของคุณ ซึ่งไม่ได้ใช้ฟังก์ชันการพับใด ๆ แต่มีการยการยการที่คุณจะต้อง — ดําเนินการ

TripPin.View = (baseUrl as text, entity as text) as table =>
    let
        // Implementation of Table.View handlers.
        //
        // We wrap the record with Diagnostics.WrapHandlers() to get some automatic
        // tracing if a handler returns an error.
        //
        View = (state as record) => Table.View(null, Diagnostics.WrapHandlers([
            // Returns the table type returned by GetRows()
            GetType = () => CalculateSchema(state),

            // Called last - retrieves the data from the calculated URL
            GetRows = () => 
                let
                    finalSchema = CalculateSchema(state),
                    finalUrl = CalculateUrl(state),

                    result = TripPin.Feed(finalUrl, finalSchema),
                    appliedType = Table.ChangeType(result, finalSchema)
                in
                    appliedType,

            //
            // Helper functions
            //
            // Retrieves the cached schema. If this is the first call
            // to CalculateSchema, the table type is calculated based on
            // the entity name that was passed into the function.
            CalculateSchema = (state) as type =>
                if (state[Schema]? = null) then
                    GetSchemaForEntity(entity)
                else
                    state[Schema],

            // Calculates the final URL based on the current state.
            CalculateUrl = (state) as text => 
                let
                    urlWithEntity = Uri.Combine(state[Url], state[Entity])
                in
                    urlWithEntity
        ]))
    in
        View([Url = baseUrl, Entity = entity]);

ถ้าคุณดูที่การเรียกไปยัง Table.View คุณจะเห็นฟังก์ชัน wrapper เพิ่มเติมรอบ handlersDiagnostics.WrapHandlers เรกคอร์ด ฟังก์ชันผู้ช่วยเหลือนี้พบในโมดูลการวินิจฉัย (ที่แนะนวลในบทช่วยสอนก่อนหน้านี้) และให้วิธีที่เป็นประโยชน์ในการติดตามข้อผิดพลาดที่เกิดขึ้นโดยตัวจัดการแต่ละรายการโดยอัตโนมัติ

มี GetType GetRows การอัปเดตฟังก์ชัน และ เพื่อใช้ฟังก์ชันผู้ช่วยเหลือใหม่สองตัว — CalculateSchema CaculateUrl และ ในตอนนี้ การใช้ฟังก์ชันเหล่านั้นจะค่อนข้างตรงไปตรงมา คุณจะ — สังเกตเห็นส่วนต่างๆ ของสิ่งที่เคยดําเนินการโดย GetEntity ฟังก์ชันก่อนหน้านี้

สุดท้าย คุณจะสังเกตเห็นว่าคุณเป็นคนนิยามฟังก์ชันภายใน ( View ) ที่ state ยอมรับพารามิเตอร์ เมื่อคุณใช้ตัวจัดการเพิ่มเติม พวกเขาจะเรียกใช้ฟังก์ชันภายในแบบเรียกใช้อีกครั้ง View ปรับปรุง และส่งผ่านตาม state ที่พวกเขาไป

อัปเดต TripPinNavTable ฟังก์ชันอีกครั้ง โดยแทนที่การเรียก ไปยัง TripPin.SuperSimpleView ด้วยการเรียกไปยังฟังก์ชัน TripPin.View ใหม่ และเรียกใช้การทดสอบหน่วยอีกครั้ง คุณจะไม่เห็นฟังก์ชันใหม่ใด ๆ ตอนนี้คุณมีพื้นฐานที่ทึบเพื่อการทดสอบ

การดําเนินการพับคิวรี

เนื่องจากกลไกจัดการ M จะถอยกลับไปยังการประมวลผลภายในเครื่องโดยอัตโนมัติเมื่อไม่สามารถพับคิวรีได้ คุณต้องปฏิบัติตามขั้นตอนเพิ่มเติมเพื่อ Table.View ตรวจสอบว่าตัวจัดการของคุณนั้นใช้งานได้ถูกต้องหรือไม่

วิธีการตรวจสอบลักษณะการพับด้วยตนเองคือการดู URL ร้องขอการทดสอบหน่วยของคุณโดยใช้เครื่องมือเช่น Fiddler อีกวิธีหนึ่งคือ บันทึกการวินิจฉัยที่คุณเพิ่มเข้าไป จะปล่อย URL แบบเต็มที่เรียกใช้ TripPin.Feed ซึ่ง ควร รวมพารามิเตอร์สตริงของคิวรี OData ที่ตัวจัดการของคุณเพิ่ม

วิธีอัตโนมัติในการตรวจสอบการพับคิวรีคือการบังคับให้การเรียกใช้การทดสอบหน่วยของคุณล้มเหลวหากคิวรีไม่พับเต็มรูปแบบ คุณสามารถสามารถสิ่งนี้ได้โดยการเปิดคุณสมบัติของโครงการ และการตั้งค่า ข้อผิดพลาด ในความล้มเหลวของการพับ****เป็น จริง เมื่อเปิดใช้งานการตั้งค่านี้ คิวรีใด ๆ ที่ต้องใช้การประมวลผลภายในเครื่องจะส่งผลให้เกิดข้อผิดพลาดต่อไปนี้:

เราไม่สามารถพับนิพจน์ไปยังแหล่งข้อมูลได้ โปรดลองนิพจน์ที่ง่ายขึ้น

คุณสามารถทดสอบได้โดยการเพิ่มไฟล์ใหม่ Fact ลงในไฟล์ทดสอบหน่วยของคุณที่มีการแปลงตารางอย่างน้อยหนึ่งรายการ

// Query folding tests
Fact("Fold $top 1 on Airlines", 
    #table( type table [AirlineCode = text, Name = text] , {{"AA", "American Airlines"}} ), 
    Table.FirstN(Airlines, 1)
)

หมายเหตุ

การตั้งค่า ข้อผิดพลาดในความล้มเหลวของการ พับเป็นวิธีการ "ทั้งหมดหรือไม่มีสิ่งใด" หากคุณต้องการทดสอบคิวรีที่ไม่ได้ออกแบบมาเพื่อพับเป็นส่วนหนึ่งของการทดสอบหน่วยของคุณ คุณจะต้องเพิ่มตรรกะเงื่อนไขบางอย่างเพื่อเปิดใช้งาน/ปิดใช้งานการทดสอบตามล้ออกแบบ

ส่วนที่เหลือของบทช่วยสอนนี้จะเพิ่มตัวจัดการ Table.View ใหม่ คุณจะใช้วิธีการทดสอบ การพัฒนาขับเคลื่อนด้วย (TDD) ซึ่งขั้นแรกคุณต้องเพิ่มการทดสอบหน่วยล้มเหลว จากนั้นจึงใช้รหัส M เพื่อแก้ไข

แต่ละส่วนตัวจัดการด้านล่างจะอธิบายฟังก์ชันการดําเนินการที่ให้ไว้โดยตัวจัดการ ไวยากรณ์คิวรีที่เทียบเท่า OData การทดสอบหน่วย และการใช้งาน การใช้โค้ดการยการยการที่อธิบายไว้ข้างต้น การใช้งานตัวจัดการแต่ละแห่งต้องมีการเปลี่ยนแปลงสองอย่าง:

  • การเพิ่มตัวจัดการลงใน Table.View ที่จะ state อัปเดตระเบียน
  • การปรับเปลี่ยน CalculateUrl เพื่อดึงค่าจาก state และเพิ่มไปยังพารามิเตอร์สตริงของ url และ/หรือคิวรี

จัดการ Table.FirstN กับ OnTake

ตัวจัดการ OnTake ได้รับพารามิเตอร์ count ซึ่งเป็นจํานวนแถวสูงสุดที่จะรับ ในเงื่อนไข OData คุณสามารถแปลเป็นภาษาพารามิเตอร์ $top คิวรีได้

คุณจะใช้การทดสอบหน่วยต่อไปนี้:

// Query folding tests
Fact("Fold $top 1 on Airlines", 
    #table( type table [AirlineCode = text, Name = text] , {{"AA", "American Airlines"}} ), 
    Table.FirstN(Airlines, 1)
),
Fact("Fold $top 0 on Airports", 
    #table( type table [Name = text, IataCode = text, Location = record] , {} ), 
    Table.FirstN(Airports, 0)
),

การทดสอบเหล่านี้ทั้งสอง Table.FirstN ใช้เพื่อกรองข้อมูลไปยังผลลัพธ์ที่ตั้งค่าเป็นจํานวน X แถวแรก ถ้าคุณเรียกใช้การทดสอบเหล่านี้ด้วยข้อผิดพลาดในความล้มเหลวของการพับที่ตั้งค่าเป็น (ค่าเริ่มต้น) การทดสอบควรประสบความสเร็จ แต่ถ้าคุณเรียกใช้ Fiddler (หรือตรวจสอบบันทึกการติดตาม) คุณจะเห็นว่าการร้องขอที่คุณส่งไม่มีพารามิเตอร์คิวรี False OData

การติดตามการวินิจฉัย

ถ้าคุณตั้งค่า ข้อผิดพลาดในความล้มเหลวในการ พับ True จะล้มเหลวด้วยนิพจน์ "โปรดลองใช้นิพจน์ที่ง่าย" ข้อผิดพลาด เมื่อต้องการแก้ไขปัญหานี้ คุณจะกําหนดตัวจัดการ Table.View แรกของคุณ OnTake ให้กับ

ตัวจัดการ OnTake มีลักษณะดังนี้:

OnTake = (count as number) =>
    let
        // Add a record with Top defined to our state
        newState = state & [ Top = count ]
    in
        @View(newState),

CalculateUrlฟังก์ชันได้รับการอัปเดตเพื่อแยก Top ค่าจาก state เรกคอร์ดและตั้งค่าพารามิเตอร์ที่ถูกต้องในสตริงแบบสอบถาม

// Calculates the final URL based on the current state.
CalculateUrl = (state) as text => 
    let
        urlWithEntity = Uri.Combine(state[Url], state[Entity]),

        // Uri.BuildQueryString requires that all field values
        // are text literals.
        defaultQueryString = [],

        // Check for Top defined in our state
        qsWithTop =
            if (state[Top]? <> null) then
                // add a $top field to the query string record
                defaultQueryString & [ #"$top" = Number.ToText(state[Top]) ]
            else
                defaultQueryString,

        encodedQueryString = Uri.BuildQueryString(qsWithTop),
        finalUrl = urlWithEntity & "?" & encodedQueryString
    in
        finalUrl

เรียกใช้การทดสอบหน่วยอีกครั้ง คุณจะเห็นว่า URL ที่คุณเข้าถึงในขณะนี้ประกอบด้วย $top พารามิเตอร์ (โปรดทราบว่าเนื่องจากการเข้ารหัส URL $top ปรากฏเป็น %24top แต่บริการ OData สมาร์ทพอที่จะแปลงโดยอัตโนมัติ)

การติดตามการวินิจฉัยที่มีด้านบน

การจัดการ Table.Skip ด้วย OnSkip

ตัวจัดการ OnSkip นั้นเหมือนกับ OnTake มาก ซึ่งได้รับ count พารามิเตอร์ซึ่งเป็นจํานวนแถวที่จะข้ามจากชุดผลลัพธ์ ซึ่งแปลได้ดีกับพารามิเตอร์คิว $skip OData

การทดสอบหน่วย:

// OnSkip
Fact("Fold $skip 14 on Airlines",
    #table( type table [AirlineCode = text, Name = text] , {{"EK", "Emirates"}} ), 
    Table.Skip(Airlines, 14)
),
Fact("Fold $skip 0 and $top 1",
    #table( type table [AirlineCode = text, Name = text] , {{"AA", "American Airlines"}} ),
    Table.FirstN(Table.Skip(Airlines, 0), 1)
),

การดําเนินการ:

// OnSkip - handles the Table.Skip transform.
// The count value should be >= 0.
OnSkip = (count as number) =>
    let
        newState = state & [ Skip = count ]
    in
        @View(newState),

จับคู่การอัปเดตเป็น CalculateUrl :

qsWithSkip = 
    if (state[Skip]? <> null) then
        qsWithTop & [ #"$skip" = Number.ToText(state[Skip]) ]
    else
        qsWithTop,

การจัดการ Table.SelectColumns กับ OnSelectColumns

ตัวจัดการ OnSelectColumns ถูกเรียกเมื่อผู้ใช้เลือกหรือลบคอลัมน์ออกจากชุดผลลัพธ์ ตัวจัดการได้รับค่า เป็น list text ซึ่งแสดงคอลัมน์ที่จะเลือก ในเงื่อนไข OData การดําเนินการนี้จะแมป ไปยัง$select คิวรี ประโยชน์ของการเลือกคอลัมน์การพับจะชัดเจนเมื่อคุณจัดการกับตารางที่มีหลายคอลัมน์ ตัว $select ประกอบจะลบคอลัมน์ที่ไม่ได้เลือกออกจากชุดผลลัพธ์ ซึ่งส่งผลให้คิวรีมีประสิทธิภาพมากขึ้น

การทดสอบหน่วย:

// OnSelectColumns
Fact("Fold $select single column", 
    #table( type table [AirlineCode = text] , {{"AA"}} ),
    Table.FirstN(Table.SelectColumns(Airlines, {"AirlineCode"}), 1)
),
Fact("Fold $select multiple column", 
    #table( type table [UserName = text, FirstName = text, LastName = text],{{"russellwhyte", "Russell", "Whyte"}}), 
    Table.FirstN(Table.SelectColumns(People, {"UserName", "FirstName", "LastName"}), 1)
),
Fact("Fold $select with ignore column", 
    #table( type table [AirlineCode = text] , {{"AA"}} ),
    Table.FirstN(Table.SelectColumns(Airlines, {"AirlineCode", "DoesNotExist"}, MissingField.Ignore), 1)
),

การทดสอบสองครั้งแรกเลือกตัวเลขที่แตกต่างกันของ Table.SelectColumns คอลัมน์ด้วย และรวม Table.FirstN การโทรเพื่อลดความซับซ้อนของกรณีการทดสอบ

หมายเหตุ

ถ้าการทดสอบเป็น เพียงแค่แสดงชื่อคอลัมน์ (โดยใช้ Table.ColumnNames ) และไม่ใช่ข้อมูลใด ๆ การร้องขอไปยังบริการ OData จะไม่ถูกส่งจริง เนื่องจากการเรียก จะส่งกลับ GetType schema ซึ่งประกอบด้วยข้อมูลทั้งหมดที่กลไกจัดการ M ต้องคํานวณผลลัพธ์

การทดสอบที่สามใช้ตัวเลือก MissingField.Ignore ซึ่งบอกกลไกจัดการ M ให้ละเว้นคอลัมน์ที่เลือกใดๆ ที่ไม่มีอยู่ในชุดผลลัพธ์ ตัวจัดการไม่เพียงแค่ต้องกังวลเกี่ยวกับตัวเลือกนี้ แต่กลไกจัดการ M จะจัดการโดยอัตโนมัติ (นั่นคือ คอลัมน์ OnSelectColumns — ที่ขาดหายไปจะไม่รวมอยู่ใน columns รายการ)

หมายเหตุ

ตัวเลือกอื่นๆ Table.SelectColumns ของ MissingField.UseNull , ต้องใช้ตัวเชื่อมต่อ OnAddColumn เพื่อใช้ตัวจัดการ ซึ่งการขั้นตอนนี้จะเสร็จสิ้นในบทเรียนถัดไป

การดําเนินการ OnSelectColumns เพื่อดําเนินการสองสิ่งต่อไปนี้:

  • เพิ่มรายการของคอลัมน์ที่เลือก state ไปยัง
  • คํานวณค่า Schema ใหม่เพื่อให้คุณสามารถตั้งค่าชนิดตารางที่ถูกต้องได้
OnSelectColumns = (columns as list) =>
    let
        // get the current schema
        currentSchema = CalculateSchema(state),
        // get the columns from the current schema (which is an M Type value)
        rowRecordType = Type.RecordFields(Type.TableRow(currentSchema)),
        existingColumns = Record.FieldNames(rowRecordType),
        // calculate the new schema
        columnsToRemove = List.Difference(existingColumns, columns),
        updatedColumns = Record.RemoveFields(rowRecordType, columnsToRemove),
        newSchema = type table (Type.ForRecord(updatedColumns, false))
    in
        @View(state & 
            [ 
                SelectColumns = columns,
                Schema = newSchema
            ]
        ),

CalculateUrl ได้รับการอัปเดตเพื่อดึงรายการคอลัมน์จากสถานะ และรวม (ด้วยตัวคั่น) $select ของพารามิเตอร์

// Check for explicitly selected columns
qsWithSelect =
    if (state[SelectColumns]? <> null) then
        qsWithSkip & [ #"$select" = Text.Combine(state[SelectColumns], ",") ]
    else
        qsWithSkip,

การจัดการ Table.Sort ด้วย OnSort

ตัวจัดการ OnSort ได้รับ list record ค่า แต่ละระเบียน Name ประกอบด้วยเขตข้อมูล ที่ระบุชื่อของคอลัมน์ และ Order เขตข้อมูลที่เท่ากับ Order.Ascending Order.Descending หรือ ในเงื่อนไข OData การดําเนินการนี้จะแมปไปยัง $orderby คิวรีของคุณ ไวยากรณ์ $orderby มีชื่อคอลัมน์ตามด้วย หรือ เพื่อระบุ asc ล. desc ก.ม. จากน้อยไปหามาก หรือมากไปหาน้อย เมื่อเรียงลกรกฎาคมตามคอลัมน์หลายคอลัมน์ ค่าจะถูกคั่นด้วยเครื่องหมายจุลภาค โปรดทราบว่าถ้า columns พารามิเตอร์ประกอบด้วยรายการมากกว่าหนึ่งรายการ สิ่งสําคัญคือต้องรักษาลวบที่พารามิเตอร์ปรากฏขึ้น

การทดสอบหน่วย:

// OnSort
Fact("Fold $orderby single column",
    #table( type table [AirlineCode = text, Name = text], {{"TK", "Turkish Airlines"}}),
    Table.FirstN(Table.Sort(Airlines, {{"AirlineCode", Order.Descending}}), 1)
),
Fact("Fold $orderby multiple column",
    #table( type table [UserName = text], {{"javieralfred"}}),
    Table.SelectColumns(Table.FirstN(Table.Sort(People, {{"LastName", Order.Ascending}, {"UserName", Order.Descending}}), 1), {"UserName"})
)

การดําเนินการ:

// OnSort - receives a list of records containing two fields: 
//    [Name]  - the name of the column to sort on
//    [Order] - equal to Order.Ascending or Order.Descending
// If there are multiple records, the sort order must be maintained.
//
// OData allows you to sort on columns that do not appear in the result
// set, so we do not have to validate that the sorted columns are in our 
// existing schema.
OnSort = (order as list) =>
    let
        // This will convert the list of records to a list of text,
        // where each entry is "<columnName> <asc|desc>"
        sorting = List.Transform(order, (o) => 
            let
                column = o[Name],
                order = o[Order],
                orderText = if (order = Order.Ascending) then "asc" else "desc"
            in
                column & " " & orderText
        ),
        orderBy = Text.Combine(sorting, ", ")
    in
        @View(state & [ OrderBy = orderBy ]),

อัปเดตเป็น CalculateUrl :

qsWithOrderBy = 
    if (state[OrderBy]? <> null) then
        qsWithSelect & [ #"$orderby" = state[OrderBy] ]
    else
        qsWithSelect,

การจัดการ Table.RowCount ด้วย GetRowCount

ซึ่งแตกต่างจากตัวจัดการคิวรีอื่นๆ ที่คุณได้ใช้ ตัวจัดการ GetRowCount จะส่งกลับค่า — เดียวจํานวนแถวที่คาดไว้ในชุดผลลัพธ์ ในคิวรี M โดยทั่วไปจะเป็นผลลัพธ์ของ Table.RowCount การแปลง คุณมีตัวเลือกที่แตกต่างกันสองสามตัวเกี่ยวกับวิธีการจัดการสิ่งนี้เป็นส่วนหนึ่งของคิวรี OData

  • พารามิเตอร์ $countคิวรี ซึ่งแสดงค่าการนับเป็นเขตข้อมูลแยกต่างหากในชุดผลลัพธ์
  • เซ ก$countเส้นทาง /$countซึ่ง จะแสดงเฉพาะ จํานวนรวมเป็นค่าสเกลา

ข้อเสียของวิธีการพารามิเตอร์คิวรีคือคุณยังต้องส่งคิวรีทั้งหมดไปยังบริการ OData เนื่องจากการนับกลับมาแบบอินไลน์เป็นส่วนหนึ่งของชุดผลลัพธ์ คุณจะต้องประมวลผลหน้าแรกของข้อมูลจากชุดผลลัพธ์ แม้ว่าชุดผลลัพธ์ทั้งหมดจะยังมีประสิทธิภาพมากกว่าแต่การนับชุดผลลัพธ์และนับแถว แต่ก็อาจยังต้องการใช้งานมากกว่าที่คุณต้องการ

ประโยชน์ของวิธีการเซกเมนต์เส้นทางคือคุณจะได้รับค่าสเกลาเดียวในผลลัพธ์เท่านั้น ซึ่งนี่ช่วยให้การดําเนินการทั้งหมดมีประสิทธิภาพมากขึ้น อย่างไรก็ตาม ตามที่อธิบายไว้ในข้อมูลทางเทคนิคของ OData เซ็กเมนต์$count /$count จะส่งคืนข้อผิดพลาดถ้าคุณใส่พารามิเตอร์คิวรีอื่นๆ เช่น หรือ ซึ่ง $top $skip จํากัดประโยชน์

ในบทช่วยสอนนี้ คุณจะใช้ GetRowCount ตัวจัดการโดยใช้วิธีเซกเมนต์เส้นทาง เพื่อหลีกเลี่ยงข้อผิดพลาดที่คุณจะได้รับหากมีพารามิเตอร์คิวรีอื่น ๆ รวมอยู่ คุณจะต้องตรวจสอบค่าสถานะอื่น ๆ และส่งกลับ "ข้อผิดพลาดทั่วไป" ( ... ) ถ้าคุณพบ การส่งคืนข้อผิดพลาดใดๆ จากตัวจัดการบอกกลไกจัดการ M ว่าไม่สามารถพับการดําเนินการได้ และควรแสดงแทนตัวจัดการเริ่มต้นแทน (ซึ่งในกรณีนี้จะนับ Table.View จํานวนแถวทั้งหมด)

ก่อนอื่น เพิ่มการทดสอบหน่วยอย่างง่าย:

// GetRowCount
Fact("Fold $count", 15, Table.RowCount(Airlines)),

เนื่องจากเซกเมนต์เส้นทางส่งกลับค่าเดียว (ในรูปแบบธรรมดา/ข้อความ) แทนชุดผลลัพธ์ JSON คุณจะต้องเพิ่มฟังก์ชัน /$count ภายในใหม่ ( ) เพื่อดําเนินการร้องขอ TripPin.Scalar และจัดการผลลัพธ์

// Similar to TripPin.Feed, but is expecting back a scalar value.
// This function returns the value from the service as plain text.
TripPin.Scalar = (url as text) as text =>
    let
        _url = Diagnostics.LogValue("TripPin.Scalar url", url),

        headers = DefaultRequestHeaders & [
            #"Accept" = "text/plain"
        ],

        response = Web.Contents(_url, [ Headers = headers ]),
        toText = Text.FromBinary(response)
    in
        toText;

การดําเนินการจะใช้ฟังก์ชันนี้ (ถ้าไม่พบพารามิเตอร์คิวรีอื่น ๆ ใน state ):

GetRowCount = () as number =>
    if (Record.FieldCount(Record.RemoveFields(state, {"Url", "Entity", "Schema"}, MissingField.Ignore)) > 0) then
        ...
    else
        let
            newState = state & [ RowCountOnly = true ],
            finalUrl = CalculateUrl(newState),
            value = TripPin.Scalar(finalUrl),
            converted = Number.FromText(value)
        in
            converted,

CalculateUrlฟังก์ชันได้รับการอัปเดตเพื่อ /$count ผนวกเข้ากับ URL RowCountOnly ถ้าเขตข้อมูลถูกตั้งค่า state ใน

// Check for $count. If all we want is a row count,
// then we add /$count to the path value (following the entity name).
urlWithRowCount =
    if (state[RowCountOnly]? = true) then
        urlWithEntity & "/$count"
    else
        urlWithEntity,

การทดสอบ Table.RowCount หน่วยใหม่ควรผ่านแล้วในขณะนี้

เมื่อต้องการทดสอบกรณีที่ใช้แสดงก่อน คุณจะต้องเพิ่มการทดสอบอื่นที่บังคับให้เกิดข้อผิดพลาด ก่อนอื่นให้เพิ่มวิธีการช่วยเหลือที่ตรวจสอบผลลัพธ์ของการ try ดําเนินการข้อผิดพลาดการพับ

// Returns true if there is a folding error, or the original record (for logging purposes) if not.
Test.IsFoldingError = (tryResult as record) =>
    if ( tryResult[HasError]? = true and tryResult[Error][Message] = "We couldn't fold the expression to the data source. Please try a simpler expression.") then
        true
    else
        tryResult;

จากนั้นเพิ่มการทดสอบที่ใช้ ทั้ง Table.RowCount Table.FirstN และ เพื่อบังคับใช้ข้อผิดพลาด

// test will fail if "Fail on Folding Error" is set to false
Fact("Fold $count + $top *error*", true, Test.IsFoldingError(try Table.RowCount(Table.FirstN(Airlines, 3)))),

หมายเหตุสําคัญที่นี่คือการทดสอบนี้จะแสดงข้อผิดพลาดถ้า ข้อผิดพลาดเกี่ยวกับข้อผิดพลาดในพับ ถูกตั้งค่าเป็น เนื่องจากการดําเนินการ false จะถอยกลับไปยัง Table.RowCount ตัวจัดการภายในเครื่อง (ค่าเริ่มต้น) การเรียกใช้การทดสอบด้วย ข้อผิดพลาดบนชุดข้อผิดพลาดการ พับ true จะ Table.RowCount ทําให้ล้มเหลวและช่วยให้การทดสอบประสบความสเร็จ

บทสรุป

การใช้ Table.View กับตัวเชื่อมต่อของคุณเพิ่มความซับซ้อนให้กับโค้ดของคุณจํานวนมาก เนื่องจากกลไกจัดการ M สามารถประมวลผลการแปลงทั้งหมดภายในเครื่อง การเพิ่มตัวจัดการไม่เปิดใช้งานสถานการณ์ใหม่ๆ ของผู้ใช้แต่จะส่งผลให้การประมวลผลมีประสิทธิภาพมากขึ้น Table.View (และอาจเป็นผู้ใช้ที่มีประสิทธิภาพมากขึ้น) หนึ่งในข้อดีหลักของตัวจัดการที่เลือกได้คือให้คุณสามารถเพิ่มฟังก์ชันการฟังก์ชันใหม่แบบเพิ่ม Table.View หน่วยโดยไม่ส่งผลกระทบต่อความเข้ากันได้ย้อนหลังของตัวเชื่อมต่อของคุณ

ใช้ตัวเชื่อมต่อส่วนใหญ่ ตัวจัดการที่สําคัญ (และพื้นฐาน) ที่จะใช้คือ OnTake (ซึ่งแปลเป็น $top ใน OData) เนื่องจากจะจํากัดจํานวนแถวที่ส่งกลับ ประสบการณ์Power Queryนี้มักจะปฏิบัติการกับแถวต่าง ๆ เมื่อแสดงตัวอย่างในบานหน้าต่างตัวนําทางและตัวแก้ไขคิวรี ดังนั้นผู้ใช้ของคุณอาจเห็นการปรับปรุงประสิทธิภาพการใช้งานได้อย่างมีนัยสําคัญ OnTake 1000 เมื่อใช้งานชุดข้อมูลขนาดใหญ่ขึ้น