FInd rows with 0 count from group by sql - sql

I have a select query to select an ID and corresponding count and group them based on ID and this is the sql for that
SELECT InID,
COUNT(*) as ICount
FROM RawData
WHERE CompletedDate>= #BeginDate AND CompletedDate<= #EndDate
AND InID in (3851,4151,11)
GROUP BY InID
This returns only one record for me
4151 225
To find the missing entries ie 3851 and 11 , i tried the query
SELECT InID,
COUNT(*) as ICount
FROM RawData
WHERE CompletedDate>= #BeginDate AND CompletedDate<= #EndDate
AND InID in (3851,4151,11)
GROUP BY InID
HAVING COUNT(*)=0
But it returned 0 records. So to check the IDs with missing records in a group by what is the proper way

This won't return you a row if there are no records for the specified Id's That satisfy the condition in the Where Clause. You Can try this Logic
;WITH CTE
AS
(
SELECT MyId = 3851
UNION ALL
SELECT 4151
UNION ALL
SELECT 11
)
SELECT
CTE.MyId,
COUNT(1)
FROM CTE
LEFT JOIN RawData RD
ON CTE.MyId = RD.InID
WHERE CompletedDate>= #BeginDate
AND CompletedDate<= #EndDate
GROUP BY CTE.MyId
Or Simply this will also work for you
select
InID,
SUM(CASE WHEN CompletedDate>= #BeginDate
AND CompletedDate<= #EndDate THEN 1 ELSE 0 END) as ICount
FROM RawData
whereInID in (3851,4151,11)
group by InID
To Filter records with Zero count
select
InID,
SUM(CASE WHEN CompletedDate>= #BeginDate
AND CompletedDate<= #EndDate THEN 1 ELSE 0 END) as ICount
FROM RawData
whereInID in (3851,4151,11)
group by InID
having SUM(CASE WHEN CompletedDate>= #BeginDate
AND CompletedDate<= #EndDate THEN 1 ELSE 0 END) = 0

TRY THIS: I think you can achieve your desired output through OUTER APPLY as below:
SELECT InID, rd1.ICount
FROM RawData rd
OUTER APPLY (SELECT COUNT(*) ICount
FROM RowDate rd1
WHERE rd1.InID = rd.InID
AND rd1.CompletedDate>= #BeginDate
AND rd1.CompletedDate<= #EndDate) rd1
WHERE InID IN (3851,4151,11)
GROUP BY InID
IDs those doesn't have records will display NULL.

Using LEFT JOIN:
SELECT r.InID, ISNULL(c.ICount, 0) as ICount
FROM RawData r
LEFT JOIN (
select InID, COUNT(*) as ICount
FROM RawData
where CompletedDate>= #BeginDate AND CompletedDate<= #EndDate
and InID in (3851,4151,11)
group by InID
) c ON c.InID = r.InID
WHERE r.InID in (3851,4151,11)
Using OUTER APPLY:
SELECT r.InID, ISNULL(c.ICount, 0) as ICount
FROM RawData r
OUTER APPLY (
select COUNT(*) as ICount
FROM RawData
where CompletedDate>= #BeginDate AND CompletedDate<= #EndDate
and InID = r.InID
) c
WHERE r.InID in (3851,4151,11)

You need to do a left join from a resultset that contains all the values you want to include
select ids.Id, count(InId)
from
(select 3851 as Id union select 4151 union select 11) ids
left join
RawData
on ids.id = rawdata.inid
where CompletedDate>= #BeginDate AND CompletedDate<= #EndDate
group by Ids.id

Related

How to get the sum of values in a table interval?

The question is very simple :-). I'm a beginner.
tables
Declare #startDate date,
Declare #endDate date
Select
d.ID,
d.Date as DateDocumet,
dt.id As TypeDocument,
p.Name as ProductName,
p.Price as Price,
d.qty
from Documents d
LEFT join product p on d.ProductId = p.id
LEFT join DocumentType dt on d.DocumentTypeId = dt.id
Result:
A taskā€¦..
There are two date variables.
How to get the sum of values(qty) between dates(#startDate - #endDate).
How to get the sum of values(qty) up to #startDate.
How get the sum of values(qty) down to #endDate.
If DocumentType is 1. Then the value(qty) minus.
Looks like you need conditional aggregation SUM(CASE WHEN....
CROSS APPLY also makes it easier to pre-calculate the negative qty
Declare #startDate date;
Declare #endDate date;
Select
SUM(CASE WHEN d.Date < #startDate THEN QtyToSum END),
SUM(CASE WHEN d.Date >= #startDate AND d.Date < #endDate THEN QtyToSum END),
SUM(CASE WHEN d.Date >= #endDate THEN QtyToSum END),
from Documents d
CROSS APPLY (VALUES (CASE WHEN d.DocumentTypeId = 1 THEN -d.qty ELSE d.qty END) ) AS v(QtyToSum)

How do I include records in a Summary query to include those that don't have data?

I have a query on a transaction table that returns the Summarized total on a column for each ID based on a data range. The query works great except it doesn't include those IDs that don't have data in the transaction table. How can I include those IDs in my result filled with a zero total. Here's a simplified version of my query.
SELECT tblID.IDName
,SUM(CASE
WHEN tblTransactions.idxTransType = 30
THEN CAST(tblTransactions.TimeAmount AS FLOAT) / 60.0
ELSE 0
END) AS 'Vacation'
FROM tblTransactions
INNER JOIN tblTransTypes ON tblTransactions.idxTransType = tblTransTypes.IdxTransType
INNER JOIN tblID ON tblTransactions.idxID = tblID.IdxID
WHERE (tblTransactions.Deleted = 0)
AND (tblTransactions.NotCurrent = 0)
AND (tblTransactions.TransDate >= CONVERT(DATETIME, 'March 1, 2018', 102))
AND (tblTransactions.TransDate <= CONVERT(DATETIME, 'April 11, 2018', 102))
GROUP BY tblID.IDName
Actually it's slightly more complicated than that:
SELECT
i.IDName,
SUM(CASE WHEN t.idxTransType = 30 THEN CAST(t.TimeAmount AS FLOAT) / 60.0 ELSE 0 END) AS 'Vacation'
FROM
tblID i
LEFT JOIN tblTransactions t ON t.idxID = i.IdxID AND t.Deleted = 0 AND t.NotCurrent = 0 AND t.TransDate BETWEEN '20180301' AND '20180411'
LEFT JOIN tblTransTypes tt ON tt.IdxTransType = t.idxTransType
GROUP BY
i.IDName;
You want left joins:
SELECT i.IDName,
SUM(CASE WHEN t.idxTransType = 30 THEN CAST(t.TimeAmount AS Float) / 60.0 ELSE 0 END) AS Vacation
FROM tblID i LEFT JOIN
tblTransactions t
ON t.idxID = i.IdxID AND
t.Deleted = 0 AND
t.NotCurrent = 0 AND
t.TransDate >= '2018-03-01' AND
t.TransDate <= '2018-04-11'
tblTransTypes tt
ON t.idxTransType = tt.IdxTransType
GROUP BY i.IDName;
Notes:
Table aliases make the query much easier to write and to read.
Use ISO/ANSI standard date formats.
The filter conditions on all but the first table belong in the ON clauses.

sql join and group by generated date range

I have Table1 and I need a query to populate Table2:
Problem here is with Date column. I want to know the process of location/partner combination per day. Main issue here is that I can't pick DateCreated and make it as default date since it doesn't necessarily cover whole date range, like in this example where it doesn't have 2015-01-07 and 2015-01-09. Same case with other dates.
So, my idea is to first select dates from some table which contains needed date range and then perform calculation for each day/location/partner combination from cte but in that case I can't figure out how to make a join for LocationId and PartnerId.
Columns:
Date - CreatedItems - number of created items where Table1.DateCreated = Table2.Date
DeliveredItems - number of delivered items where Table1.DateDateOut = Table2.Date
CycleTime - number of days delivered item was in the location (DateOut - DateIn + 1)
I started with something like this but it's very like that I completely missed the point with it:
with d as
(
select date from DimDate
where date between DATEADD(DAY, -365, getdate()) and getdate()
),
cr as -- created items
(
select
DateCreated,
LocationId,
PartnerId,
CreatedItems = count(*)
from Table1
where DateCreated is not null
group by DateCreated,
LocationId,
PartnerId
),
del as -- delivered items
(
select
DateOut,
LocationId,
ParnerId,
DeliveredItems = count(*),
CycleTime = DATEDIFF(Day, DateOut, DateIn)
from Table1
where DateOut is not null
and Datein is not null
group by DateOut,
LocationId,
PartnerId
)
select
d.Date
from d
LEFT OUTER JOIN cr on cr.DateCreated = d.Date -- MISSING JOIN PER LocationId and PartnerId
LEFT OUTER JOIN del on del.DateCompleted = d.Date -- MISSING JOIN PER LocationId and PartnerId
with range(days) as (
select 0 union all select 1 union all select 2 union all
select 3 union all select 4 union all select 5 union all
select 6 /* extend as necessary */
)
select dateadd(day, r.days, t.DateCreated) as "Date", locationId, PartnerId,
sum(
case
when dateadd(day, r.days, t.DateCreated) = t.DateCreated
then 1 else 0
end) as CreatedItems,
sum(
case
when dateadd(day, r.days, t.DateCreated) = t.Dateout
then 1 else 0
end) as DeliveredItems,
sum(
case
when dateadd(day, r.days, t.DateCreated) = t.Dateout
then datediff(days, t.DateIn, t.DateOut) + 1 else 0
end) as CycleTime
from
<yourtable> as t
inner join range as r
on r.days between 0 and datediff(day, t.DateCreated, t.DateOut)
group by dateadd(day, r.days, t.DateCreated), LocationId, PartnerId;
If you only want the end dates (rather than all the dates in between) this is probably a better approach:
with range(dt) as (
select distinct DateCreated from T union
select distinct DateOut from T
)
select r.dt as "Date", locationId, PartnerId,
sum(
case
when r.dt = t.DateCreated
then 1 else 0
end) as CreatedItems,
sum(
case
when r.dt = t.Dateout
then 1 else 0
end) as DeliveredItems,
sum(
case
when r.dt = t.Dateout
then datediff(days, t.DateIn, t.DateOut) + 1 else 0
end) as CycleTime
from
<yourtable> as t
inner join range as r
on r.dt in (t.DateCreated, t.DateOut)
group by r.dt, LocationId, PartnerId;
If to specify WHERE clause? Something Like that:
WHERE cr.LocationId = del.LocationId AND
cr.PartnerId = del.PartnerId

Calculate total business working days between two dates

select count(distinct(dateadd(d, 0, datediff(d, 0,checktime)))) as workingdays
from departments,
dbo.USERINFO INNER JOIN dbo.CHECKINOUT ON
dbo.USERINFO.USERID = dbo.CHECKINOUT.USERID
where userinfo.name='Gokul Gopalakrishnan' and deptname='GEN/SUP-TBL'
and checktime>='2014-05-01' and checktime<='2014-05-30'
from the above code I am able to find total working days of employee between two dates.
workingdays
20
but now I want other column name total business days. I want to calculate total business days between two dates.
workingdays businessdays
20 21
how can i do this?
If you only want to exclude weekends then you can simply just exclude these using a conditional count by adding:
count(distinct case when datepart(weekday, getdate()) <= 5 then date end)
So your query becomes:
set datefirst 1;
select count(distinct(dateadd(d, 0, datediff(d, 0,checktime)))) as workingdays,
count(distinct case when datepart(weekday, getdate()) <= 5
then dateadd(d, 0, datediff(d, 0,checktime))
end) as weekdays
from departments,
dbo.USERINFO INNER JOIN dbo.CHECKINOUT ON
dbo.USERINFO.USERID = dbo.CHECKINOUT.USERID
where userinfo.name='Gokul Gopalakrishnan' and deptname='GEN/SUP-TBL'
and checktime>='2014-05-01' and checktime<='2014-05-30'
HOWEVER I would really recommend adding a calendar table to your database. It makes everything so easy, your query would become:
SELECT DaysWorked = COUNT(cio.Date),
WeekDaysWorked = COUNT(CASE WHEN c.IsWeekDay = 1 THEN cio.Date END),
WorkingDaysWorked = COUNT(CASE WHEN c.IsWorkingDay = 1 THEN cio.Date END),
TotalDays = COUNT(*),
TotalWeekDays = COUNT(CASE WHEN c.IsWeekDay = 1 THEN 1 END),
TotalWorkingDays = COUNT(CASE WHEN c.IsWorkingDay = 1 THEN 1 END)
FROM dbo.Calender AS c
LEFT JOIN
( SELECT DISTINCT
Date = CAST(CheckTime AS DATE)
FROM dbo.Departments AS d
CROSS JOIN dbo.userInfo AS ui
INNER JOIN dbo.CheckInOut AS cio
ON cio.UserID = ui.UserID
WHERE ui.Name = 'Gokul Gopalakrishnan'
AND d.deptname = 'GEN/SUP-TBL'
) AS cio
ON c.Date = cio.Date
WHERE d.Date >= '2014-05-01'
AND d.Date <= '2014-05-30';
This way you can define public holidays, weekends, etc. It is so much more flexible than any other solution.
EDIT
I think I misunderstood your original criteria. This should work for you with no calendar table:
SET DATEFIRST 1;
DECLARE #StartDate DATE = '2014-05-01',
#EndDate DATE = '2014-05-30';
DECLARE #Workdays INT =
(DATEDIFF(DAY, #StartDate, #EndDate) + 1)
-(DATEDIFF(WEEK, #StartDate, #EndDate) * 2)
-(CASE WHEN DATEPART(WEEKDAY, #StartDate) = 7 THEN 1 ELSE 0 END)
-(CASE WHEN DATEPART(WEEKDAY, #EndDate) = 6 THEN 1 ELSE 0 END);
SELECT WorkingDays = COUNT(DISTINCT CAST(CheckTime AS DATE)),
BusinessDays = #Workdays
FROM dbo.Departments AS d
CROSS JOIN dbo.userInfo AS ui
INNER JOIN dbo.CheckInOut AS cio
ON cio.UserID = ui.UserID
WHERE ui.Name = 'Gokul Gopalakrishnan'
AND d.deptname = 'GEN/SUP-TBL'
AND cio.CheckTime >= #StartDate
AND cio.CheckTime <= #EndDate;
following query calculate Fridays count between #FromDate and #ToDate variable
((DATEDIFF(DAY,#FromDate,#ToDate)-(6-DATEPART(dw,#FromDate)))/7)*2
Following query calculate Working day count and business day count between to date :
DECLARE #FromDate DATE = '2014-05-01',
#ToDate DATE = '2014-05-30'
SELECT COUNT(DISTINCT CAST(checktime AS Date)) as workingdays,
DATEDIFF(DAY,#FromDate,#ToDate) -
((DATEDIFF(DAY,#FromDate,#ToDate)-(6-DATEPART(dw,#FromDate)))/7)*2 AS BusinessDay
from departments,
dbo.USERINFO INNER JOIN dbo.CHECKINOUT ON
dbo.USERINFO.USERID = dbo.CHECKINOUT.USERID
where userinfo.name='Gokul Gopalakrishnan' and deptname='GEN/SUP-TBL'
and checktime>= #FromDate and checktime<=#ToDate

Duplicates from an SQL Query

I have a dataset I retrieve from multiple joins. I have used SELECT DISTINCT in my statements but I still see duplicates in the result set. Here is the code:
SELECT DISTINCT Account
, PayoffAmtDOL as 'Payoff Amount DOL'
, PayoffAmtLOG as 'Payoff Amount LOG'
, PayoffAmountLive as 'Payoff Amount Live'
, [Difference]
, PrincipalBalance as 'Principal Balance'
, CreationDate as 'Date Entered System'
, CACSState as 'CACS State at Entry'
, PaymentsMade AS 'Payments Made'
, TotalPaymentAmount as 'Total Payment Amount'
, 'Liquidation Percentage' = CASE WHEN PayoffAmountLive = 0 THEN 1
WHEN ISNULL([Difference],0) = ISNULL(PayoffAmtDOL, 0) THEN 1
WHEN ISNULL([Difference],0) < 0 AND ISNULL(PayoffAmtDOL, 0) > 0 THEN 0
WHEN ISNULL([Difference],0) > 0 AND ISNULL(PayoffAmtDOL, 0) < 0 THEN 1
WHEN ISNULL([Difference],0) > ISNULL(PayoffAmtDOL, 0) THEN 1
WHEN [Difference] > 0 AND ISNULL(PayoffAmtDOL, 0) = 0 THEN 1
WHEN ISNULL(PayoffAmtDOL, 0) = 0 THEN 0
ELSE ISNULL([Difference],0)/ISNULL(PayoffAmtDOL, 0) END
, Cnt = 1
FROM
(
SELECT DISTINCT a.Account,
c.PayoffAmtDOL,
c.PayoffAmtLOG,
(ISNULL(c.PayoffAmtCACS, cacs.payoff_amt)) as 'PayoffAmountLive',
(ISNULL(c.PayoffAmtDOL, 0) - (ISNULL(c.PayoffAmtCACS , ISNULL(cacs.payoff_amt, 0)))) as 'Difference',
c.PrincipalBalance,
c.CreationDate,
c.CACSState,
(SELECT COUNT(PaymentID)
FROM tblATLPaymentInfo p
WHERE p.AccountID = a.AccountID
AND CONVERT(DATETIME, CONVERT(VARCHAR(10), p.CreationDate, 101)) >= '1/1/2014'
AND CONVERT(DATETIME, CONVERT(VARCHAR(10), p.CreationDate, 101)) <= '3/27/2014'
) as 'PaymentsMade',
(SELECT SUM(PaymentAmount)
FROM tblATLPaymentInfo p
WHERE p.AccountID = a.AccountID
AND CONVERT(DATETIME, CONVERT(VARCHAR(10), p.CreationDate, 101)) >= '1/1/2014'
AND CONVERT(DATETIME, CONVERT(VARCHAR(10), p.CreationDate, 101)) <= '3/27/2014'
) as 'TotalPaymentAmount'
FROM tblATLAcctInfo a
RIGHT JOIN tblATLClaimInfo c
ON c.AccountID = a.AccountID
LEFT JOIN SCFLOKYDCMSQL03.CACS_DM.dbo.Cacs_Info cacs
ON cacs.Account = a.Account
WHERE CONVERT(DATETIME, CONVERT(VARCHAR(10), c.CreationDate, 101)) >= '1/1/2014'
AND CONVERT(DATETIME, CONVERT(VARCHAR(10), c.CreationDate, 101)) <= '3/27/2014'
AND c.ClaimTypeID = (SELECT DISTINCT ClaimTypeID FROM tblATLClaimType WHERE ClaimType = 'N02 - Claims')
) a
ORDER BY Account
Here is an example of the duplicate rows:
AccountID DateEntered
123 01/19/2014
123 01/21/2014
345 02/1/2014
345 02/10/2014
The difference between appears to be the date entered. Maybe selecting the Row_Number() and then deleting the later date could be a solution
DISTINCT should not return multiple rows.. there should be at least one column that is different in each row, no? With character data, sometimes one can be fooled by non-visible differences, such as trailing spaces. Not sure if that is the case here, though.
Can you give an example of the duplicate rows?
OK, I see your edit. You have to select which of the dates to display. Try this to get the earliest date per AccountID:
SELECT AccountID, MIN(DateEntered) AS DateEntered
FROM ....
GROUP BY AccountID
ORDER BY AccountID
You can add more columns in the SELECT, as long as they are distinct you will not get more rows.
If you want, you can add COUNT(*) to the select to get the number of rows grouped.
DISTINCT will only reject lines that are exact duplicates, the DateEntered is different on each ID. If you want the latest, use Max(DateEntered)