जीत-हार-टाई डेटा से स्ट्रीक काउंट और स्ट्रीक टाइप प्राप्त करें


15

मैंने इस सवाल के लिए एक SQL फिडेल बनाया, अगर वह किसी के लिए भी आसान बनाता है।

मेरे पास एक प्रकार का फंतासी स्पोर्ट्स डेटाबेस है और जो मैं जानने की कोशिश कर रहा हूं कि "वर्तमान लकीर" डेटा के साथ कैसे आना है (जैसे 'W2' अगर टीम ने अपने पिछले 2 मैचअप जीते हैं, या 'L1' अगर वे हार गए हैं पिछले मैचअप को जीतने के बाद उनका आखिरी मैचअप - या 'T1' अगर उन्होंने अपने सबसे हालिया मैचअप को बांधा है)।

यहाँ मेरा मूल स्कीमा है:

CREATE TABLE FantasyTeams (
  team_id BIGINT NOT NULL
)

CREATE TABLE FantasyMatches(
    match_id BIGINT NOT NULL,
    home_fantasy_team_id BIGINT NOT NULL,
    away_fantasy_team_id BIGINT NOT NULL,
    fantasy_season_id BIGINT NOT NULL,
    fantasy_league_id BIGINT NOT NULL,
    fantasy_week_id BIGINT NOT NULL,
    winning_team_id BIGINT NULL
)

कॉलम NULLमें एक मान winning_team_idउस मैच के लिए एक टाई दर्शाता है।

यहां 6 टीमों के लिए कुछ नमूना डेटा और 3 सप्ताह के मैचअप के साथ एक नमूना डीएमएल कथन है:

INSERT INTO FantasyTeams
SELECT 1
UNION
SELECT 2
UNION
SELECT 3
UNION
SELECT 4
UNION
SELECT 5
UNION
SELECT 6

INSERT INTO FantasyMatches
SELECT 1, 2, 1, 2, 4, 44, 2
UNION
SELECT 2, 5, 4, 2, 4, 44, 5
UNION
SELECT 3, 6, 3, 2, 4, 44, 3
UNION
SELECT 4, 2, 4, 2, 4, 45, 2
UNION
SELECT 5, 3, 1, 2, 4, 45, 3
UNION
SELECT 6, 6, 5, 2, 4, 45, 6
UNION
SELECT 7, 2, 6, 2, 4, 46, 2
UNION
SELECT 8, 3, 5, 2, 4, 46, 3
UNION
SELECT 9, 4, 1, 2, 4, 46, NULL

GO

यहाँ वांछित आउटपुट (ऊपर डीएमएल पर आधारित) का एक उदाहरण है कि मुझे यह पता लगाने में परेशानी हो रही है कि मुझे कैसे प्राप्त करना है:

| TEAM_ID | STEAK_TYPE | STREAK_COUNT |
|---------|------------|--------------|
|       1 |          T |            1 |
|       2 |          W |            3 |
|       3 |          W |            3 |
|       4 |          T |            1 |
|       5 |          L |            2 |
|       6 |          L |            1 |

मैंने उप-वर्ग और सीटीई का उपयोग करके विभिन्न तरीकों की कोशिश की है, लेकिन मैं इसे एक साथ नहीं रख सकता। मैं एक कर्सर का उपयोग करने से बचना चाहूंगा क्योंकि भविष्य में इसके खिलाफ चलने के लिए मेरे पास एक बड़ा डेटासेट हो सकता है। मुझे ऐसा लग रहा है कि टेबल वैरिएबल को शामिल करने का एक तरीका हो सकता है जो इस डेटा को किसी भी तरह से अपने आप में शामिल कर ले, लेकिन मैं अभी भी इस पर काम कर रहा हूं।

अतिरिक्त जानकारी: टीमों की संख्या अलग-अलग हो सकती है (6 और 10 के बीच की कोई भी संख्या) और हर हफ्ते प्रत्येक टीम के लिए कुल मैचअप में 1 की वृद्धि होगी। यह कैसे करना चाहिए पर कोई विचार?


