SQL Columns Subquery with different criteria - sql

Lets say a have two tables, [Products] and [Quantiy].
I need to select a join in both in order to built a table with total Quantity per product.
I know who to do this in a vanilla join. My problem is that i need 3 columns one [PreviewTotal] where I should have this total quantity since ever till yesterday no matter sign of quantity other [TodaytotalPos] with quantity for today and positive and other [TodaytotalNeg] only today and negative.
result like:
[Products] [PreviewTotal] [TodaytotalPos] [TodaytotalNeg]
AAPL 20,000 500 -700
MCD 15,000 NULL -300
BAC -30,000 2,000 NULL
Sample of structure:
Producst:
[id] [name]
1 AAPL
2 MCD
3 BAC
Quantity:
[date] [Id_Product] [Quantity]
12/16 1 500
12/16 2 -300
12/17 1 1,000
12/18 3 5,500
12/18 1 -2,000

Based on the rules you specify, I think you are just looking for conditional aggregation. Your query would look something like this:
select p.name,
sum(case when [date] < cast(getdate() as date) then quantity end) as PreviewTotal,
sum(case when [date] = cast(getdate() as date) and quantity > 0
then quantity end) as TodayTotalPos,
sum(case when [date] = cast(getdate() as date) and
quantity < 0 then quantity end) as TodayTotNeg
from products p join
quantity q
on q.id_product = p.id
group by p.name
order by p.name;
However, your desired results don't match the input data, based on these rules.

In SQL Server you can use CASE to select a field value if a condition is true or false. I assume your [Products] table contains at least ProductId and ProductName, and your [Quantity] table contains at least ProductId, Qty, and SoldDate.
-- get the time of midnight, the start of today:
DECLARE Today datetime;
SELECT DATEADD(DAY, DATEDIFF(DAY, '19000101', GETDATE()), '19000101') INTO Today
-- get the totals
SELECT Products.ProductName,
SUM(
CASE
WHEN Quantity.SoldDate >= Today THEN Quantity.Qty
ELSE 0
END) AS PreviewTotal,
SUM(
CASE
WHEN Quantity.Qty> 0 AND Quantity.SoldDate >= Today) THEN Quantity.Qty
ELSE 0
END) AS TodaytotalPos,
SUM(
CASE
WHEN Quantity.Qty< 0 AND Quantity.SoldDate >= Today THEN Quantity.Qty
ELSE 0
END) AS TodaytotalNeg
FROM Products JOIN Quantity on Products.ProductId = Quantity.ProductId
GROUP BY ProductName

Related

Difference between COUNT and SUM within an Aggregate CASE statement

