पेजिंग को लागू करने का कुशल तरीका


118

क्या मुझे पेजिंग के लिए LINQ's Skip()और Take()विधि का उपयोग करना चाहिए , या SQL क्वेरी के साथ अपने पेजिंग को लागू करना चाहिए ?

सबसे कुशल कौन सा है? मैं एक को दूसरे पर क्यों चुनूंगा?

मैं SQL Server 2008, ASP.NET MVC और LINQ का उपयोग कर रहा हूं।


मुझे लगता है कि यह निर्भर करता है। आप किस ऐप पर काम कर रहे हैं? किस तरह का लोड होगा?
बड्डीजियो

इस उत्तर पर भी नज़र डालें: stackoverflow.com/a/10639172/416996
13zbek

जवाबों:


175

यदि आप skip(n).take(m)linq पर विधियों (SQL 2005/2008 डेटाबेस सर्वर के रूप में) के साथ अपने प्रश्न का संक्षिप्त उत्तर देने की कोशिश कर रहे हैं, तो आपकी क्वेरी Select ROW_NUMBER() Over ...स्टेटमेंट का उपयोग कर रही होगी , किसी तरह SQL इंजन में प्रत्यक्ष पेजिंग है।

आपको एक उदाहरण देते हुए, मेरे पास एक db तालिका है जिसे mtcityमैंने लिखा है और मैंने निम्नलिखित प्रश्न (काम के साथ-साथ संस्थाओं को linq के साथ) लिखा है:

using (DataClasses1DataContext c = new DataClasses1DataContext())
{
    var query = (from MtCity2 c1 in c.MtCity2s
                select c1).Skip(3).Take(3);
    //Doing something with the query.
}

परिणामी क्वेरी होगी:

SELECT [t1].[CodCity], 
    [t1].[CodCountry], 
    [t1].[CodRegion], 
    [t1].[Name],  
    [t1].[Code]
FROM (
    SELECT ROW_NUMBER() OVER (
        ORDER BY [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]) AS [ROW_NUMBER], 
        [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]
    FROM [dbo].[MtCity] AS [t0]
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER]

जो कि एक विंडो डेटा एक्सेस है (बहुत शांत, btw cuz बहुत भीख माँगने के बाद से डेटा लौटाएगा और जब तक शर्तें पूरी होंगी तब तक टेबल तक पहुंच जाएगा)। यह बहुत समान होगा:

With CityEntities As 
(
    Select ROW_NUMBER() Over (Order By CodCity) As Row,
        CodCity //here is only accessed by the Index as CodCity is the primary
    From dbo.mtcity
)
Select [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]
From CityEntities c
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity
Where c.Row Between @p0 + 1 AND @p0 + @p1
Order By c.Row Asc

अपवाद के साथ, इस दूसरी क्वेरी को linq परिणाम की तुलना में तेजी से निष्पादित किया जाएगा क्योंकि यह डेटा एक्सेस विंडो बनाने के लिए विशेष रूप से इंडेक्स का उपयोग करेगा; इसका मतलब है, अगर आपको कुछ फ़िल्टरिंग की आवश्यकता है, तो Entity लिस्टिंग (जहां पंक्ति बनाई गई है) में फ़िल्टरिंग (या होनी चाहिए) और अच्छे प्रदर्शन को बनाए रखने के लिए कुछ अनुक्रमित भी बनाए जाने चाहिए।

अब, क्या बेहतर है?

यदि आपके पास अपने तर्क में बहुत अधिक ठोस वर्कफ़्लो हैं, तो उचित SQL तरीका लागू करना जटिल होगा। उस स्थिति में LINQ समाधान होगा।

यदि आप तर्क के उस हिस्से को सीधे SQL (एक संग्रहीत प्रक्रिया में) में कम कर सकते हैं, तो यह और भी बेहतर होगा क्योंकि आप मेरे द्वारा दिखाए गए दूसरी क्वेरी को लागू कर सकते हैं (अनुक्रमित का उपयोग करके) और SQL को निष्पादन योजना को बनाने और संग्रहीत करने की अनुमति देते हैं। क्वेरी (प्रदर्शन में सुधार)।


2
अच्छा उत्तर - सामान्य तालिका अभिव्यक्ति पेजिंग करने का एक अच्छा तरीका है।
जारोड डिक्सन

