2016-07-28 24 views
1

我試圖在使用MySQL的HackerRank上解決這個問題。 https://www.hackerrank.com/challenges/occupationsMYSQL操作順序操作失敗:ORDER BY正在影響SELECT語句的結果

支點職業的職業欄,使得每個名稱按字母順序排序和其相應的職業下方顯示。輸出列標題應分別爲醫生,教授,歌手和演員。

#Input Format 

Samantha Doctor 
Jenny Doctor 
Ashley Professor 
... 

#Sample Output 

Jenny Ashley  Meera Jane 
Samantha Christeen Priya Julia 
NULL  Ketty  NULL Maria 

這是MySQL給出瞭解決方案:

set @r1=0, @r2=0, @r3=0, @r4=0; 
select min(Doctor), min(Professor), min(Singer), min(Actor) from(
    select case when Occupation='Doctor' then (@r1:[email protected]+1) 
     when Occupation='Professor' then (@r2:[email protected]+1) 
     when Occupation='Singer' then (@r3:[email protected]+1) 
     when Occupation='Actor' then (@r4:[email protected]+1) end as RowNumber, 
    case when Occupation='Doctor' then Name end as Doctor, 
    case when Occupation='Professor' then Name end as Professor, 
    case when Occupation='Singer' then Name end as Singer, 
    case when Occupation='Actor' then Name end as Actor 
from OCCUPATIONS 
order by Name 
) Temp 
group by RowNumber 

這將返回按字母順序排列的輸出,而不是一個隨機的順序,因爲ROWNUMBER發生了變化,由於ORDER BY語句。

我的問題是爲什麼order by Name影響列RowNumber的排序?

SQL中的操作順序表示所有SELECT語句都發生在ORDER BY語句之前。 ORDER BY發生時,不應該計算行號嗎?

編輯
既然人都在問關於操作順序,我已經包含在那裏我看到了(更不用提我的SQL教材)的多個地方的鏈接: http://sqlbolt.com/lesson/select_queries_order_of_execution
http://www.bennadel.com/blog/70-sql-query-order-of-operations.htm

+2

那就是爲什麼每當我做這樣的東西我在子查詢中把ORDER BY這是由這種遞增計算所使用的。從技術上講,SELECT應該發生在ORDER BY之前,但這並不意味着MySQL不會早些使用「提示」;類似於MySQL在使用條件減少結果行之前不會忽略有用的WHERE條件,而是有利於生成連接整個表。 – Uueerdo

+0

感謝您的回答。來自EARLIER的SQL「提示」是什麼意思?我認爲SELECT語句應該在它到達ORDER BY時已經執行完畢。這會導致行號變量在到達ORDER BY語句時已被設置/打印 –

+0

'ORDER BY'可以利用索引;索引並不真正與選擇的結果有關,所以它必須能夠在完全評估選擇之前使用這樣的索引。 – Uueerdo

回答

1

首先,其中它說,在操作順序,一個SELECTORDER BY之前執行?這一說法根本不是事實。

什麼真的是,SELECTORDER BY之前由編譯器評估。由於這個原因,在SELECT中定義的列別名可用於ORDER BY

除此之外,變量是對SQL的擴展。 MySQL是比較明確的是SELECT是所執行的最後一句:

SELECT聲明,只有當發送到客戶端 每個選擇表達式。這意味着在HAVING,GROUP BY或ORDER BY BY 子句中,引用在選擇 表達式列表中指定的值的變量不能按預期工作。 。 。

因爲ORDER BY在將結果返回給客戶端之前發生,所以在對包含變量的表達式求值之前對數據進行排序。

+0

奇怪的是,MySQL的示例'SELECT(@aa:= id)AS a,(@ aa + 3)AS b FROM tbl_name HAVING b = 5;'似乎只是因爲無法使用'HAVING'而打破結果。如果添加「GROUP BY」和/或「ORDER BY」,結果會顯示出從左至右的字段評估方式。 – Uueerdo

+0

@Uueerdo。 。 。我期望的結果是不確定的;) –

+0

...進一步的類似查詢實驗似乎表明它評估這樣的領域的計算兩次(首先爲有,然後爲選擇)當只有一個存在。使用'SELECT(@aa:= id)AS a,(@ aa + id)AS b FROM tbl_name HAVING b = 4;'我在連續迭代中收到不同的結果。 _Indicating'b'獨立於'a'而被評估爲'HAVING'._ – Uueerdo

1

評價的順序是undefined

從MySQL參考手冊http://dev.mysql.com/doc/refman/5.7/en/user-variables.html

作爲一般規則,比SET語句等,你永遠不應該值分配給一個用戶變量和讀取同一語句中的價值。例如,爲了增加一個變量,這是好的:

SET @a = @a + 1;

對於其他的語句,如SELECT,你可能會得到預期的結果,但是這不能保證。在下面的語句,你可能會認爲,MySQL將評估@a第一,然後做一個分配第二:

SELECT @a, @a:[email protected]+1, ...;

然而,評價的順序涉及用戶變量表達式是不確定的。