2
संयोग से, इस तरह के सभी स्कीमा मैंने कभी भी मैच परिणाम के लिए एक ट्रिस्टेट (उदाहरण के लिए 1 2 3 का अर्थ होम विन / टाई / अवे विन) कॉलम का उपयोग करते हुए देखा है, न कि वैल्यू आईडी / NULL / आईडी के साथ आपके win_team_id के बजाय। डीबी की जांच के लिए एक कम बाधा।
आकाशवाणी

तो क्या आप कह रहे हैं कि डिजाइन I सेटअप "अच्छा" है?
jamauss

1
ठीक है, अगर मुझे टिप्पणियों के लिए कहा जाता है तो मैं कहूंगा: 1) इतने सारे नामों में 'फंतासी' 2) bigintइतने सारे स्तंभों के लिए क्यों जहां intशायद 3 करेंगे) क्यों सभी _एस ?! 4) मैं तालिका के नाम को एकवचन के रूप में पसंद करता हूं, लेकिन यह स्वीकार नहीं करता कि हर कोई मेरे साथ सहमत नहीं है // लेकिन जो आपने हमें यहां दिखाया है वह सुसंगत दिखता है, हां
आकाशवाणी

जवाबों:


17

चूँकि आप SQL Server 2012 पर हैं, आप नए विंडोिंग फ़ंक्शंस के एक जोड़े का उपयोग कर सकते हैं।

with C1 as
(
  select T.team_id,
         case
           when M.winning_team_id is null then 'T'
           when M.winning_team_id = T.team_id then 'W'
           else 'L'
         end as streak_type,
         M.match_id
  from FantasyMatches as M
    cross apply (values(M.home_fantasy_team_id),
                       (M.away_fantasy_team_id)) as T(team_id)
), C2 as
(
  select C1.team_id,
         C1.streak_type,
         C1.match_id,
         lag(C1.streak_type, 1, C1.streak_type) 
           over(partition by C1.team_id 
                order by C1.match_id desc) as lag_streak_type
  from C1
), C3 as
(
  select C2.team_id,
         C2.streak_type,
         sum(case when C2.lag_streak_type = C2.streak_type then 0 else 1 end) 
           over(partition by C2.team_id 
                order by C2.match_id desc rows unbounded preceding) as streak_sum
  from C2
)
select C3.team_id,
       C3.streak_type,
       count(*) as streak_count
from C3
where C3.streak_sum = 0
group by C3.team_id,
         C3.streak_type
order by C3.team_id;

एसक्यूएल फिडल

C1streak_typeप्रत्येक टीम और मैच के लिए गणना करता है ।

C2streak_typeद्वारा पिछले आदेश पाता है match_id desc

C3एक लंबे समय तक रखने के streak_sumद्वारा आदेशित राशि उत्पन्न करता है जो अंतिम मान के समान है।match_id desc0streak_type

मुख्य क्वेरी जहां लकीर है, streak_sumवह है 0


4
के उपयोग के लिए +1 LEAD()। 2012 में नए विंडोिंग कार्यों के बारे में पर्याप्त लोग नहीं जानते हैं
मार्क सिंकिन्सन

4
+1, मुझे LAG में अवरोही क्रम का उपयोग करने की चाल पसंद है, बाद में अंतिम लकीर, बहुत साफ! वैसे, के बाद से ओपी केवल टीम आईडी चाहता है, तो आप बदल सकते FantasyTeams JOIN FantasyMatchesके साथ FantasyMatches CROSS APPLY (VALUES (home_fantasy_team_id), (away_fantasy_team_id))है और इस तरह के संभावित प्रदर्शन में सुधार।
एंड्री एम

@AndriyM अच्छी पकड़ !! मैं उसी के साथ उत्तर को अपडेट करूंगा। यदि आपको इसके अन्य स्तंभों की आवश्यकता FantasyTeamsहै, तो इसके बजाय मुख्य क्वेरी में शामिल होना बेहतर है।
मिकेल एरिकसन 16

इस कोड उदाहरण के लिए धन्यवाद - मैं इसे एक कोशिश देने जा रहा हूं और बैठकों से बाहर होने के थोड़ी देर बाद वापस रिपोर्ट करूंगा ...>: - \
जामौस