क्या आप मेरे प्रश्न ( stackoverflow.com/questions/11100929/… ) की जांच कर सकते हैं ? मैंने एक SP बनाया, जिसे मैंने अपने EDMX में जोड़ा और इसे linq-to-इकाइयों क्वेरी में उपयोग किया।
मिस्सी

2
+1, अच्छा जवाब, मैं आपको दूसरे उदाहरण के प्रदर्शन लाभों की व्याख्या करता हूं
कोहेन

@ जोहान: एक विकल्प है जिसे सीक विधि कहा जाता है जो बड़े पृष्ठ नंबरों के लिए भारी आउटपरफॉर्म करता है।
लुकास एडर

50

प्रयोग करके देखें

FROM [TableX]
ORDER BY [FieldX]
OFFSET 500 ROWS
FETCH NEXT 100 ROWS ONLY

SQL सर्वर में 501 से 600 तक पंक्तियों को प्राप्त करने के लिए, उन्हें मेमोरी में लोड किए बिना। ध्यान दें कि यह वाक्य रचना के साथ उपलब्ध हो गया है SQL सर्वर 2012 केवल


मुझे लगता है कि यह गलत है। एसक्यूएल प्रदर्शित 502-601 से पंक्तियों को दिखाता है (जब तक कि आप शून्य-अनुक्रमण नहीं कर रहे हैं?)
Smudge202

नहीं, यह 501 से 600 तक पंक्तियाँ नहीं पाता है
Volkan Sen

12

जबकि LINQ-to-SQL एक OFFSETक्लॉज उत्पन्न करेगा (संभवतः ROW_NUMBER() OVER() जैसा कि दूसरों ने उल्लेख किया है ) का उपयोग करते हुए , SQL में पेजिंग करने के लिए एक पूरी तरह से अलग, बहुत तेज़ तरीका है। इसे अक्सर इस ब्लॉग पोस्ट में वर्णित "तलाश विधि" कहा जाता है

SELECT TOP 10 first_name, last_name, score
FROM players
WHERE (score < @previousScore)
   OR (score = @previousScore AND player_id < @previousPlayerId)
ORDER BY score DESC, player_id DESC

@previousScoreऔर @previousPlayerIdमूल्यों पिछले पृष्ठ से पिछले रिकॉर्ड के संबंधित मूल्यों कर रहे हैं। यह आपको "अगला" पृष्ठ लाने की अनुमति देता है। यदि ORDER BYदिशा है ASC, तो >इसके बजाय बस का उपयोग करें ।

उपरोक्त विधि के साथ, आप पहले पिछले 40 रिकॉर्ड प्राप्त किए बिना तुरंत पृष्ठ 4 पर नहीं जा सकते। लेकिन अक्सर, आप वैसे भी कूदना नहीं चाहते हैं। इसके बजाय, आपको एक बहुत तेज़ क्वेरी मिलती है जो आपके अनुक्रमण के आधार पर निरंतर समय में डेटा लाने में सक्षम हो सकती है। साथ ही, आपके पृष्ठ "स्थिर" बने रहते हैं, इससे कोई फर्क नहीं पड़ता कि अंतर्निहित डेटा बदलता है (उदाहरण पृष्ठ 1 पर, जबकि आप पृष्ठ 4 पर हैं)।

उदाहरण के लिए, वेब अनुप्रयोगों में अधिक डेटा लोड करने में आलसी होने पर पेजिंग को लागू करने का यह सबसे अच्छा तरीका है।

ध्यान दें, "सीक विधि" को कीसेट पेजिंग भी कहा जाता है ।


5

LinqToSql स्वचालित रूप से एक .Skip (N1) .Take (N2) को आपके लिए TSQL सिंटैक्स में बदल देगा। वास्तव में, हर एक "क्वेरी" जो आप Linq में करते हैं, वास्तव में सिर्फ पृष्ठभूमि में आपके लिए एक SQL क्वेरी बना रहा है। इसका परीक्षण करने के लिए, जब आपका एप्लिकेशन चल रहा हो, तो बस SQL ​​Profiler चलाएँ।

स्किप / टेक मेथडोलॉजी ने मेरे लिए बहुत अच्छा काम किया है, और अन्य जो मैंने पढ़ा है।