Full disclosure, I am learning and I have searched all over the internet and I just can't figure out my question.
I am working on an online class and was given the following example:
select
DATENAME(MONTH, DATEADD(MONTH, MONTH(OrderDate), -1)) AS 'Month',
SUM(CASE WHEN YEAR(OrderDate) = 2005 THEN 1 ELSE 0 END) AS Orders,
SUM(CASE YEAR(OrderDate) WHEN 2005 THEN Totaldue ELSE 0 END) AS 'Total Value'
from
sales.salesorderheader
group by Month(orderdate)
order by Month(orderdate) ASC
That returns the following results:
I understood that (I thought) so I began messing around with the code to further understand Case statements. Looking at the code I thought that the Orders field was essentially finding all the orders in a month, assigning a 1 to each one, and then adding them all up. Because each one was assigned a 1 I figured that I could change the SUM to COUNT and I would get the same results.
However, this code:
select
DATENAME(MONTH, DATEADD(MONTH, MONTH(OrderDate), -1)) AS 'Month',
COUNT(CASE WHEN YEAR(OrderDate) = 2005 THEN 1 ELSE 0 END) AS Orders,
SUM(CASE YEAR(OrderDate) WHEN 2005 THEN Totaldue ELSE 0 END) AS 'Total Value'
from
sales.salesorderheader
group by Month(orderdate)
order by Month(orderdate) ASC
Returns these results:
To try and break this down I created a query that would just look for the orders in January 2005 and count them.
SELECT COUNT(*)
FROM Sales.SalesOrderHeader
WHERE OrderDate >= '1/1/2005' AND OrderDate < '1/1/2005'
This returned 0. The same as the SUM query. I get that COUNT counts rows and SUM sums numbers in a column, but I just don't understand the results I'm getting. Could someone please explain why the count query is returning 2483 for January and not 0?
For COUNT 1 and 0 are the same. What you really need is NULL:
COUNT(ALL expression) evaluates expression for each row in a group and returns the number of nonnull values.
select
DATENAME(MONTH, DATEADD(MONTH, MONTH(OrderDate), -1)) AS 'Month',
COUNT(CASE WHEN YEAR(OrderDate) = 2005 THEN 1 ELSE NULL END) AS Orders,
SUM(CASE YEAR(OrderDate) WHEN 2005 THEN Totaldue ELSE 0 END) AS 'Total Value'
from sales.salesorderheader
group by Month(orderdate)
order by Month(orderdate) ASC;
Or even shorter(default ELSE is NULL so we could omit that part)
COUNT(CASE WHEN YEAR(OrderDate) = 2005 THEN 1 END) AS Orders,
Example:
SUM COUNT COUNT
2005 1 1 1
2006 0 0 NULL
2007 0 0 NULL
2005 1 1 1
===============================================
2 4 2
When you use count(*) you count ALL the rows. If you want to count how many orders you have, you have to use a column: eg: count(OrderDate). Try it
count example:
assume that your column has 3 value and column name is the order
2 ---------- 5 ---------- 4----- Null
now if you run
count (order)
it will return = 3 how many entries you have in the column without null
sum example:
2 ---------- 5 ---------- 4
now if you run
sum (order)
it will return = 2+5+4=11 its add all the entries

Total Count of each

