SQL finding the total weight - sql-server

tables diagram
Code used:
SELECT
ROUND(SUM(COALESCE(p.[Weight], 0)), 2) AS [Total weight]
FROM
SalesLT.SalesOrderHeader soh
JOIN
SalesLT.SalesOrderDetail sod ON sod.SalesOrderID = soh.SalesOrderID
JOIN
SalesLT.Product p ON p.ProductID = sod.ProductID
WHERE
DATEPART(YEAR, soh.DueDate) = '2017'
AND DATEPART(MONTH, soh.DueDate) = '05'
OR (DATEPART(MONTH, soh.DueDate) = '06'
OR DATEPART(MONTH, soh.DueDate) = '12')
AND soh.ShipMethod = 'Unknown';
I got this question wrong and was wondering if someone could see what was wrong with the code? Looks right to me.
Question:
What's the total weight in kilograms of orders due in May 2017, or June 2017, or December 2017? Only include orders which used an Unknown shipping method. Get the total weight, rounded and padded to 2dp.

You need both unit weight multiplied by the quantity of those units. If one ships a single item - or - 100 of those items the total weight is not the same.
Plus, you need to couple the year restriction with each of the month restrictions so you get 2017-04, 2017-07, 2017-11. In you original you get 2017-04 but any rows in months 7 and 11. Use parentheses to control the meaning of the predicates.
SELECT
ROUND(SUM(COALESCE(p.[Weight] * sod.OrderQty, 0)), 2) AS [Total weight]
FROM SalesLT.SalesOrderHeader soh
JOIN SalesLT.SalesOrderDetail sod
ON sod.SalesOrderID = soh.SalesOrderID
JOIN SalesLT.Product p
ON p.ProductID = sod.ProductID
WHERE soh.ShipMethod = 'UnKnown'
AND DATEPART(YEAR, soh.DueDate) = 2017
AND (
DATEPART(MONTH, soh.DueDate) = 04
OR DATEPART(MONTH, soh.DueDate) = 07
OR DATEPART(MONTH, soh.DueDate) = 11
)
as mentioned by marc_s also avoid the type conversion as datepart() returns integers.
Personally I would prefer to use 3 full date ranges instead of using datepart
WHERE soh.ShipMethod = 'UnKnown'
AND (soh.DueDate >= '20170401' and soh.DueDate < '20170501')
AND (soh.DueDate >= '20170701' and soh.DueDate < '20170801')
AND (soh.DueDate >= '20171101' and soh.DueDate < '20171201')

Related

DATEDIF Report builder can i do an OR or where

I am building a report where I need to display the number of days an item was with a customer in the chosen month.
#Month is set as a parameter and the report is filtered based on the month a user chooses.
I have DATEDIFF(d, dbo.Rental.StartDateTime, { fn NOW() }) + 1 AS [Days Live] however this isn't what I need.
A rental could start before the chosen month or part way through. It could end and any date in the chosen month or not have been ended at all. so for example if running the report for November (assuming i am now in December) i could have a rental that started (dbo.Rental.StartDateTime) 15th October and ended (dbo.Rental.EndDateTime) 11th November. I would need the field to say 11 however if there is no end date i would need it to say 30
I have formatted both fields to be dd/mm/yyy. The dataset is also doing various other things in terms of displaying the relevant records.
My entire dataset is below.
SELECT TOP (100) PERCENT System_1.SystemTypeId, System_1.Id, System_1.CancellationCode, System_1.RentalId, Format(System_1.CreatedOnDateTime, 'dd/MM/yyyy') AS [Assembled Date],
Format(dbo.Rental.StartDateTime, 'dd/MM/yyyy') AS [Start Date], DATEPART(month, dbo.Rental.StartDateTime) AS [Start Date Month], dbo.Rental.Revision, dbo.Rental.RentalNumber, dbo.Rental.RentalStatus, dbo.Customer.Name AS Customer, dbo.Site.Name AS Site,
dbo.Location.Name AS Location, dbo.Rental.SpecialInstructions, DATEDIFF(d, dbo.Rental.StartDateTime, { fn NOW() }) + 1 AS [Days Live], dbo.SystemType.Description, Format(dbo.Rental.EndDateTime,
'dd/MM/yyyy') AS [End Date], DATEPART(month, dbo.Rental.EndDateTime) AS [End Date Month]
FROM dbo.SystemType INNER JOIN
dbo.System AS System_1 INNER JOIN
dbo.Contract ON System_1.ContractId = dbo.Contract.Id INNER JOIN
dbo.Customer ON dbo.Contract.CustomerId = dbo.Customer.Id ON dbo.SystemType.Id = System_1.SystemTypeId LEFT OUTER JOIN
dbo.Location INNER JOIN
dbo.Rental ON dbo.Location.Id = dbo.Rental.LocationId INNER JOIN
dbo.Site ON dbo.Location.SiteId = dbo.Site.Id ON System_1.Id = dbo.Rental.SystemId
WHERE (dbo.Rental.RentalStatus NOT LIKE 'PendingActivations') AND dbo.customer.ID = #Customer AND ((DATEPART(month, dbo.Rental.StartDateTime) = #Month) OR (DATEPART(month, dbo.Rental.EndDateTime) = #Month) OR ( (DATEPART(month, dbo.Rental.StartDateTime) < #Month) AND (dbo.Rental.EndDateTime IS NULL )))
ORDER BY System_1.Id

