2

I am trying to get this output using a SQL statement and the NORTHWIND database:

Employee Name:Nancy Davolio
Number of Sales:345
Total Sales:192107.60

Employee Name:Andrew Fuller
Number of Sales:241
Total Sales:166537.75

Employee Name:Janet Leverling
Number of Sales:321
Total Sales:202812.84

Employee Name:Margaret Peacock
Number of Sales:420
Total Sales:232890.85

Employee Name:Steven Buchanan
Number of Sales:117
Total Sales:68792.28

...and 4 more entries

When I use this statement:

USE Northwind

DECLARE @EmployeeName VARCHAR(40),
        @NumberOfSales INT,
        @TotalSales DECIMAL(10,2),
        @Counter TINYINT = 1,
        @NumEmployees INT = IDENT_CURRENT('dbo.Employees');

WHILE @Counter < @NumEmployees
BEGIN
    --SELECT @EmployeeName = E.FirstName+' '+E.LastName
    --SELECT @NumberOfSales = count(od.OrderID)
    SELECT @TotalSales = SUM(unitprice * quantity * (1 - Discount))
    FROM Employees E
    JOIN Orders AS O ON O.EmployeeID = E.EmployeeID
    JOIN [Order Details] AS OD ON OD.OrderID = O.OrderID
    WHERE E.EmployeeID = @Counter

    PRINT 'Employee Name:       '--+ @EmployeeName;
    PRINT 'Number of Sales:     '--+ LTRIM(STR(@NumberOfSales));
    PRINT 'Total Sales:         '+CONVERT(varchar(10),@TotalSales);
    PRINT '';

    SET @Counter += 1;
END

I can get each select to work singly but I cannot figure out the syntax to get a single SELECT statement to do all the work. I should also be able to do this with three SET statements but I've not been able to figure that out either. Pointers to both possibilities would be awesome.

Here's that actual step verbiage: "Within the loop, use a SELECT statement to retrieve the first and last name of each employee, the number of orders handled by each employee and the total sales amount for each employee (you are processing each employee one by one). You will need to join multiple tables together and use aggregate functions to get a count and a total. Assign the concatenated full name, number of sales and total sales amount to the appropriate variables."

Output should be in Messages tab, no table or format other than the expected output listed above.

SecretAgentMan
  • 2,856
  • 7
  • 21
  • 41
Tim
  • 71
  • 9
  • "Output should be in Messages tab" means using `Print`, `RaIsError` or `Throw` to output the data. AFAIK, query results can't be redirected to the Messages tab. You could write a single query, e.g. like those used to construct a comma-delimited list, to generate a single string with all of the data (including line breaks) and then `Print` that result. It's not the way an application would ordinarily be written. – HABO Dec 26 '18 at 03:51

2 Answers2

5

There is no need for loop(RBAR - Row By Agonizing Row approach should be avoided if possible):

SELECT EmployeeID
       ,[Employee Name] = E.FirstName+' '+E.LastName
       ,[TotalSales] = SUM(unitprice * quantity * (1-Discount))
       ,[NumberOfSales] = COUNT(DISTINCT o.OrderID)
FROM Employees E
JOIN Orders AS O ON O.EmployeeID = E.EmployeeID
JOIN [Order Details] AS OD ON OD.OrderID = O.OrderID
GROUP BY E.EmployeeID, E.FirstName+' '+E.LastName
ORDER BY E.EmployeeID;

EDIT:

Loop version - assigning multiple variables at once.

USE Northwind

DECLARE @EmployeeName VARCHAR(40),
        @NumberOfSales INT,
        @TotalSales DECIMAL(10,2),
        @Counter TINYINT = 1,
        @NumEmployees INT = IDENT_CURRENT('dbo.Employees');

WHILE @Counter < @NumEmployees
BEGIN
    SELECT @EmployeeName  = E.FirstName+' '+E.LastName
          ,@NumberOfSales = COUNT(DISTINCT o.OrderID)
          ,@TotalSales    = SUM(unitprice * quantity * (1 - Discount))
    FROM Employees E
    JOIN Orders AS O ON O.EmployeeID = E.EmployeeID
    JOIN [Order Details] AS OD ON OD.OrderID = O.OrderID
    WHERE E.EmployeeID = @Counter
    GROUP BY E.FirstName+' '+E.LastName;

    PRINT 'Employee Name:       '+ @EmployeeName;
    PRINT 'Number of Sales:     '+ LTRIM(STR(@NumberOfSales));
    PRINT 'Total Sales:         '+ CONVERT(varchar(10),@TotalSales);
    PRINT '';

    SET @Counter += 1;