जिज्ञासा से बाहर, आपके पास किस प्रकार की स्व-पेजिंग क्वेरी है, जो आपको लगता है कि लिनक के स्किप / ले से अधिक कुशल है?


4

हम एक संग्रहीत प्रक्रिया के भीतर डायनेमिक SQL में लिपटे CTE (क्योंकि हमारे एप्लिकेशन को डायनेमिक सर्वर साइड के डायनेमिक सॉर्टिंग की आवश्यकता है) का उपयोग करते हैं। यदि आप चाहें तो मैं एक मूल उदाहरण प्रदान कर सकता हूं।

LINQ द्वारा निर्मित T / SQL को देखने का मुझे मौका नहीं मिला है। क्या कोई नमूना पोस्ट कर सकता है?

हम LINQ या तालिकाओं तक सीधी पहुंच का उपयोग नहीं करते हैं क्योंकि हमें सुरक्षा की अतिरिक्त परत की आवश्यकता होती है (गतिशील SQL इसे कुछ हद तक तोड़ दिया जाता है)।

कुछ इस तरह से करना चाहिए ट्रिक आप मापदंडों के लिए पैरामीटर किए गए मानों में जोड़ सकते हैं, आदि।

exec sp_executesql 'WITH MyCTE AS (
    SELECT TOP (10) ROW_NUMBER () OVER ' + @SortingColumn + ' as RowID, Col1, Col2
    FROM MyTable
    WHERE Col4 = ''Something''
)
SELECT *
FROM MyCTE
WHERE RowID BETWEEN 10 and 20'

2
@ मर्डनी - आपके द्वारा दिए गए उदाहरण के लिए एक संकेत : sp_executesqlआपके पास सुरक्षित तरीके से पैरामीटर पास करने की संभावना है, जैसे EXECUTE sp_executesql 'WITH myCTE AS ... WHERE Col4=@p1) ...', '@p1 nvarchar(max)', @ValueForCol4:। इस संदर्भ में सुरक्षित होने का मतलब है कि यह SQL इंजेक्शन के खिलाफ मजबूत है - आप चर के अंदर हर संभव मूल्य पास कर सकते हैं @ValueForCol4- यहां तक ​​कि '--', और क्वेरी अभी भी काम करेगी!
मैट

1
@ मर्डेनी हाय, इस प्रश्न का उपयोग करने के बजाय हम कुछ इस तरह का उपयोग करते हैं: SELECT ROW_NUMBER() OVER (ORDER BY CASE WHEN @CampoId = 1 THEN Id WHEN @CampoId = 2 THEN field2 END)
एज़ेक्विएल

यह कुछ भयानक SQL निष्पादन योजनाओं का उत्पादन कर सकता है।
मर्डनी

@ मर्डनी: बड़े पृष्ठ संख्याओं के लिए, साधक विधिROW_NUMBER() OVER() ऑफसेट एमुलेशन की तुलना में बहुत तेज हो सकती है । इसे भी देखें: 4guysfromrolla.com/webtech/042606-1.shtml
लुकास ईडर

2

SQL Server 2008 में:

DECLARE @PAGE INTEGER = 2
DECLARE @TAKE INTEGER = 50

SELECT [t1].*
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[COLUMNORDER] DESC) AS [ROW_NUMBER], [t0].*
    FROM [dbo].[TABLA] AS [t0]
    WHERE ([t0].[COLUMNS_CONDITIONS] = 1)
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN ((@PAGE*@TAKE) - (@TAKE-1)) AND (@PAGE*@TAKE)
ORDER BY [t1].[ROW_NUMBER]

T0 में सभी रिकॉर्ड्स हैं। t1 में केवल वे ही उस पृष्ठ के अनुरूप हैं


2

मैं जो दृष्टिकोण दे रहा हूं वह सबसे तेज़ पृष्ठांकन है जो SQL सर्वर प्राप्त कर सकता है। मैंने 5 मिलियन रिकॉर्ड पर यह परीक्षण किया है। यह दृष्टिकोण SQL सर्वर द्वारा उपलब्ध कराए गए "OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY" से कहीं बेहतर है।

-- The below given code computes the page numbers and the max row of previous page
-- Replace <<>> with the correct table data.
-- Eg. <<IdentityColumn of Table>> can be EmployeeId and <<Table>> will be dbo.Employees