@MikaelEriksson - यह महान काम करता है - धन्यवाद! त्वरित प्रश्न - मुझे मौजूदा पंक्तियों को अपडेट करने के लिए सेट किए गए इस परिणाम का उपयोग करने की आवश्यकता है (FantasyTeams.team_id पर शामिल होना) - आप इसे अपडेट विवरण में कैसे बदलने की सलाह देंगे? मैंने सिर्फ एक UPDATE में SELECT को बदलने की कोशिश शुरू की लेकिन मैं एक UPDATE में ग्रुप बाय का उपयोग नहीं कर सकता। क्या आप कहेंगे कि मुझे केवल एक परिणाम तालिका में सेट करना चाहिए और इसके साथ जुड़ना चाहिए या कुछ और करना चाहिए? धन्यवाद!
jamauss

10

इस समस्या को हल करने के लिए एक सहज दृष्टिकोण है:

  1. प्रत्येक टीम के लिए सबसे हाल का परिणाम खोजें
  2. पिछले मैच की जाँच करें और परिणाम प्रकार से मेल खाता है तो एक लकीर गिनती में जोड़ें
  3. चरण 2 को दोहराएँ, लेकिन जैसे ही पहला अलग परिणाम सामने आता है, रुक जाते हैं

यह रणनीति विंडो फंक्शन सॉल्यूशन (जो डेटा का पूरा स्कैन करती है) पर जीत हासिल कर सकती है क्योंकि मेजर बड़ा होता है, यह मानते हुए कि रिकर्सिव रणनीति को कुशलता से लागू किया जाता है। सफलता की कुंजी यह है कि जल्दी से पंक्तियों का पता लगाने के लिए (सीक का उपयोग करके) और तरह से बचने के लिए कुशल सूचकांक प्रदान किया जाए। आवश्यक अनुक्रमित हैं:

-- New index #1
CREATE UNIQUE INDEX uq1 ON dbo.FantasyMatches 
    (home_fantasy_team_id, match_id) 
INCLUDE (winning_team_id);

-- New index #2
CREATE UNIQUE INDEX uq2 ON dbo.FantasyMatches 
    (away_fantasy_team_id, match_id) 
INCLUDE (winning_team_id);

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

-- Table to hold just the rows that form streaks
CREATE TABLE #StreakData
(
    team_id bigint NOT NULL,
    match_id bigint NOT NULL,
    streak_type char(1) NOT NULL,
    streak_length integer NOT NULL,
);

-- Temporary table unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq ON #StreakData (team_id, match_id);

मेरा पुनरावर्ती क्वेरी समाधान निम्नानुसार है ( यहां SQL Fiddle ):