END

Please note that using WHILE loop maybe very inefficient when you have gaps(i.e. you are starting from 1 up to IDENT_CURRENT, it may be a situation where you have ids like 1,5, 200671 and you end up with unecessary looping).


EDIT 2:

It seems the GROUP BY is required when multiple assigns take place in the select

I've added GROUP BY because FirstName and LastName was not wrapped with aggregated function. You could skip that clause but then you need to add MIN/MAX function:

SELECT @EmployeeName  = MIN(E.FirstName)+' '+MIN(E.LastName)
      ,@NumberOfSales = COUNT(DISTINCT o.OrderID)
      ,@TotalSales    = SUM(unitprice * quantity * (1 - Discount))
FROM Employees E
JOIN Orders AS O ON O.EmployeeID = E.EmployeeID
JOIN [Order Details] AS OD ON OD.OrderID = O.OrderID
WHERE E.EmployeeID = @Counter;
-- and we are sure that all values for First/Last nane are the same because of  
-- WHERE E.EmployeeID = @Counter

Related: Group by clause

In standard SQL, a query that includes a GROUP BY clause cannot refer to nonaggregated columns in the select list that are not named in the GROUP BY clause

Lukasz Szozda
  • 162,964
  • 23
  • 234
  • 275
  • Because, put me in a plane and we all die. Teach me to crawl, walk, run, jump,... then I can fly. While I can mostly(because I don't exactly understand your statement though I want to learn) appreciate the brevity of your code, I still need to learn these integral agonizing nuggets. – Tim Dec 26 '18 at 02:23
  • 1
    @TimGibney If you really have to use `LOOP` then you could assign mutliple variables at once. – Lukasz Szozda Dec 26 '18 at 09:41
  • Yes, perfect @Lukasz Szozda! Thank you. It seems the GROUP BY is required when multiple assigns take place in the select? – Tim Dec 26 '18 at 11:26
  • 1
    @TimGibney I've added clarification for that part – Lukasz Szozda Dec 26 '18 at 12:13
  • Right @Lukasz Szozda. Agreed about the WHILE logic when/if a non-contiguous section is happened upon. Preliminary bounds checking and continuity would be checked IRL. – Tim Dec 26 '18 at 13:09
  • 1
    @TimGibney Another option is CURSOR LOOP, but I assume it is outside of scope of your exercise for now – Lukasz Szozda Dec 26 '18 at 13:11
1

This should do it. I used CROSS APPLY to unpivot the set and then format it accordingly. You can read more about it in the article called: "CROSS APPLY an Alternative Method to Unpivot". Since SQL works with sets, input and output from SQL should always be a set in my humble opinion.

I am afraid that the way you formatted might not be a SQL's job but still do-able with a "single" select statement as a set operation:

;WITH CTE AS 
(
SELECT 
        EMPLOYEENAME =    E.FirstName +' '+ E.LastName,  
        NUMBEROFORDERS =  COUNT(OD.OrderID),
        TOTALSALES =      SUM(unitprice * quantity * (1-Discount))

   FROM Employees E
   INNER JOIN Orders AS O ON O.EmployeeID = E.EmployeeID
   INNER JOIN [Order Details] AS OD ON OD.OrderID = O.OrderID
   GROUP BY E.FirstName + ' ' + E.LastName
)


SELECT COLNAME, ColValue
FROM CTE

CROSS APPLY ( VALUES ('Employe Name:', EMPLOYEENAME),
                     ('Number of Sales:', LTRIM(STR(NUMBEROFORDERS, 25, 5)) ),
                     ('Total Sales:', LTRIM(STR(TOTALSALES, 25, 5)) ),
                     ('','')

            ) A  (COLNAME, ColValue)

Sample output is following:

COLNAME             ColValue
-------------   | ------------- 
Employe Name:   | Nancy Davolio
Number of Sales:|  345.00000
Total Sales:    |  192107.60432
  • So the expected output I list to begin with is from a formal "learn SQL" course that I am getting second hand. The workbook states explicit instructions but offers very minimal "answers" other than expected outputs. So strictly speaking the output listed does not match what's expected. Also, strictly speaking CROSS APPLY is not in this section nor is "CTE", whatever that is. – Tim Dec 26 '18 at 02:38
  • Well, that's interesting, if you happen to figure it out or stumble upon a solution, please, post it here. I am just curious :) –  Dec 26 '18 at 02:44