I have a problem with my SQL Server query.
My query must display the total number of each category. Not the total amount of all categories.
SELECT [CATEGORY],
(SELECT COUNT(*)
FROM [Group_New_DB].[dbo].[INCIDENTSM1]
WHERE ([OPEN_TIME] >= #StartDate and [OPEN_TIME] < #EndDate + 1) ) AS OpenedCount,
(SELECT COUNT(*)
FROM [Group_New_DB].[dbo].[INCIDENTSM1]
WHERE ([CLOSE_TIME] >= #StartDate and [CLOSE_TIME] < #EndDate + 1)) AS ClosedCount
FROM [Group_New_DB].[dbo].[INCIDENTSM1]
GROUP BY CATEGORY
ORDER BY CATEGORY
The report consists of a table with 3 columns: Category, Registered, Closed.
1 column is the category name.
2 column is how many categories have been registered.
3 column -> how many columns were closed.
But result
The result that turned out as a result does not look right.
I don't quite understand what is the difference between total number vs total amount as the question doesn't provide any context.
Although, if you are trying to get OpenedCount and ClosedCount as you have named the columns, I suggest you try below:
SELECT
CATEGORY,
SUM(CASE WHEN (OPEN_TIME >= #start_date AND OPEN_TIME < #end_date+1)
THEN 1
ELSE 0
END) AS OPENED_COUNT,
SUM(CASE WHEN (CLOSED_TIME >= #start_date AND CLOSED_TIME < #end_date+1)
THEN 1
ELSE 0
END) AS CLOSED_COUNT
FROM
[Group_New_DB].[dbo].[INCIDENTSM1]
GROUP BY
CATEGORY
ORDER BY
CATEGORY
IMO, This is also a better way as it doesn't include multiple sub-queries.
Correction: changed COUNT to SUM as suggested by TriV - Thanks!
Try this instead -
SELECT [CATEGORY],
COUNT(CASE WHEN [OPEN_TIME] >= #StartDate and [OPEN_TIME] < #EndDate + 1 THEN 1 ELSE NULL END) AS OpenedCount,
COUNT(CASE WHEN [CLOSE_TIME] >= #StartDate and [CLOSE_TIME] < #EndDate + 1 THEN 1 ELSE NULL END) AS ClosedCount
FROM [Group_New_DB].[dbo].[INCIDENTSM1] GROUP BY
CATEGORY ORDER BY CATEGORY

SQL: Create Multiple Selects Based One Date

I've been searching for help on this but I can't seem to find the solution. What I have is:
Table called Details_Orders.
Columns: CustomerName, InvoiceDate, SaleAmount and SaleCost.
What I want to do is select a date ('1-1-2015') and display the Customer Name, the Date ('1-1-2015'), the sum of SaleAmount for that Date, and the Sum of the SaleCost. And here's the part I'm having problems with, I want the next columns to display the previous months SaleAmount and SaleCost, then the previous YEARS SaleAmount and SaleCost.
I'm having trouble figuring out how to code the Previous Time Periods Select statements. Any help on this would be greatly appreciated.
Select
CustomerName,
InvoiceDate,
Sum(SaleAmount),
Sum(SaleCost),
*PREVIOUS MONTH Sum(SalesAmount) DATEADD(MONTH,-1,'1-1'2015'),
PREVIOUS MONTH Sum(SalesCost) DATEADD(MONTH,-1,'1-1'2015'),
PREVIOUS YEAR Sum(SalesAmount) DATEADD(YEAR,-1,'1-1'2015'),
PREVIOUS YEAR Sum(SalesCost) DATEADD(YEAR,-1,'1-1'2015')*
From Details_Orders
WHERE InvoiceDate='1-1-2015'
Basic
You want to use conditional aggregation, and also need a group by. It is a bit unclear what the exact definitions of previous month and year are. Here is one interpretation:
Select CustomerName,
Sum(case when InvoiceDate = thedate then SaleAmount else 0 end),
Sum(case when InvoiceDate = thedate then SaleCost else 0 end),
Sum(case when year(InvoiceDate)*12 + month(InvoiceDate) =
year(thedate)*12 + month(thedate) - 1
then SaleAmount else 0
end),
Sum(case when year(InvoiceDate)*12 + month(InvoiceDate) =
year(thedate)*12 + month(thedate) - 1
then SaleCost else 0
end),
Sum(case when InvoiceDate < thedate and
InvoiceDate >= dateadd(year, -1, thedate
then SaleAmount else 0
end),
Sum(case when InvoiceDate < thedate and
InvoiceDate >= dateadd(year, -1, thedate
then SaleCost else 0
end)
From (select cast('2015-01-01' as date) as thedate
) params cross join
Details_Orders o
group by CustomerName;

SQL Query: Including Two Sums based on different criteria from the same table

I currently have the following sql query
SELECT [Date], DATENAME(dw,[Date]) AS Day, SUM(Units) AS TotalUnits
FROM tblTimesheetEntries
WHERE UserID = 'PJW'
AND Date >= '2013-01-01'
GROUP BY [Date]
ORDER BY [Date] DESC;
Which returns the Total [Units] for a given user and date.
However I would like to produce two separate Total Units based on a new criteria i.e. whether or not the [Units] where 'Chargeable or Not'. There is a field in tblTimeSheets called Chargeable which is a boolean (true for chargeable, false for not). How do I incorporate this to show two Sums one for each type of Units?
You can use a CASE with the SUM() to calculate the separate totals:
SELECT [Date],
DATENAME(dw,[Date]) AS Day,
sum(case when Chargeable = 1 then Units else 0 end) ChargeableTotal,
sum(case when Chargeable = 0 then Units else 0 end) NotChargeableTotal,
sum(Units) AS TotalUnits
FROM tblTimesheetEntries
WHERE UserID = 'PJW'
AND Date >= '2013-01-01'
GROUP BY [Date]
ORDER BY [Date] DESC;

SQL statement to get record datetime field value as column of result

I have the following two tables
activity(activity_id, title, description, group_id)
statistic(statistic_id, activity_id, date, user_id, result)
group_id and user_id come from active directory. Result is an integer.
Given a user_id and a date range of 6 days (Mon - Sat) which I've calculated on the business logic side, and the fact that some of the dates in the date range may not have a statistic result for the particular date (ie. day1 and day 4 may have entered statistic rows for a particular activity, but there may not be any entries for days 2, 3, 5 and 6) how can I get a SQL result with the following format? Keep in mind that if a particular activity doesn't have a record for the particular date in the statistics table, then that day should return 0 in the SQL result.
activity_id group_id day1result day2result day3result day4result day5result day6 result
----------- -------- ---------- ---------- ---------- ---------- ---------- -----------
sample1 Secured 0 5 1 0 2 1
sample2 Unsecured 1 0 0 4 3 2
Note: Currently I am planning on handling this in the business logic, but that would require multiple queries (one to create a list of distinct activities for that user for the date range, and one for each activity looping through each date for a result or lack of result, to populate the 2nd dimension of the array with date-related results). That could end up with 50+ queries for each user per date range, which seems like overkill to me.
I got this working for 4 days and I can get it working for all 6 days, but it seems like overkill. Is there a way to simplify this?:
SELECT d1d2.activity_id, ISNULL(d1d2.result1,0) AS day1, ISNULL(d1d2.result2,0) AS day2, ISNULL(d3d4.result3,0) AS day3, ISNULL(d3d4.result4,0) AS day4
FROM
(SELECT ISNULL(d1.activity_id,0) AS activity_id, ISNULL(result1,0) AS result1, ISNULL(result2,0) AS result2
FROM
(SELECT ISNULL(statistic_result,0) AS result1, ISNULL(activity_id,0) AS activity_id
FROM statistic
WHERE user_id='jeremiah' AND statistic_date='11/22/2011'
) d1
FROM JOIN
(SELECT ISNULL(statistic_result,0) AS result2, ISNULL(activity_id,0) AS activity_id
FROM statistic WHERE user_id='jeremiah' AND statistic_date='11/23/2011'
) d2
ON d1.activity_id=d2.activity_id
) d1d2
FULL JOIN
(SELECT d3.activity_id AS activity_id, ISNULL(d3.result3,0) AS result3, ISNULL(d4.result4,0) AS result4
FROM
(SELECT ISNULL(statistic_result,0) AS result3, ISNULL(activity_id,0) AS activity_id
FROM statistic WHERE user_id='jeremiah' AND statistic_date='11/24/2011'
) d3
FULL JOIN
(SELECT ISNULL(statistic_result,0) AS result4, ISNULL(activity_id,0) AS activity_id
FROM statistic WHERE user_id='jeremiah' AND statistic_date='11/25/2011'
) d4
ON d3.activity_id=d4.activity_id
) d3d4
ON d1d2.activity_id=d3d4.activity_id
ORDER BY d1d2.activity_id
Here is a typical approach for this kind of thing:
DECLARE #minDate DATETIME,
#maxdate DATETIME,
#userID VARCHAR(200)
SELECT #minDate = '2011-11-15 00:00:00',
#maxDate = '2011-11-22 23:59:59',
#userID = 'jeremiah'
SELECT A.activity_id, A.group_id,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 0 THEN S.Result ELSE 0 END) AS Day1Result,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 1 THEN S.Result ELSE 0 END) AS Day2Result,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 2 THEN S.Result ELSE 0 END) AS Day3Result,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 3 THEN S.Result ELSE 0 END) AS Day4Result,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 4 THEN S.Result ELSE 0 END) AS Day5Result,
SUM(CASE WHEN DATEDIFF(day, #minDate, S.date) = 5 THEN S.Result ELSE 0 END) AS Day6Result
FROM activity A
LEFT OUTER JOIN statistic S
ON A.activity_id = S.activity_ID
AND S.user_id = #userID
WHERE S.date between #minDate AND #maxDate
GROUP BY A.activity_id, A.group_id
First, I'm using group by to reduce the resultset to one row per activity_id/group_id, then I'm using CASE to separate values for each individual column. In this case I'm looking at which day in the last seven, but you can use whatever logic there to determine what date. The case statements will return the value of S.result if the row is for that particular day, or 0 if it's not. SUM will add up the individual values (or just the one, if there is only one) and consolidate that into a single row.
You'll also note my date range is based on midnight on the first day in the range and 11:59PM on the last day of the range to ensure all times are included in the range.
Finally, I'm performing a left join so you will always have a 0 in your columns, even if there are no statistics.
I'm not entirely sure how your results are segregated by group in addition to activity (unless group is a higher level construct), but here is the approach I would take:
SELECT activity_id
day1result = SUM(CASE DATEPART(weekday, date) WHEN 1 THEN result ELSE 0 END)
FROM statistic
GROUP BY activity_id
I will leave the rest of the days and addition of group_id to you, but you should see the general approach.