Adding 30 days, 60 days and 90 days column for payments made during that time in SQLServer

I want to add three columns to my GridControl that shows payments made in the past 30, 60 and 90 days.
Below is my SQL statement that I use to get all the payments (Amount) that was made by a single customer.
SELECT p.CustomerID, c.FirstName, c.LastName, SUM(p.Amount) As 'Amount
FROM Payment p
INNER JOIN Customer c
on p.CustomerID = c.ID
Group by p.CustomerID, c.FirstName, c.LastName
I have also tried the following but it only gives me a NULL value
SELECT p.CustomerID, c.FirstName, c.LastName, SUM(p.Amount) As 'Amount', a.[30Days] As '30 Days'
FROM Payment p
inner join Customer c
on p.CustomerID = c.ID
JOIN (SELECT SUM(p.Amount) AS '30Days'
FROM Payment p
WHERE DATEDIFF(day, GETDATE(), PaymentDate) BETWEEN 0 AND 30) AS a ON c.ID = p.CustomerID
Group by p.CustomerID, c.FirstName, c.LastName, a.[30Days]
You should review datediff - you have the parameters the wrong way round, and you can use conditional aggregation so a third join is not required.
SELECT p.CustomerID, SUM(p.Amount) As 'Amount',
sum(case when DATEDIFF(day, PaymentDate,GETDATE()) BETWEEN 0 AND 30 then amount else 0 end) [30days],
sum(case when DATEDIFF(day, PaymentDate,GETDATE()) BETWEEN 0 AND 60 then amount else 0 end) [60days],
sum(case when DATEDIFF(day, PaymentDate,GETDATE()) BETWEEN 0 AND 90 then amount else 0 end) [90days]
FROM Payment p
INNER JOIN Customer c
on p.CustomerID = c.customerID
Group by p.CustomerID;

Percentage of Sales for each type of customer

