There could be 2 ways to do it.
One way is to include all non-aggregate columns into the group by list as below:
SELECT
c.firstname,
c.lastname,
c.CustomerId,
count(si.CustomerID) as 'No Of Orders'
FROM
customer c
LEFT OUTER JOIN
SalesInvoice si ON si.CustomerID = c.CustomerID
GROUP BY
c.CustomerID,
c.firstname,
c.lastname
Another way to do it, would be to use an aggregate function (min or max should do) in the select list:
SELECT
MIN(c.firstname) as firstname,
MIN(c.lastname) as lastname,
c.CustomerId,
count(si.CustomerID) as 'No Of Orders'
FROM
customer c
LEFT OUTER JOIN
SalesInvoice si ON si.CustomerID = c.CustomerID
GROUP BY
c.CustomerID
I think the first query may be better in performance. To improve overall performance you will need to create an included or covering index.
You can have a look at the following links to give you an idea on included indexes:
- Why use the INCLUDE clause when creating an index?
- http://msdn.microsoft.com/en-IN/library/ms190806.aspx