Home > Enterprise >  How do I create a dynamic order by on a multiple UNION query?
How do I create a dynamic order by on a multiple UNION query?

Time:09-30

I had a college exercise where I had to demonstrate a handy use of Union. I am using a sample TSQLV4 database.

I came up with the following code:

FROM

(SELECT TOP 5 
cust.companyname as [Customer], 
SUM(ord.total) AS [Total Spent],
COUNT(ord.orderid) AS [Total Orders],
'Top Spender' AS [Description]

FROM sales.Orders ord 
JOIN sales.customers cust ON cust.custid = ord.custid
GROUP BY [companyname]
ORDER BY 2 desc) base

UNION

SELECT [Customer],[Total Spent],[Total Orders],[Description]
FROM

(SELECT TOP 5 
cust.companyname as [Customer], 
SUM(ord.total) AS [Total Spent],
COUNT(ord.orderid) AS [Total Orders],
'Lowest Spender' AS [Description]

FROM sales.Orders ord 
JOIN sales.customers cust ON cust.custid = ord.custid
GROUP BY [companyname]
ORDER BY 2 ASC) base

UNION

SELECT [Customer],[Total Spent],[Total Orders],[Description]
FROM

(SELECT TOP 5 
cust.companyname as [Customer], 
SUM(ord.total) AS [Total Spent],
COUNT(ord.orderid) AS [Total Orders],
'Most Orders' AS [Description]

FROM sales.Orders ord 
JOIN sales.customers cust ON cust.custid = ord.custid
GROUP BY [companyname]
ORDER BY 3 desc) base

UNION

SELECT [Customer],[Total Spent],[Total Orders],[Description]
FROM

(SELECT TOP 5 
cust.companyname as [Customer], 
SUM(ord.total) AS [Total Spent],
COUNT(ord.orderid) AS [Total Orders],
'Least Orders' AS [Description]

FROM sales.Orders ord 
JOIN sales.customers cust ON cust.custid = ord.custid
GROUP BY [companyname]
ORDER BY 3 asc) base
order by 4,2

It returns:

Customer Total Spent Total Orders Description
Customer VMLOG 100.8000 1 Least Orders
Customer UISOJ 357.0000 2 Least Orders
Customer GCJSG 649.0000 3 Least Orders
Customer FVXPQ 1488.7000 2 Least Orders
Customer EYHKM 1571.2000 3 Least Orders
Customer VMLOG 100.8000 1 Lowest Spender
Customer UISOJ 357.0000 2 Lowest Spender
Customer IAIJK 522.5000 3 Lowest Spender
Customer GCJSG 649.0000 3 Lowest Spender
Customer MDLWA 836.7000 5 Lowest Spender
Customer CYZTN 32555.5500 19 Most Orders
Customer FRXZL 57317.3900 19 Most Orders
Customer THHDP 113236.6800 30 Most Orders
Customer LCOUJ 115673.3900 31 Most Orders
Customer IRRVL 117483.3900 28 Most Orders
Customer NYUHS 52245.9000 18 Top Spender
Customer FRXZL 57317.3900 19 Top Spender
Customer THHDP 113236.6800 30 Top Spender
Customer LCOUJ 115673.3900 31 Top Spender
Customer IRRVL 117483.3900 28 Top Spender

It works fine for the purpose of the drill (and the homework got an A ), but I notice there is an issue with the sorting and can't seem to let it go, lol.

I chose to order the whole "UNIONED" query (please correct with the right nomenclature, if any) by the 4th column so the rows are stuck together by category, but then I had to chose between the second ORDER BY level to be the 2nd or 3rd columns (Total Spent, Total Orders). The problem is that if I sort by one or another, the second level sorting won't make sense for half of the categories (Least/Most orders sorted by total spent, or Lowest/Top Spenders sorted by total orders).

I have already created a table function that picks up a varchar (planning it to be the description column value) and returning the whole UNIONED query custom ordered, depending on IF statements that read the value for the @description parameter.

Now I cannot seem to create that last line that I thought would give me my intended behavior.

[...]
ORDER BY 3 asc) base
ORDER BY dbo.FtOrdenamiento(base.[Description])

Cannot find either column "dbo" or the user-defined function or aggregate "dbo.FtOrdenamiento", or the name is ambiguous.

Could anyone suggest a way to achieve this?

CodePudding user response:

put all of your query in a cte and select your fields in the cto after that write your order by

for e.g:


WITH q
AS
(
SELECT p.ProductID,p.Name,p.ModifiedDate
FROM Production.Product p

UNION 

SELECT ProductCategoryID,Name,ModifiedDate
FROM Production.ProductCategory
)
SELECT *
FROM q
ORDER BY q.Name,q.ModifiedDate

CodePudding user response:

You can add a ROW_NUMBER in each of your subqueries and use this to order the final result:

e.g.

SELECT s.Client , s.InvoiceAmount,s.Description,s.RN
FROM    (
    SELECT TOP 5 f.Client , f.InvoiceAmount,'Small clients' as Description,ROW_NUMBER() OVER (ORDER BY f.InvoiceAmount) RN
    FROM dbo.FactSales f
    ORDER BY f.InvoiceAmount 
    ) s

UNION ALL

SELECT l.Client , l.InvoiceAmount,Description,l.RN
FROM    (
    SELECT TOP 5 f.Client , f.InvoiceAmount,'Large clients' as Description,ROW_NUMBER() OVER (ORDER BY f.InvoiceAmount DESC) RN
    FROM dbo.FactSales f
    ORDER BY f.InvoiceAmount DESC
    ) l

ORDER BY Description,RN;
  • Related