DECLARE @PageNumber int=1; --1st/2nd/nth page. In stored proc take this as input param.
DECLARE @NoOfRecordsPerPage int=1000;

 DECLARE @PageDetails TABLE
       (
        <<IdentityColumn of Table>> int,
        rownum int,
        [PageNumber] int
       )           
       INSERT INTO @PageDetails values(0, 0, 0)
       ;WITH CTE AS
       (
       SELECT <<IdentityColumn of Table>>, ROW_NUMBER() OVER(ORDER BY <<IdentityColumn of Table>>) rownum FROM <<Table>>
       )
       Insert into @PageDetails 
       SELECT <<IdentityColumn of Table>>, CTE.rownum, ROW_NUMBER() OVER (ORDER BY rownum) as [PageNumber] FROM CTE WHERE CTE.rownum%@NoOfRecordsPerPage=0


--SELECT * FROM @PageDetails 

-- Actual pagination
SELECT TOP (@NoOfRecordsPerPage)
FROM <<Table>> AS <<Table>>
WHERE <<IdentityColumn of Table>> > (SELECT <<IdentityColumn of Table>> FROM 
@PageDetails WHERE PageNumber=@PageNumber)
ORDER BY <<Identity Column of Table>>

0

आप इस प्रदर्शन को और बेहतर बना सकते हैं

From CityEntities c
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity
Where c.Row Between @p0 + 1 AND @p0 + @p1
Order By c.Row Asc

यदि आप इस तरह से इसका उपयोग करेंगे तो यह बेहतर परिणाम देगा:

From   dbo.MtCity  t0
   Inner Join  CityEntities c on c.CodCity = t0.CodCity

कारण: क्योंकि आप CityEntities टेबल पर क्लास का उपयोग कर रहे हैं जो MtCity में शामिल होने से पहले कई रिकॉर्ड को खत्म कर देगा, इसलिए 100% सुनिश्चित है कि यह प्रदर्शन को कई गुना बढ़ा देगा ...

वैसे भी रॉडरिगोल्प द्वारा उत्तर वास्तव में सहायक है।

धन्यवाद


मुझे संदेह है कि इस सलाह का उपयोग करने से कोई प्रदर्शन प्रभाव पड़ेगा। इसके लिए एक संदर्भ नहीं मिल सकता है, लेकिन क्वेरी में आंतरिक शामिल होने का क्रम वास्तविक जुड़ने के आदेश से भिन्न हो सकता है। उत्तरार्द्ध को तालिका के आँकड़ों और ऑपरेशन लागत अनुमानों का उपयोग करके क्वेरी ऑप्टिमाइज़र द्वारा तय किया जाता है।
इमरे पुवेल

@ आईएमआरपी: यह वास्तव में कुछ हद तक सीक विधि के अनुरूप हो सकता है , जिसका मैंने वर्णन किया है । हालाँकि, मुझे यकीन नहीं है कि @p0और कहाँ @p1से विशेष रूप से आते हैं
लुकास ईडर

0

पेजइंडेक्स पास करके आप इस सरल तरीके से पेजिंग को लागू कर सकते हैं

Declare @PageIndex INT = 1
Declare  @PageSize INT = 20

Select ROW_NUMBER() OVER ( ORDER BY Products.Name ASC )  AS RowNumber,
    Products.ID,
    Products.Name
into #Result 
From Products

SELECT @RecordCount = COUNT(*) FROM #Results 

SELECT * 
FROM #Results
WHERE RowNumber
BETWEEN
    (@PageIndex -1) * @PageSize + 1 
    AND
    (((@PageIndex -1) * @PageSize + 1) + @PageSize) - 1

0

2008 में हम छोड़ें () का उपयोग नहीं कर सकते। ()

तरीका यह है:

var MinPageRank = (PageNumber - 1) * NumInPage + 1
var MaxPageRank = PageNumber * NumInPage

var visit = Visita.FromSql($"SELECT * FROM (SELECT [RANK] = ROW_NUMBER() OVER (ORDER BY Hora DESC),* FROM Visita WHERE ) A WHERE A.[RANK] BETWEEN {MinPageRank} AND {MaxPageRank}").ToList();
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.