SQL Server解惑——為什麼ORDER BY改變了變量的字符串拼接結果

2021-02-14 DBA閒思雜想錄

  在SQL Server中可能有這樣的拼接字符串需求,需要將查詢出來的一列拼接成字符串,如下案例所示,我們需要將AddressID <=10的AddressLine1欄位拼接起來,分隔符為|。如下截圖所示。這種方式看起來似乎沒有什麼問題,而且簡單測試也是OK的:

USE AdventureWorks2014;
GO
DECLARE @address_list NVARCHAR(MAX);
SET @address_list ='';

SELECT @address_list = @address_list + AddressLine1 + '|' 
FROM [Person].[Address] WHERE AddressID <=10;

SELECT @address_list



  但是,如果SQL多了一個排序操作,結果就變了,這個SQL的變量@address_list只獲取到了最後一條記錄」9833 Mt. Dias Blv.|「,

USE AdventureWorks2014;
GO
DECLARE @address_list NVARCHAR(MAX);
SET @address_list ='';

SELECT @address_list = @address_list + AddressLine1 + '|' 
FROM [Person].[Address] WHERE AddressID <=10 ORDER BY 1;

SELECT @address_list


  但是你使用其它一些欄位排序的話,它又是OK的。在各種實際生產環境中,可能按某個欄位排序,字符串拼接就不正常了。但是按有些欄位排序又是正常的。有點搞不清套路。下面簡單構造一個案例

USE AdventureWorks2014;
GO
CREATE TABLE TEST
(
    ID  INT NOT NULL
   ,NAME NVARCHAR(100) NOT NULL 
   ,SortID  INT NOT NULL
   ,CONSTRAINT PK_TEST PRIMARY KEY (ID)
);

INSERT INTO dbo.TEST
SELECT 1, 'Kerry'  , 1 UNION ALL 
SELECT 2, 'Jerry'  , 2 UNION ALL
SELECT 3, 'Ken'    , 3 UNION ALL
SELECT 4, 'Richard', 4 UNION ALL
SELECT 5, 'Jimmy'  , 5;

DECLARE @name_list NVARCHAR(100);
SET @name_list='';

SELECT @name_list = @name_list + t.NAME + '|'
FROM dbo.TEST t
ORDER BY t.SortID;

SELECT @name_list;

上面腳本測試都正常,下面測試就會出現連接字符串只獲取了最後一行記錄的情況。

DECLARE @name_list NVARCHAR(100)='';

SET @name_list=' '
SELECT @name_list = @name_list + t.NAME + '| '
FROM dbo.TEST t
WHERE ID IN (1,2,3)
ORDER BY t.SortID;

SELECT @name_list;



  在生產環境還有各種魔幻的現象,按其中一個欄位排序是正常,換另外一個欄位排序就出現這種現象。如果你將上面測試表的欄位的大小修改一下,然後測試下面腳本,發現又不會出現這種情況:

USE AdventureWorks2014;
GO
DROP TABLE dbo.TEST;
GO
CREATE TABLE TEST
(
 ID  INT NOT NULL
   ,NAME NVARCHAR(32) NOT NULL 
   ,SortID  INT NOT NULL
   ,CONSTRAINT PK_TEST PRIMARY KEY (ID)
);

INSERT INTO dbo.TEST
SELECT 1, 'Kerry'  , 1 UNION ALL 
SELECT 2, 'Jerry'  , 2 UNION ALL
SELECT 3, 'Ken'    , 3 UNION ALL
SELECT 4, 'Richard', 4 UNION ALL
SELECT 5, 'Jimmy'  , 5;


初看像一個「Bug」,但是它確實不是一個Bug,官方文檔http://support.microsoft.com/kb/287515有介紹這個現象,但是目前現在這個連結失效了,搜索也找不到對應的連結了(微軟的官方文檔這一點是相當坑爹,不如Oracle做得好,經常一個連結失效,好的情況是連結換了,糟糕的情況就是這種,根本找不到了),下面的資料是在其它資料裡面引用KB 287515的內容:

事實證明,此迭代級聯/迭代拼接(iterative concatenation)的功能是不受支持的功能。Microsoft知識庫文章287515指出

You may encounter unexpected results when you apply any operators or expressions to the ORDER BY clause of aggregate concatenation queries.

We do not make any guarantees on the correctness of concatenation queries (like using variable assignments with data retrieval in a specific order). The query output can change in SQL Server 2008 depending on the plan choice, data in the tables etc. You shouldn't rely on this working consistently even though the syntax allows you to write a SELECT statement that mixes ordered rows retrieval with variable assignment.

The correct behavior for an aggregate concatenation query is undefined