由於@Uueerdo評論,我們可以解決這個通常會得到與內嵌視圖(派生表)。在內聯視圖中執行ORDER BY,然後從中選擇並使用用戶定義的變量執行操作。

此外,我不相信有任何保證CASE表達式中的表達式不會被執行... CASE表達式只需要返回表達式的值,但不能保證分配不會發生。


像這樣的東西,行爲仍然是不確定的,但我們通常看到更一致的行爲:(未測試)

SELECT MIN(IF(s.Occupation='Doctor' ,s.Name,NULL)) AS Doctor 
     , MIN(IF(s.Occupation='Professor' ,s.Name,NULL)) AS Professor 
     , MIN(IF(s.Occupation='Singer' ,s.Name,NULL)) AS Singer 
     , MIN(IF(s.Occupation='Actor'  ,s.Name,NULL)) AS Actor 
    FROM (SELECT t.Name 
       , t.Occupation 
       , @r1 := @r1 + IF(t.Occupation='Doctor' ,1,0) AS r1 
       , @r2 := @r2 + IF(t.Occupation='Professor' ,1,0) AS r2 
       , @r3 := @r3 + IF(t.Occupation='Singer' ,1,0) AS r3 
       , @r3 := @r4 + IF(t.Occupation='Actor'  ,1,0) AS r4 
      FROM OCCUPATIONS t 
      CROSS 
      JOIN (SELECT @r1:=0, @r2:=0, @r3:=0, @r4:=0) i 
      WHERE t.Occupation IN ('Doctor','Professor','Singer','Actor') 
      ORDER BY t.Name, t.Occupation 
     ) s 
    GROUP BY CASE s.Occupation 
      WHEN 'Doctor' THEN s.r1 
      WHEN 'Professor' THEN s.r2 
      WHEN 'Singer' THEN s.r3 
      WHEN 'Actor'  THEN s.r4 
      ELSE NULL 
      END 
    ORDER BY CASE s.Occupation 
      WHEN 'Doctor' THEN s.r1 
      WHEN 'Professor' THEN s.r2 
      WHEN 'Singer' THEN s.r3 
      WHEN 'Actor'  THEN s.r4 
      ELSE NULL 
      END 

這仍然可能會因爲問題操作順序。要解決這一問題,我們可以使用內嵌視圖,以獲得MySQL我們做行前的編號進行排序操作...

SELECT MIN(IF(s.Occupation='Doctor' ,s.Name,NULL)) AS Doctor 
     , MIN(IF(s.Occupation='Professor' ,s.Name,NULL)) AS Professor 
     , MIN(IF(s.Occupation='Singer' ,s.Name,NULL)) AS Singer 
     , MIN(IF(s.Occupation='Actor'  ,s.Name,NULL)) AS Actor 
    FROM (SELECT t.Name 
       , t.Occupation 
       , @r1 := @r1 + IF(t.Occupation='Doctor' ,1,0) AS r1 
       , @r2 := @r2 + IF(t.Occupation='Professor' ,1,0) AS r2 
       , @r3 := @r3 + IF(t.Occupation='Singer' ,1,0) AS r3 
       , @r3 := @r4 + IF(t.Occupation='Actor'  ,1,0) AS r4 
      FROM (SELECT u.Name 
         , u.Occupation 
        FROM OCCUPATIONS u 
        WHERE u.Occupation IN ('Doctor','Professor','Singer','Actor') 
        ORDER BY u.Name 
       ) t 
      CROSS 
      JOIN (SELECT @r1:=0, @r2:=0, @r3:=0, @r4:=0) i 
     ) s 
    GROUP BY CASE s.Occupation 
      WHEN 'Doctor' THEN s.r1 
      WHEN 'Professor' THEN s.r2 
      WHEN 'Singer' THEN s.r3 
      WHEN 'Actor'  THEN s.r4 
      ELSE NULL 
      END 
    ORDER BY CASE s.Occupation 
      WHEN 'Doctor' THEN s.r1 
      WHEN 'Professor' THEN s.r2 
      WHEN 'Singer' THEN s.r3 
      WHEN 'Actor'  THEN s.r4 
      ELSE NULL 
      END 
+1

根據我的經驗,SELECT中的字段從左到右進行評估;似乎當人們開始從同一個字段表達式中分配變量時會遇到會話變量的問題,例如'IF(@i <> 1,@i:= 1,@i)'傾向於導致問題_(並非特別說明,這可能很好,只是給出導致問題的一般「使用方式」)。 – Uueerdo

+0

我遇到了在IF函數中做賦值的問題。我在函數,表達式......之外進行增量賦值。 SELECT'@v:= @v + expr_return_1_or_0'。我知道表達式的評估必須在添加之前和分配之前完成。 – spencer7593

+1

是的,當我需要在同一個選擇表達式中使用和更新會話變量時,我通常會犯這樣的偏執狂。與我之前的評論相比,我實際使用的一個等價物將是'@i:= IF(@i> 1,1,@i)' – Uueerdo