I am wanting to find the percentage of sales per week for each customer type. I can see the math but I cant figure out how to write the query.
SELECT
c.customerType as 'Customer Type',
DATEPART(WEEK, o.orderDate) as 'Week of the year',
COUNT(c.customerType) as 'Number of sales'
FROM
[dbo].[Order] o
JOIN
Customer c ON c.id = o.customerId
GROUP BY
c.customerType, DATEPART(WEEK, o.orderDate)
This query outputs a count of each sale grouped by customer type.
CustomerType Week Number of Sales
------------------------------------
Cash 36 248
Corporate 36 10
Personal 36 5
Cash 37 113
Corporate 37 3
Personal 37 2
Cash 38 136
Corporate 38 7
Personal 38 2
Cash 39 138
Corporate 39 4
Personal 39 3
You can wrap your query and use a window function:
select
t.*,
(100.0 * [Number of sales])
/(sum([Number of sales]) over(partition by [Week of the year])
[Percent of Total Sales]
from (
select
c.customerType as [Customer Type],
datepart(week, o.orderDate) as [Week of the year],
count(c.customerType) as [Number of sales],
from [dbo].[Order] o
join Customer c ON c.id = o.customerId
group by c.customerType, datepart(week, o.orderDate), datepart(year, o.orderDate)
) t
Notes:
in SQLServer, better use brackets than quotes to define identifiers (quotes are usually reserved to strings)
the .0 in 100.0 is important : it forces SQLServer to perform decimal division (by default it would go for integer division, which is not what you want)
I added the year to the definition of the group; if your data spreads over several year, you probably don't want the same week in different years to be counted together
Side note: SQLServer is quite flexible about mixing window functions and aggregation. So this might also work:
select
c.customerType as [Customer Type],
datepart(week, o.orderDate) as [Week of the year],
count(c.customerType) as [Number of sales],
(100.0 * count(c.customerType))
/ (sum(count(c.customerType)) over(partition by datepart(week, o.orderDate)))
as [Percent of Total Sales]
from [dbo].[Order] o
join Customer c ON c.id = o.customerId
group by c.customerType, datepart(week, o.orderDate), datepart(year, o.orderDate)

Comparing date in subquery prevents SQL Server from using index

I'm trying to filter only events that happened within 30 days after the company was created (RegisteredUtc).
Does anyone know how to rewrite this query to make sure that predicate uses the index that exists?
I've tried both with an index that has (eventtype, timeutc) and includes (companyid) and one that has (eventtype) and includes (companyid, timeutc).
This is the query that does what I want. NOTE: between these 3 queries it's only the last predicate in the first where clause that changes.
select
DATEPART(year, q.RegisteredUtc) as [year],
DATEPART(month, q.RegisteredUtc) as [month],
Count(*) as [Count]
from
(select
c.Id,
c.RegisteredUtc,
(select count(*)
from dbo.events sc
where sc.companyId = c.Id
and sc.eventtype = 'CreateInvoice'
and sc.TimeUtc < DATEADD(day, 30, c.RegisteredUtc)) as [Count]
from
dbo.companies c) as q
where
q.Count > 0
group by
DATEPART(year, q.RegisteredUtc), DATEPART(month, q.RegisteredUtc)
But it's very slow because of the and TimeUtc < DATEADD(day, 30, c.RegisteredUtc). Without that it uses the indexes and runs very fast but with that predicate there it does a Eager spool that is very expensive instead of using the index.
There are actually indexes available. Confirmed by simply swapping DATEADD(day, 30, c.RegisteredUtc)with a constant which made the query fast again.
This is also slow.
select
DATEPART(year, q.RegisteredUtc) as [year],
DATEPART(month, q.RegisteredUtc) as [month],
Count(*) as [Count]
from
(select
c.Id,
c.RegisteredUtc,
(select count(*)
from dbo.events sc
where sc.companyId = c.Id
and sc.eventtype = 'CreateInvoice'
and sc.TimeUtc < c.RegisteredUtc) as [Count]
from
dbo.companies c) as q
where
q.Count > 0
group by
DATEPART(year, q.RegisteredUtc), DATEPART(month, q.RegisteredUtc)
This query hits the index and works fast though but of course doesn't produce the correct results.
select
DATEPART(year, q.RegisteredUtc) as [year],
DATEPART(month, q.RegisteredUtc) as [month],
Count(*) as [Count]
from
(select
c.Id,
c.RegisteredUtc,
(select count(*)
from dbo.events sc
where sc.companyId = c.Id
and sc.eventtype = 'CreateInvoice'
and sc.TimeUtc = c.RegisteredUtc) as [Count]
from
dbo.companies c) as q
where
q.Count > 0
group by
DATEPART(year, q.RegisteredUtc), DATEPART(month, q.RegisteredUtc)
As I see it you are not even using the inner count
If it matches the join then the count > 0
select DATEPART(year, q.RegisteredUtc) as [year],
DATEPART(month, q.RegisteredUtc) as [month],
Count(*) as [Count]
from ( select distinct c.Id, c.RegisteredUtc
from dbo.companies c
join dbo.events sc
on sc.companyId = c.Id
and eventtype = 'CreateInvoice'
and TimeUtc < DATEADD(day, 30, c.RegisteredUtc)
) as q
group by DATEPART(year, q.RegisteredUtc), DATEPART(month, q.RegisteredUtc)
where exists may work better. Your query is confusing to me. You get a count just to compare it to > 0 and you are worried about not using an in index?
It might be worthwhile to compute the DATEADD(day, 30, c.RegisteredUtc) in the select and then compare TimeUtc with the computed column, in other words:
select
c.Id,
c.RegisteredUtc,
DATEADD(day, 30, c.RegisteredUtc) as RegisteredUtc30
(
select count(*)
from dbo.events sc
where sc.companyId = c.Id
and sc.eventtype = 'CreateInvoice'
and sc.TimeUtc < RegisteredUtc30
from dbo.companies c

Sql query to get default value as 0 for a particular column

I am facing a issue with a query.
My query is :-
SELECT MONTH(o.OrderDate) as MonthValue,
YEAR(o.OrderDate) as YearValue,
C.CustomerTypeID, Count(o.Total) as NoOfOrders
FROM Orders o
RIGHT JOIN Customers C on C.CustomerID = o.CustomerID
WHERE o.OrderDate >= CONVERT(DATETIME, '1/1/2013 00:00:00 AM')
AND o.OrderDate <= CONVERT(DATETIME, '12/31/2013 23:59:59 PM')
GROUP BY MONTH(o.OrderDate),
YEAR(o.OrderDate),
C.CustomerTypeID
ORDER BY MONTH(o.OrderDate),
YEAR(o.OrderDate),
C.CustomerTypeID
It is giving result as follows :-
MonthValue YearValue CustomerTypeID NoOfOrders
1 2013 1 10
1 2013 2 20
1 2013 3 45
2 2013 1 45
2 2013 2 45
3 2013 1 88
3 2013 2 56
3 2013 3 89
As for month 2, customer type 3 has no result, so it is not appears in result.
But I want to show "0" as a default result for it, like below :-
2 2013 3 0
Thanks in advance.
Try this :
SELECT 2013 as [Year],
months.number,
Amount = SUM(COALESCE(o.Total,0)),
C.CustomerType
FROM Customers C
CROSS JOIN
(SELECT number FROM master..spt_values WHERE type='p' and number between 1 and 12) months
LEFT JOIN [Orders] o on C.CustomerId = o.CustomerId and YEAR(o.OrderDate) = 2013 and MONTH(o.OrderDate) = months.number
GROUP BY months.number, C.CustomerType
ORDER BY months.number, C.CustomerType
EDITED: You must outer join all months do get the missing entries. So for best readability cross join all customers and months first and then outer join the orders.
SELECT all_months.MonthValue,
all_months.YearValue,
C.CustomerTypeID,
Count(o.Total) as NoOfOrders
FROM
(
SELECT distinct MONTH(OrderDate) as MonthValue, YEAR(OrderDate) as YearValue
FROM orders
WHERE YEAR(OrderDate) = 2013
) all_months
CROSS JOIN Customers C
LEFT OUTER JOIN Orders o
ON o.CustomerID = C.CustomerID
AND MONTH(o.OrderDate) = all_months.MonthValue
AND YEAR(o.OrderDate) = all_months.YearValue
GROUP BY all_months.MonthValue,
all_months.YearValue,
C.CustomerTypeID
ORDER BY all_months.MonthValue,
all_months.YearValue,
C.CustomerTypeID ;
Well you could probably do a full outer join which should give you all of the customers, retuning null for the missing data. You could then select ISNULL(NoOfOrders, 0) to get 0 instead of a null.
I'm not 100& sure that would work, but you could give it a try.

Resources