簡單來說,這樣拼接字符串,雖然在語法上支持,但是卻不能保證這樣的結果正確性,聚合串聯查詢的行為是不確定的。如果想安全可靠的拼接字符串的話,有下面一些方式:

方式1:

DECLARE @name_list VARCHAR(512);

SELECT  @name_list=
(
SELECT  t.NAME + '|'
FROM dbo.TEST t
WHERE ID IN (1,2,3)
ORDER BY t.SortID
FOR XML PATH(''), TYPE
).value('.', 'varchar(max)')

SELECT @name_list;

方式2:

SELECT Name + '|' AS 'data()' 
FROM dbo.TEST 
WHERE ID IN (1,2,3)
FOR XML PATH('');



方式3. 藉助STUFF函數


方式4. 藉助COALESCE函數

注意,使用COALESCE有可能也是不行的。如果定義@name_list為 VARCHAR(512)或VARCHAR(MAX)則是OK的。

DECLARE @name_list VARCHAR(100);
SELECT @name_list = COALESCE(@name_list + ', ', '') + Name 
FROM dbo.TEST
WHERE ID IN (1,2,3)
ORDER BY SortID

SELECT @name_list

5. 使用CRL聚合拼接字符串。

6. 如果SQL Server 2017使用STRING_AGG實現。

SELECT  STRING_AGG(Name, '|') AS Departments
FROM dbo.TEST
WHERE ID IN (1,2,3)


SELECT SortID, STRING_AGG(Name, '|') AS Departments
FROM dbo.TEST
WHERE ID IN (1,2,3)
GROUP BY SortID
ORDER BY SortID;

參考資料:

https://stackoverflow.com/questions/5538187/why-sql-server-ignores-vaules-in-string-concatenation-when-order-by-clause-speci/5538210#5538210

https://stackoverflow.com/questions/194852/how-to-concatenate-text-from-multiple-rows-into-a-single-text-string-in-sql-serv