-- Solution query
WITH Streaks AS
(
    -- Anchor: most recent match for each team
    SELECT 
        FT.team_id, 
        CA.match_id, 
        CA.streak_type, 
        streak_length = 1
    FROM dbo.FantasyTeams AS FT
    CROSS APPLY
    (
        -- Most recent match
        SELECT
            T.match_id,
            T.streak_type
        FROM 
        (
            SELECT 
                FM.match_id, 
                streak_type =
                    CASE 
                        WHEN FM.winning_team_id = FM.home_fantasy_team_id
                            THEN CONVERT(char(1), 'W')
                        WHEN FM.winning_team_id IS NULL
                            THEN CONVERT(char(1), 'T')
                        ELSE CONVERT(char(1), 'L')
                    END
            FROM dbo.FantasyMatches AS FM
            WHERE 
                FT.team_id = FM.home_fantasy_team_id
            UNION ALL
            SELECT 
                FM.match_id, 
                streak_type =
                    CASE 
                        WHEN FM.winning_team_id = FM.away_fantasy_team_id
                            THEN CONVERT(char(1), 'W')
                        WHEN FM.winning_team_id IS NULL
                            THEN CONVERT(char(1), 'T')
                        ELSE CONVERT(char(1), 'L')
                    END
            FROM dbo.FantasyMatches AS FM
            WHERE
                FT.team_id = FM.away_fantasy_team_id
        ) AS T
        ORDER BY 
            T.match_id DESC
            OFFSET 0 ROWS 
            FETCH FIRST 1 ROW ONLY
    ) AS CA
    UNION ALL
    -- Recursive part: prior match with the same streak type
    SELECT 
        Streaks.team_id, 
        LastMatch.match_id, 
        Streaks.streak_type, 
        Streaks.streak_length + 1
    FROM Streaks
    CROSS APPLY
    (
        -- Most recent prior match
        SELECT 
            Numbered.match_id, 
            Numbered.winning_team_id, 
            Numbered.team_id
        FROM
        (
            -- Assign a row number
            SELECT
                PreviousMatches.match_id,
                PreviousMatches.winning_team_id,
                PreviousMatches.team_id, 
                rn = ROW_NUMBER() OVER (
                    ORDER BY PreviousMatches.match_id DESC)
            FROM
            (
                -- Prior match as home or away team
                SELECT 
                    FM.match_id, 
                    FM.winning_team_id, 
                    team_id = FM.home_fantasy_team_id
                FROM dbo.FantasyMatches AS FM
                WHERE 
                    FM.home_fantasy_team_id = Streaks.team_id
                    AND FM.match_id < Streaks.match_id
                UNION ALL
                SELECT 
                    FM.match_id, 
                    FM.winning_team_id, 
                    team_id = FM.away_fantasy_team_id
                FROM dbo.FantasyMatches AS FM
                WHERE 
                    FM.away_fantasy_team_id = Streaks.team_id
                    AND FM.match_id < Streaks.match_id
            ) AS PreviousMatches
        ) AS Numbered
        -- Most recent
        WHERE 
            Numbered.rn = 1
    ) AS LastMatch
    -- Check the streak type matches
    WHERE EXISTS
    (
        SELECT 
            Streaks.streak_type
        INTERSECT
        SELECT 
            CASE 
                WHEN LastMatch.winning_team_id IS NULL THEN 'T' 
                WHEN LastMatch.winning_team_id = LastMatch.team_id THEN 'W' 
                ELSE 'L' 
            END
    )
)
INSERT #StreakData
    (team_id, match_id, streak_type, streak_length)
SELECT
    team_id,
    match_id,
    streak_type,
    streak_length
FROM Streaks
OPTION (MAXRECURSION 0);

टी-एसक्यूएल पाठ काफी लंबा है, लेकिन क्वेरी का प्रत्येक खंड इस उत्तर की शुरुआत में दी गई व्यापक प्रक्रिया की रूपरेखा के निकट है। क्वेरी को सॉर्ट से बचने और TOPक्वेरी के पुनरावर्ती भाग (जो आमतौर पर अनुमति नहीं है) में उत्पादन करने के लिए कुछ ट्रिक्स का उपयोग करने की आवश्यकता से अधिक समय तक बनाया जाता है ।

निष्पादन योजना क्वेरी की तुलना में अपेक्षाकृत छोटी और सरल है। मैंने नीचे दिए गए स्क्रीनशॉट में एंकर क्षेत्र को पीला, और पुनरावर्ती भाग हरे रंग में छायांकित किया है:

पुनरावर्ती निष्पादन योजना

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

-- Basic results
SELECT
    SD.team_id,
    StreakType = MAX(SD.streak_type),
    StreakLength = MAX(SD.streak_length)
FROM #StreakData AS SD
GROUP BY 
    SD.team_id
ORDER BY
    SD.team_id;

मूल क्वेरी निष्पादन योजना

FantasyTeamsटेबल को अपडेट करने के लिए आधार के रूप में एक ही क्वेरी का उपयोग किया जा सकता है :

-- Update team summary
WITH StreakData AS
(
    SELECT
        SD.team_id,
        StreakType = MAX(SD.streak_type),
        StreakLength = MAX(SD.streak_length)
    FROM #StreakData AS SD
    GROUP BY 
        SD.team_id
)
UPDATE FT
SET streak_type = SD.StreakType,
    streak_count = SD.StreakLength
FROM StreakData AS SD
JOIN dbo.FantasyTeams AS FT
    ON FT.team_id = SD.team_id;

या, यदि आप पसंद करते हैं MERGE:

MERGE dbo.FantasyTeams AS FT
USING
(
    SELECT
        SD.team_id,
        StreakType = MAX(SD.streak_type),
        StreakLength = MAX(SD.streak_length)
    FROM #StreakData AS SD
    GROUP BY 
        SD.team_id
) AS StreakData
    ON StreakData.team_id = FT.team_id
