correct count of grouped results - sql

I have a procedure:
ALTER PROCEDURE [dbo].[GetActualFeedbackQueueTree]
#dtNow datetime
as
BEGIN
select
count(f.Id) as [Total],
f.AccountCode,
f.AccountName,
f.Utc,
f2.CityCode,
f2.CityName
from
InnerPortal.Feedback.QueueFeedback f
left join
InnerPortal.Feedback.QueueFeedback f2
on
f2.AccountCode = f.AccountCode
where
(f.Done is null or f.Done = 0) and
(f.Busy is NULL or f.Busy = 0) and
((DATEPART(hour, DATEADD(HOUR, f.Utc, #dtNow)) >= 9 ) and
(DATEPART(hour, DATEADD(HOUR, f.Utc, #dtNow)) <= 20))
group by
f.AccountCode, f2.CityCode,
f2.CityName, f.AccountName, f.Utc
END
I group rows by AccountName and by CityName. As result we have something like a tree. The problem is the [Total] not calculates correctly.
Then I get a select for a special AccountCode the count if much less then get me as result the procedure. For example:
select count(f.Id) from Feedback.QueueFeedback f where f.AccountCode = '01507'
returns 16 rows but the procedure result is 256.
The target is to get a count of collected rows with the same account. How to make it work correctly?
Thanks.
Software: T-Sql, Ms Sql server 2012

Pretty sure you want
count(distinct(f.Id))

Related

Move Functions from Where Clause to Select Statement

I have a union query that runs abysmally slow I believe mostly because there are two functions in the where clause of each union. I am pretty sure that there is no getting around the unions, but there may be a way to move the functions from the where of each. I won't post ALL of the union sections because I don't think it is necessary as they are all almost identical with the exception of one table in each. The first function was created by someone else but it takes a date, and uses the "frequency" value like "years, months, days, etc." and the "interval" value like 3, 4, 90 to calculate the new "Due Date". For instance, a date of today with a frequency of years, and an interval of 3, would produce the date 4/21/2025. Here is the actual function:
ALTER FUNCTION [dbo].[ReturnExpiration_IntervalxFreq](#Date datetime2,#int int, #freq int)
RETURNS datetime2
AS
BEGIN
declare #d datetime2;
SELECT #d = case when #int = 1 then null-- '12-31-9999'
when #int = 2 then dateadd(day,#freq,#date)
when #int = 3 then dateadd(week,#freq,#date)
when #int = 4 then dateadd(month,#freq,#date)
when #int = 5 then dateadd(quarter,#freq,#date)
when #int = 6 then dateadd(year,#freq,#date)
end
RETURN #d;
The query itself is supposed to find and identify records whose Due Date has past or is within 90 days of the current date. Here is what each section of the union looks like
SELECT
R.RequirementId
, EC.EmployeeCompanyId
, EC.CompanyId
, DaysOverdue =
CASE WHEN
R.DueDate IS NULL
THEN
CASE WHEN
EXISTS(SELECT 1 FROM tbl_Training_Requirement_Compliance RC WHERE RC.EmployeeCompanyId = EC.EmployeeCompanyId AND RC.RequirementId = R.RequirementId AND RC.Active = 1 AND ((DATEDIFF(DAY, R.DueDate, GETDATE()) > -91 OR R.DueDate Is Null ) OR (DATEDIFF(DAY, dbo.ReturnExpiration_IntervalxFreq(TRC.EffectiveDate, R.IntervalId, R.Frequency), GETDATE()) > -91)) OR R.IntervalId IS NULL)
THEN
DateDiff(day,ISNULL(dbo.ReturnExpiration_IntervalxFreq(TRC.EffectiveDate, R.IntervalId, R.Frequency), '12/31/9999'),getdate())
ELSE
0
END
ELSE
DATEDIFF(day,R.DueDate,getdate())
END
,CASE WHEN
EXISTS(SELECT 1 FROM tbl_Training_Requirement_Compliance RC WHERE RC.EmployeeCompanyId = EC.EmployeeCompanyId AND RC.RequirementId = R.RequirementId AND RC.Active=1 AND (GETDATE() > dbo.ReturnExpiration_IntervalxFreq(RC.EffectiveDate, R.IntervalId, R.Frequency) OR R.IntervalId IS NULL))
THEN
CONVERT(VARCHAR(12),dbo.ReturnExpiration_IntervalxFreq(TRC.EffectiveDate, R.IntervalId, R.Frequency), 101)
ELSE
CONVERT(VARCHAR(12),R.DueDate,101)
END As DateDue
FROM
#Employees AS EC
INNER JOIN dbo.tbl_Training_Requirement_To_Position TRP ON TRP.PositionId = EC.PositionId
INNER JOIN #CompanyReqs R ON R.RequirementId = TRP.RequirementId
LEFT OUTER JOIN tbl_Training_Requirement_Compliance TRC ON TRC.EmployeeCompanyId = EC.EmployeeCompanyId AND TRC.RequirementId = R.RequirementId AND TRC.Active = 1
WHERE
NOT EXISTS(SELECT 1
FROM tbl_Training_Requirement_Compliance RC
WHERE RC.EmployeeCompanyId = EC.EmployeeCompanyId
AND RC.RequirementId = R.RequirementId
AND RC.Active = 1
)
OR (
(DATEDIFF(DAY, R.DueDate, GETDATE()) > -91
OR R.DueDate Is Null )
OR (DATEDIFF(DAY, dbo.ReturnExpiration_IntervalxFreq(TRC.EffectiveDate, R.IntervalId, R.Frequency), GETDATE()) > -91))
UNION...
It is supposed to exclude records that either don't exist at all on the tbl_Training_Requirement_Compliance table, or if they do exist, once the frequency an intervals have been calculated, would have a new due date that is within 90 days of the current date. I am hoping that someone with much more experience and expertise in SQL Server can show me a way, if possible, to remove the functions from the WHERE clause and help the performance of this stored procedure.

My SQL - How to run different WHERE statements on different weekdays, like one for monday and another for friday

I missunderstod this a lot and thanks again for all the help.
I eventually found a solution for my case and it didn't even involve anything like
IF Monday THEN SELECT
I didn't even needed a CASE-statement.
What I had to do was to put different WHERE statements with an OR in between.
I have stated my final code bolow:
SELECT * FROM
(SELECT
t.pay_date
, t.supp_name
, t.client AS row_client
, t.ip_status
, t.bank_account
, t.remitt_curr AS remitt_curr
, t.remitt_id
, t.apar_id
, t.payment_id
FROM
aipheader t
WHERE
DATEPART(DW, GETDATE()) IN (2,3,4)
AND
t.pay_date BETWEEN '2019-01-01' AND DATEADD(DAY,-4,GETDATE())
OR
DATEPART(DW, GETDATE()) IN (5,6)
AND
t.pay_date BETWEEN '2019-01-01' AND DATEADD(DAY,-2,GETDATE())) x
ORDER BY 1
The statement above will execute the OR-block that is true, in my case the one with the rigt weekday.
********* OLD QUESTIONS BELOW ***********
I want to run different SELECT statements on different days off the week.
I can actually get the following code to work:
select
case
when DATEPART(DW, GETDATE()) = 4 then
(select 'HELLO' )
ELSE
(select 'GOODBYE')
end
On a wendsday the code above returns "HELLO", every other workday it would returns "Goodbye". So far so good!
As long as the select statement only return one value it seams to work, but I realy want a full table like below.
The only differance here should be the [* FROM table] part, and it breaks it all:
select
case
when DATEPART(DW, GETDATE()) = 4 then
(select * FROM table1)
ELSE
(select * FROM table2)
end
If I can't even make the above to work I can not make different SELECTs for different workdays, so this is the hard ting to figure out.
I have tried to encapsulate things with () and add a few SELECTs in different ways but it do not work, so it is probably some SQL principal.
I get an error that I think says that this is ok if it is only one value that's returned:
SqlState 37000 Native 116 [Microsoft][ODBC SQL Server Driver][SQL
Server]Only one expression can be specified in the select list when
the subquery is not introduced with EXISTS.
THANKS FOR ALL OF THE REPLYS I NOW UNDERSTAND IT A BIT BETTER.
I was not looking to add a column, I tought I could make a complete new SELECT statement. I now see that my CASE statement only adds a column, as you point out.
So then my first ide might be a better aproce, to actually modify the WHERE-clause in the select statement.
I have now tried that but it does not work.
SELECT * FROM aipheader t
WHERE
CASE
WHEN DATEPART(DW, GETDATE()) = 4 THEN
t.pay_date = '2019-10-15'
ELSE
t.pay_date = '2019-10-17'
END
It returns the error:
SqlState 37000 Native 102 [Microsoft][ODBC SQL Server Driver][SQL
Server]Incorrect syntax near '='.
I have tried the simpler:
SELECT * FROM aipheader t
WHERE
t.pay_date BETWEEN DATEADD(DAY,-120,GETDATE()) AND DATEADD(DAY,-5,GETDATE())
The above is a simpler form, but that one returns the right values.
But the one obove that one with the CASE-clause return the error about "="-sign.
Maybee the CASE part in a WHERE statement do not work.
After your edit, the correct way to filter your date would be the following:
SELECT
*
FROM
aipheader t
WHERE
t.pay_date = CASE WHEN DATEPART(DW, GETDATE()) = 4 THEN '2019-10-15' ELSE '2019-10-17' END
However, do you really want to hard-code the 2019-10-15 and 2019-10-17 dates? Seems like these should be computed automatically as time goes.
Be careful when using DATEPART with DW, since the week number starting point can actually change depending on the server's and/or current session settings. Check this example:
DECLARE #TestDate DATE = '2020-01-01' -- Wednesday
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
SELECT DATEPART(DW, #TestDate) -- Returns 3
SET DATEFIRST 7 -- 1: Sunday, 7: Saturday
SELECT DATEPART(DW, #TestDate) -- Returns 4!
So whenever checking for a particular day of the week, make sure to force the DATEFIRST session parameter to a particular value so it's consistent with your checks.
I do not fully understand what you are trying to achieve.
Of course your statement will not work, because your select statement in the case when returns multiple columns and rows. That cannot work, because your case when statement will display one column!
So what you can do is the following:
select
case
when DATEPART(DW, GETDATE()) = 4 then
'HELLO'
ELSE
'GOODBYE'
end as [someField],
*
from
[table]
This query will have all rows and columns from table as output + the someField displaying HELLO or GOODBYE.
If you want to display different data from the table on the different dates (wednesday or not wednesday), you can alter the query this way:
select
case
when DATEPART(DW, GETDATE()) = 4 then
[table].[field1]
ELSE
[table].[field2]
end as [someField],
*
from
[table]
Is this ur looking for ? Sub query should return 1 row and specify col name
select
case
when DATEPART(DW, GETDATE()) = 4 then
(select top 1 col1 from table1 )
ELSE
(select top 1 col2 from table2 )
end as result
From table
CASE WHEN can only return a single value for each THEN, and never a Result Set with multiple rows and/or columns. That is just how CASE WHEN works.
You can achieve what you need like this:
DECLARE #DAY INT = DATEPART(DW, GETDATE())
select * FROM table
WHERE (#DAY = 4 AND table.SomeCol = SomeValue)
OR (#DAY = 5 AND table.SomeCol = SomeOtherValue)
... repeat for other days
Or like this:
DECLARE #DAY INT = DATEPART(DW, GETDATE())
IF #DAY = 4
(select * FROM table WHERE table.SomeCol = SomeValue)
ELSE IF #DAY = 5
(select * FROM table WHERE table.SomeCol = SomeOtherValue)
... repeat for other days
Or like this:
DECLARE #DAY INT = DATEPART(DW, GETDATE())
select * FROM table
WHERE #DAY = 4 AND table.SomeCol = SomeValue
UNION ALL
select * FROM table
WHERE #DAY = 5 AND table.SomeCol = SomeOtherValue
UNION ALL
... repeat for other days
My preference would probably be either the 1st or 2nd form.
A query must return before-known columns. If the tables table1 and table2 have different columns, you cannot write one query that returns, say, table1's five columns one day and table2's nine columns another.
As long as the resulting columns stay the same, however, you can join optionally. Here is a small example:
select
p.project_id,
p.project_name,
coalesce(c1.name, c2.name) as team_member,
coalesce(c1.salary, c2.salary * (1.0 + c2.tax / 100.0)) as team_member_salary,
coalesce(c1.job_name, c2.job_title) as team_member_job
from project p
left join crew1 c1 on c1.project_id = p.project_id and datepart(dw, getdate()) = 4
left join crew2 c2 on c2.project_id = p.project_id and datepart(dw, getdate()) <> 4
order by p.project_id;
Not sure I really understand what you are trying to achieve here. This example assumes that you are reading from different tables based on the date and will return all of the rows from both tables:
select * from (
select 'HELLO' as col1, * from table1 where DATEPART(DW, GETDATE()) = 4
UNION ALL
select 'GOODBYE' as col1, * from table2 where DATEPART(DW, GETDATE()) <> 4
)
order by col1

Using Conditional Aggregation in SubQuery

Thanks to the help of another user, I was able to use Conditional Aggregation to get the data point I need. I now need to implement this into an existing query in order to get an SLA % for a date range (rather than each package). Previous post for reference: Pull a DATEDIFF between Rows with Distinct value and WHERE Clause
The below query was used when the assumption that the 2 timestamps in 'PackageTable' were accurate enough to calculate SLA. Since I found out they were not, I have to run the query on a different table (PackageTable_Audit) that basically records events in a row when a package moves from LifeCycleStatusId = 1 (creation) to LifeCycleStatusId = 3 (Assigned) to LifeCycleStatusId = 5 (Completed). As such, the SLA adherence % is the amount of packages that were completed in X seconds / total packages. Since I can't use a simple DATEDIFF in a sub-query, and thus have to use the aggregate function to get a DATEDIFF between rows, I'm not sure how to work it into the query.
I've updated my old query with the Conditional Aggregate, but I get the following Error:
"Cannot perform an aggregate function on an expression containing an aggregate or a subquery."
Query:
-- VARIABLE DECLARATION AND INITIALIZATION
DECLARE #StartDate varchar(10);
DECLARE #EndDate varchar(10);
SET #StartDate = '2019-06-01';
SET #EndDate = '2019-06-31';
-- TABLE DECLARATION ##################################################
DECLARE #TABLE1 TABLE("No. Packages in SLA" INT, "Total Packages" INT, "SLA %" FLOAT)
--#####################################################################
-- WHAT GETS INSERTED INTO TABLE 1
INSERT INTO #TABLE1
SELECT
A.NUM, A.DENOM, CAST(A.NUM AS FLOAT)/A.DENOM*100
FROM
(
-- COLUMN SELECTION. TWO NUMBERS WILL REPRESENT A NUM AND A DENOM
SELECT
(SELECT SUM(CASE
WHEN
datediff(second, MAX(CASE WHEN LifeCycleStatusId = 2 THEN rowDateModified END),
MAX(CASE WHEN LifeCycleSTatusId = 5 THEN rowDateModified END)
) < 172800
THEN 1
ELSE 0
END) AS IN_SLA
FROM PackageTable WITH (nolock)
WHERE lifecyclestatusid = 5
AND rowDateCreated BETWEEN #StartDate AND #EndDate)
AS NUM,
(SELECT COUNT(PackageGuid) As No_Packages
FROM PackageTable WITH (nolock)
WHERE lifecyclestatusid = 5
AND rowDateCreated BETWEEN #StartDate AND #EndDate)
AS DENOM
) A
SELECT "No. Packages in SLA", "Total Packages", "SLA %"
FROM #TABLE1

SQL Server DATE conversion error

Here is my query:
SELECT
*
FROM
(SELECT
A.Name, AP.PropertyName, APV.Value AS [PropertyValue],
CONVERT(DATETIME, APV.VALUE, 101) AS [DateValue]
FROM dbo.Account AS A
JOIN dbo.AccountProperty AS AP ON AP.AccountTypeId = A.AccountTypeId
JOIN dbo.AccountPropertyValue AS APV ON APV.AccountPropertyId = APV.AccountPropertyId
AND APV.AccountId = A.AccountId
WHERE
A.AccountTypeId = '19602AEF-27B2-46E6-A068-7E8C18B0DD75' --VENDOR
AND AP.PropertyName LIKE '%DATE%'
AND ISDATE(APV.Value) = 1
AND LEN(SUBSTRING( REVERSE(APV.Value), 0 , CHARINDEX( '/', REVERSE(APV.Value)))) = 4 --ENSURE 4 digit year
) AS APV
WHERE
APV.DateValue < GETDATE()
It results in the following error:
Conversion failed when converting date and/or time from character string.
If you comment out the WHERE APV.DateValue < GETDATE() clause then there is no error and I get the 300+ rows. When I enable the WHERE clause I get the error.
So you are going to tell me my data is jacked up right? Well that's what I thought, so I tried to figure out where the problem in the data was, so I started using TOP() to isolate the location. Problem was once I use the TOP() function the error went away, I only have 2000 rows of data to begin with. So I put a ridiculous TOP(99999999) on the inner SELECT and now the entire query works.
The inner SELECT returns the same number of rows with or without the TOP().
WHY???
FYI, this is SQL that works:
SELECT
*
FROM
(SELECT TOP(99999999)
A.Name, AP.PropertyName, APV.Value AS [PropertyValue],
CONVERT(DATETIME, APV.VALUE, 101) AS [DateValue]
FROM dbo.Account AS A
JOIN dbo.AccountProperty AS AP ON AP.AccountTypeId = A.AccountTypeId
JOIN dbo.AccountPropertyValue AS APV ON APV.AccountPropertyId = APV.AccountPropertyId
AND APV.AccountId = A.AccountId
WHERE
A.AccountTypeId = '19602AEF-27B2-46E6-A068-7E8C18B0DD75' --VENDOR
AND AP.PropertyName LIKE '%DATE%'
AND ISDATE(APV.Value) = 1
AND LEN(SUBSTRING(REVERSE(APV.Value), 0 , CHARINDEX( '/', REVERSE(APV.Value)))) = 4
) AS APV
WHERE
APV.DateValue < GETDATE()
The problem that you are facing is that SQL Server can evaluate the expressions at any time during the query processing -- even before the WHERE clause gets evaluated. This can be a big benefit for performance. But, the consequence is that errors can be generated by rows not in the final result set. (This is true of divide-by-zero as well as conversion errors.)
Fortunately, SQL Server has a work-around for the conversion problem. Use try_convert():
TRY_CONVERT( DATETIME, APV.VALUE, 101) AS [DateValue]
This returns NULL rather than an error if there is a problem.
The reason why some versions work and others don't is because of the order of execution. There really isn't a way to predict what does and does not work -- and it could change if the execution plan for the query changes for other reasons (such as table statistics). Hence, use try_convert().
My guess is that your date is such that APV.VALUE contains also data that cannot be converted into a date, and should be filtered out using the other criteria?
Since SQL Server can decide to limit the data first using the criteria you have given:
APV.DateValue < CONVERT( DATETIME, GETDATE(),101)
And if there is data that cannot be converted into the date, then you will get the error.
To make it more clear, this is what is being filtered:
CONVERT( DATETIME, APV.VALUE, 101) AS [DateValue]
And if there is any data that cannot be converted into a date using 101 format, the filter using getdate() will fail, even if the row would not be included in the final result set for example because AP.PropertyName does not contain DATE.
Since you're using SQL Server 2012, using try_convert instead of convert should fix your problem
And why it works with top? In that case SQL Server cannot use the criteria from the outer query, because then the result might change, because it might affect the number of rows returned by top
Because number of records in the table < 999..99. And regarding the error it seems like SQL engine evaluates the WHERE clause after converting to date so you can try this:
SELECT *
FROM (
SELECT A.Name
, AP.PropertyName
, APV.Value AS [PropertyValue]
,
CASE
WHEN SDATE(APV.Value) = 1
THEN CONVERT( DATETIME, APV.VALUE, 101)
ELSE NULL
END AS [DateValue]
FROM dbo.Account AS A
JOIN dbo.AccountProperty AS AP
ON AP.AccountTypeId = A.AccountTypeId
JOIN dbo.AccountPropertyValue AS APV
ON APV.AccountPropertyId = APV.AccountPropertyId
AND APV.AccountId = A.AccountId
WHERE A.AccountTypeId = '19602AEF-27B2-46E6-A068-7E8C18B0DD75' --VENDOR
AND AP.PropertyName LIKE '%DATE%'
AND LEN( SUBSTRING( REVERSE(APV.Value), 0 , CHARINDEX( '/', REVERSE(APV.Value)))) = 4 --ENSURE 4 digit year
) AS APV
WHERE APV.DateValue IS NOT NULL AND APV.DateValue < GETDATE()

MS Access Query problem?

I am using this query:
SELECT D.Generic, D.Ww, D.Dd, D.Plan, c.TotalScan, D.Plan - c.TotalScan AS Balance
FROM TableA D
LEFT JOIN (
SELECT COUNT(a.Specific) AS TotalScan,
b.Generic, a.Dd,a.Ww
FROM TableB a
INNER JOIN TableC b
ON a.Specific = b.Specific
GROUP
BY b.Generic,a.Dd,a.Ww
WHERE DATEDIFF(DAY, a.TransactionDate, GETDATE()) = 0
) c
ON c.Generic = D.Generic
AND D.Ww = c.Ww
AND c.Dd = D.Dd
WHERE DATEDIFF(DAY, c.TransactionDate, GETDATE()) = 0;
to filter all records that is a insert in my sqlserver database.
Now i am having a hard time how can i do it ms access.
1. DATEDIFF(Day, TransactionDate, GetDate()) = 0 -- Not Work on MS Access(Which Filter all Records inserted in current Date)
2. Cant display TotalScan from subquery
Example Output Date:
TransactionDate
3/21/2011 7:26:24 AM
3/21/2011 7:26:24 AM
3/22/2011 7:26:24 AM --
3/22/2011 7:26:28 AM --
3/22/2011 7:26:30 AM --
3/22/2011 7:26:32 AM --
3/22/2011 7:26:35 AM --
if my date today is 3/22/2011 5 records will be displayed.
Thanks in Regards
GetDate() is SQL Server specific, Access has Now() instead.
The DateDiff() function also exists in Access, but the parameter for the interval is different:
DateDiff("d", TransactionDate, Now())
Equivalent of:
DATEDIFF(DAY, c.TransactionDate, GETDATE()) = 0
DATEDIFF("d", c.TransactionDate, Now()) = 0
Regards