相關焦點

  • Python字符串拼接(包含字符串拼接數字)
    str2"strname 表示拼接以後的字符串變量名,str1 和 str2 是要拼接的字符串內容。使用這種寫法,Python 會自動將兩個字符串拼接在一起。如果需要使用變量,就得藉助+運算符來拼接,具體格式為:strname = str1 + str2當然,+運算符也能拼接字符串常量。
  • Python字符串的拼接
    定義字符串可以使用單引號、雙引號和三引號來完成,這三種引號的作用是一樣的,都可以直接定義字符串,唯一的不同是三引號支持多行內容。而現在,為什麼要深入的專門探討字符串呢?原因只有一個:字符串的應用廣泛到鋪天蓋地!
  • 鞏固SQL - 窗口函數&變量&數據透視圖
    但作為合格的一個數據分析師,sql的精通肯定是必不可少的,所以最近瘋狂刷sql題,同時也來總結下我以前比較少用的語法。4.3.1、lad,lead函數的理解需要想清楚「為什麼會出現Null值」SELECT *,lag(cost,1) over(partition by name1 order by cost desc) as lag_function, lead(cost,1) over(partition by name1 order by cost
  • 一道簡單的sql語句題
    >再次執行我們上面的sql語句,就當我們以為大功告成之時,沒想到結果只輸出了一條,而不是所有的用戶的每一條記錄:咦,結果輸出好像跟我們之前沒有什麼差別?這是為什麼呢?這個在mysql中並沒有實現,在oracle或者sql server中是有實現的。
  • Python編程23:字符串拼接
    字符串的訪問就是讀取字符串的內容或者部分內容,字符串的分割就是把字符串按照一定的規律分成幾部分。那如果我們想把幾個字符串拼接起來呢?字符串的拼接字符串既然有需要分割的時候,那自然就也有需要結合的時候。今天我會介紹兩種常用的方式:1、使用「+」號如果只是合併少數字符串,可以使用加號「+」。
  • 【乾貨】SQL注入之Order-by-Leak
    $inject = $db->escape_string($_GET['order']);3. $sql = "select * from user order by $inject";4. $result = $db->query($sql);5. show_fileds($result); // 講查詢結果按順序列印出來這個時候注入點在order by 後面,order by 後面是不能帶union select,而且此處也是不存在報錯注入的。唯一顯示是通過該處查詢得到的user表的所有內容。
  • 小瘋談python:字符串的拼接與格式化輸出
    因此為了使結果具有更好的可讀性和靈活性,字符串的格式化輸出就顯得格外重要。例如,我現在接受了用戶的輸入的年齡信息,如果我直接將這個年齡列印在控制臺上那麼對於需要這些數據的人來說可能不友好,因為他們可能不知道這個數據是什麼意思,也就是說列印出來到數據不具有泛讀性。同樣的在字符串中我們無法直接使用代碼中的變量因為python解釋器會默認字符串內的元素為字符。
  • SQL Server的Descending Indexes降序索引
    OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]GO 插入測試數據DECLARE @i INT;DECLARE @sql
  • SQL Server 2014
    解壓「cn_sql_server_2014_X64.iso」64位系統解壓「cn_sql_server_2014_X64.iso」。右擊「cn_sql_server_2014_X64.iso」點擊「解壓到cn_sql_server_2014_X64.iso」。
  • SQL Server應用程式的高級Sql注入
    返回的結果集也可以加以特定條件'author'限制: select id, forename, surname from authors where forename = 'john' and surname = 'smith' 注意這裡很重要的一點是'john'和'smith'是被單引號引住的,假設'forename'和'surname'欄位是來自於用戶的輸入,攻擊者就可能通過輸入非法字符串來對這個查詢進行
  • 從WordPress SQLi談PHP格式化字符串問題
    在這裡代碼拼接出了如下sql語句,meta_value為傳入的media參數SELECT meta_id FROM wp_postmeta WHEREmeta_key = '_thumbnail_id' AND meta_value = 'payload'之後這條語句會進入查詢,結果為真代碼才能繼續,所以要修改_thumbnail_id對應的meta_value的值為payload,保證有查詢結果。
  • 驚,Java 字符串拼接竟然有這麼多玩法!| CSDN 原力計劃
    我就認認真真地寫一篇文章,給小菜解惑一下。要說姿勢,「+」號操作符必須是字符串拼接最常用的一種了,沒有之一。0,那麼返回拼接前的字符串。if (otherLen == 0) {    return this;}2)將原字符串的字符數組 value 複製到變量 buf 數組中。char buf[] = Arrays.copyOf(value, len + otherLen);3)把拼接的字符串 str 複製到字符數組 buf 中,並返回新的字符串對象。
  • SQL Server 2019安裝教程
    1、選中下載的【sql_server_2019】軟體壓縮包,滑鼠右擊選擇【解壓到sql_server_2019】。2、雙擊打開解壓後的【sql_server_2019】文件夾。3.滑鼠右擊【sql_server_2019.iso】,選擇【打開方式】-【Windows資源管理器】。
  • 字符串拼接還在用StringBuilder?快試試Java8中的StringJoiner吧,真香!
    前言之前,我們經常會通過StringBuffer或者StingBuilder對字符串進行拼接,但是你知道Java8中推出的StringJoiner嗎?它比前者更加優美、靈活,如果你現在還使用StringBuffer拼接,強烈推薦你試試StringJoiner。
  • SQL Server 安裝步驟
    本文通過圖文並茂的形式給大家介紹了SQL server 2016 安裝步驟,非常不錯,具有參考借鑑價值,需要的朋友參考下吧。
  • MyBatis 動態 SQL 詳解(以後寫 SQL 爽多了)
    的前綴,該前綴會被放在trim標籤拼接好的sql前面suffix:指定sql的後綴,該後綴會被放在trim標籤拼接好的sql後面prefixOverrides:前綴覆蓋,MyBatis會自動去除屬性前的指定字符串suffixOverrides:後綴覆蓋,MyBatis會自動去除屬性後的指定字符串在這裡我們將前綴指定為where,並指定後綴覆蓋為and
  • 經典SQL語句大全
    DB2中列加上後數據類型也不能改變,唯一能改變的是增加varchar類型的長度。12、說明:日程安排提前五分鐘提醒SQL: select * from 日程安排 where datediff('minute',f開始時間,getdate())>513、說明:一條sql 語句搞定資料庫分頁select top 10 b.* from (select top 20 主鍵欄位,排序欄位 from 表名 order by 排序欄位
  • Mybatis的sql組裝詳解
    上一篇分析了SqlSession執行sql的過程,其中並沒有分析sql是從哪裡來的,今天就來仔細分析下。Sql來源從上一篇的最後一步執行sql那裡倒推sql的來源,源碼主要過程如下圖:可以看到最後是通過BoundSql直接獲取的sql,然後往前倒推最後發現是通過MappedStatement的getBoundSql方法返回的。
  • 不一樣的 SQL Server 日期格式化
    output:DateTime Result Custom Number Result ---- 27/09/2012 123-45-6789 (1 row(s) affected) Referencehttps://docs.microsoft.com/en-us/sql
  • 「SQL」重點整理
    54、全局變量:SQL server 系統內部事先定義好的變量,不用用戶參與定義,任何程序均可隨時調用。全局變量通常用於存儲一些SQL server的配置設定值和效能統計數據。SQL server一going使用了30多個全局變量。以@@開頭。