WHEN MATCHED THEN UPDATE SET
    FT.streak_type = StreakData.StreakType,
    FT.streak_count = StreakData.StreakLength;

या तो दृष्टिकोण एक कुशल निष्पादन योजना बनाता है (अस्थायी तालिका में पंक्तियों की ज्ञात संख्या के आधार पर):

अद्यतन निष्पादन योजना

अंत में, क्योंकि पुनरावर्ती विधि स्वाभाविक रूप match_idसे इसके प्रसंस्करण में शामिल है, इसलिए match_idउत्पादन में प्रत्येक लकीर को बनाने वाली एस की सूची को जोड़ना आसान है :

SELECT
    S.team_id,
    streak_type = MAX(S.streak_type),
    match_id_list =
        STUFF(
        (
            SELECT ',' + CONVERT(varchar(11), S2.match_id)
            FROM #StreakData AS S2
            WHERE S2.team_id = S.team_id
            ORDER BY S2.match_id DESC
            FOR XML PATH ('')
        ), 1, 1, ''),
    streak_length = MAX(S.streak_length)
FROM #StreakData AS S
GROUP BY 
    S.team_id
ORDER BY
    S.team_id;

आउटपुट:

मैच सूची शामिल

निष्पादन योजना:

मैच सूची निष्पादन योजना


2
प्रभावशाली! क्या एक विशेष कारण है कि आपके पुनरावर्ती भाग का WHERE EXISTS (... INTERSECT ...)सिर्फ उपयोग के बजाय क्यों है Streaks.streak_type = CASE ...? मुझे पता है कि पूर्व विधि तब उपयोगी हो सकती है जब आपको NULLs को दोनों तरफ से और साथ ही मानों से मेल खाने की आवश्यकता होती है, लेकिन ऐसा नहीं है कि इस भाग में कोई NULLs का उत्पादन कर सकता है, इसलिए ...
Andriy M

2
@AndriyM हाँ वहाँ है। कोड बहुत सावधानी से कई स्थानों पर लिखा जाता है और बिना किसी प्रकार की योजना के निर्माण के तरीके। जब CASEउपयोग किया जाता है, तो ऑप्टिमाइज़र एक मर्ज कॉनेटेटेशन (जो यूनियन कुंजी ऑर्डर को संरक्षित करता है) का उपयोग करने में असमर्थ होता है और इसके बजाय कॉन्टेनेशन प्लस प्रकार का उपयोग करता है।
पॉल व्हाइट 9

8

परिणाम प्राप्त करने का एक और तरीका एक पुनरावर्ती CTE है

WITH TeamRes As (
SELECT FT.Team_ID
     , FM.match_id
     , Previous_Match = LAG(match_id, 1, 0) 
                        OVER (PARTITION BY FT.Team_ID ORDER BY FM.match_id)
     , Matches = Row_Number() 
                 OVER (PARTITION BY FT.Team_ID ORDER BY FM.match_id Desc)
     , Result = Case Coalesce(winning_team_id, -1)
                     When -1 Then 'T'
                     When FT.Team_ID Then 'W'
                     Else 'L'
                End 
FROM   FantasyMatches FM
       INNER JOIN FantasyTeams FT ON FT.Team_ID IN 
         (FM.home_fantasy_team_id, FM.away_fantasy_team_id)
), Streaks AS (
SELECT Team_ID, Result, 1 As Streak, Previous_Match
FROM   TeamRes
WHERE  Matches = 1
UNION ALL
SELECT tr.Team_ID, tr.Result, Streak + 1, tr.Previous_Match
FROM   TeamRes tr
       INNER JOIN Streaks s ON tr.Team_ID = s.Team_ID 
                           AND tr.Match_id = s.Previous_Match 
                           AND tr.Result = s.Result
)
Select Team_ID, Result, Max(Streak) Streak
From   Streaks
Group By Team_ID, Result
Order By Team_ID

SQLField डेमो


इस उत्तर के लिए धन्यवाद, समस्या का एक से अधिक समाधान देखना अच्छा है और दोनों के बीच प्रदर्शन की तुलना करने में सक्षम है।
जाम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.