Increase SQL Query Performance - sql

Sql:
select distinct DateAdd(Day, DateDiff(Day, 0, m.Receive_date), 0) as Date,
(select count(*) from Raw_Mats A where DateAdd(Day, DateDiff(Day, 0, A.Receive_date), 0)=DateAdd(Day, DateDiff(Day, 0, m.Receive_date), 0)) as Total,
(select count(*) from Raw_Mats B where DateAdd(Day, DateDiff(Day, 0, B.Receive_date), 0)=DateAdd(Day, DateDiff(Day, 0, m.Receive_date), 0) and B.status='Solved') as Delivered,
(select count(*) from Raw_Mats C where DateAdd(Day, DateDiff(Day, 0, C.Receive_date), 0)=DateAdd(Day, DateDiff(Day, 0, m.Receive_date), 0) and C.status='Pending') as UnDelivered
from Raw_Mats m where m.Receive_date between '2011-07-01' and '2011-07-21'
How to increase the performance of the above query. It is taking 44 secs . wanna make it less than 10 secs
Thanks

Do you have an index on both Receive_date and status? (not an index on each, combined)
Also:
You have have 4 touches in the table which means the query will scale at least O(4n).
By using COUNT(CASE) you can remove Delivered and UnDelivered subqueries
The simple count subquery isn't needed either
You need GROUP BY. YOur DISTINCT is a work around for that
BETWEEN is >= and <= which isn't the usually correct for dates with times
I've used a subquery here for clarity but it doesn't matter:
select
DateOnly as Date,
COUNT(*) AS Total,
COUNT(CASE WHEN status='Solved' THEN 1 END) AS Delivered,
COUNT(CASE WHEN status='Pending' THEN 1 END) AS UnDelivered
from
(
SELECT
DateAdd(Day, DateDiff(Day, 0, m.Receive_date), 0) as DateOnly,
status
FROM
Raw_Mats
WHERE
Receive_date >= '2011-07-01' AND Receive_date < '2011-07-21'
) T
GROUP BY
DateOnly
Edit, without subquery.
I started with a subquery because I thought it's be more complex than expected and didn't bother taking it out...
select
DateAdd(Day, DateDiff(Day, 0, m.Receive_date), 0) as Date,
COUNT(*) AS Total,
COUNT(CASE WHEN status='Solved' THEN 1 END) AS Delivered,
COUNT(CASE WHEN status='Pending' THEN 1 END) AS UnDelivered
from
Raw_Mats
WHERE
Receive_date >= '2011-07-01' AND Receive_date < '2011-07-21'
GROUP BY
DateAdd(Day, DateDiff(Day, 0, m.Receive_date), 0)

Divide and conquer: Just try each part of your sql as a separate statement and you'll find out which part is slow. If you have sub-selects and functions there is a good chance, that the server need temp-tables to perform the select, if you haven't got enough memory (or a large dataset or configured your sql server to do so), this temp-objects are swapped to disk, which makes it slow too.

Too many sub queries man! Get rid of some of them and it will help. Also you should not use functions on both sides in your sqls.
For example:
where DateAdd(Day, DateDiff(Day, 0, A.Receive_date), 0)=
DateAdd(Day, DateDiff(Day, 0, m.Receive_date), 0)
In this specific case the db engine will have to go through all the rows to evaluate DateDiff(Day, 0, A.Receive_date) and DateAdd(Day, DateDiff(Day, 0, A.Receive_date), 0) then compare it with the right hand side which also is a function! This simply is a disaster.
Also, do you have indexes on Receive_date? If not add it.

Related

Roll weekend counts into monday counts

I have a query like this:
select date, count(*)
from inflow
where date >= dateadd(year, -2, getdate())
group by date
order by date
I need to exclude Saturday and Sunday dates, and instead add their counts into the following Monday. What would be the best way to do this? Do I need to exclude Saturday, Sunday, and Mondays, then add them on with a join to a different query? The query above is a simplified version, this is a relatively big query, so I need to keep efficiency in mind.
Well, this is a somewhat brute-force approach:
select date,
(case when datename(weekday, date) = 'Monday')
then cnt + cnt1 + cnt2
else cnt
end) as cnt
from (select date, count(*) as cnt,
lag(count(*), 1, 0) over (order by date) as prev_cnt,
lag(count(*), 2, 0) over (order by date) as prev_cnt2
from inflow
where date >= dateadd(year, -2, getdate())
group by date
) d
where datename(weekday, date) not in ('Saturday', 'Sunday')
order by date;
Note: This is assuming English-language settings so the datename() logic works.
An alternative method without subqueries;
select v.dte, count(*) as cnt
from inflow i cross apply
(values (case when datename(weekday, i.date) = 'Saturday'
then dateadd(day, 2, i.date)
when datename(weekday, i.date) = 'Sunday'
then dateadd(day, 1, 9.date)
else i.date
end)
) v.dte
where i.date >= dateadd(year, -2, getdate())
group by v.dte
order by date;
You state for performance, however without knowing the full picture it's quite hard to understand how to optimise the query.
While I've been working on this, I noticed Gordon Linoff's answer, however I'll continue to write my version up as well, we both following the same path, but get to the answer a little different.
WITH DateData (date, datetoapply)
AS
(
SELECT
[date],
CASE DATEPART(w, [date])
WHEN 5 THEN DATEADD(d, 2, [date])
WHEN 6 THEN DATEADD(d, 1, [date])
ELSE date
END as 'datetoapply'
FROM inflow
WHERE [date] >= dateadd(year, -2, getdate())
)
SELECT datetoapply, count(*)
FROM DateData
GROUP BY datetoapply
ORDER BY datetoapply
While I could not get Gordon's query working as expected, I can confirm that "DATEPART(w, [date])" performs much better than "DATENAME(weekday, [date])", which if replaced in the query above increases the server processing time from 87ms to 181ms based on a table populated with 10k rows in Azure.

SQL Server: Need query to get records by comparing one of it's field with different values

Need query to get records by comparing one of it's field with different values
Here is the my table
repeated 1 means Daily
2 means Weekly
3 means Monthly
I need query to get all records which are going to Tele caste and being Tele casting by comapring current date and time.
Thank you
Do you mean something like this?
--case for daily: only need to check times
select
*
from
schedules
where
repeated = 1
--this is to compare if it starts within the next hour, may have bugs not tested
--logic is if start time - 1 hour is less than now but start time is more than now must start within the next hour
and DATEADD(day, -DATEDIFF(day, 0, dateadd(hour,startdate),-1) ), dateadd(hour,startdate),-1) ) < DATEADD(day, -DATEDIFF(day, 0, Now()), Now())
and DATEADD(day, -DATEDIFF(day, 0, startdate), startdate) > DATEADD(day, -DATEDIFF(day, 0, Now()), Now())
union
--case for weekly: is the day of the week the same, if so check times
select
*
from
schedules
where
repeated = 2
and datepart(dw,startdate) = datepart(dw,Now)
and DATEADD(day, -DATEDIFF(day, 0, dateadd(hour,startdate),-1) ), dateadd(hour,startdate),-1) ) < DATEADD(day, -DATEDIFF(day, 0, Now()), Now())
and DATEADD(day, -DATEDIFF(day, 0, startdate), startdate) > DATEADD(day, -DATEDIFF(day, 0, Now()), Now())
union
--case for monthly is the day of the month the same, if so compare times
select
*
from
schedules
where
repeated = 3
and datepart(day,startdate) = datepart(day,Now)
and DATEADD(day, -DATEDIFF(day, 0, dateadd(hour,startdate),-1) ), dateadd(hour,startdate),-1) ) < DATEADD(day, -DATEDIFF(day, 0, Now()), Now())
and DATEADD(day, -DATEDIFF(day, 0, startdate), startdate) > DATEADD(day, -DATEDIFF(day, 0, Now()), Now())
and is monthly based on date of the month , number of weeks (e.g. 4) or week of the month + weekday (e.g. second tuesday of the month)
EDIT: i think that should work where a monthly cycle is means on that day of the month every month.
The above could be modified to use a separate time column by just using that field for all time comparisons e.g.:
...
where
repeated = 3
and datepart(day,startdate) = datepart(day,Now)
and DATEADD(day, -DATEDIFF(day, 0, dateadd(hour,starttime),-1) ), dateadd(hour,starttime),-1) ) < DATEADD(day, -DATEDIFF(day, 0, Now()), Now())
and DATEADD(day, -DATEDIFF(day, 0, starttime), starttime) > DATEADD(day, -DATEDIFF(day, 0, Now()), Now())
EXPLANATION:
the normal way i would process this question as a human would be:
whats the start date
whats the repeating cycle
does startdate * any multiple of repeating cycle period = today
if so is it on now
this is a procedural, step based solution
when i think of this as a set based problem then i need to know what is the set of programs that is on now
I am allowed to add sets (union) but i cannot step through rows considering each one.. so i split it into three sets and add them together:
daily shows are on every day so i only need to check times
weekly shows are on every week on the same day, so if its weekly if the days of the week matches compare times
monthly shows are on the same day of the month every months, so if its that day of the month compare the times
hope this helps.. sorry if my explanation is crap, just trying to help :D
are you looking for this :-
Declare #Today Datetime
Select #Today = Getdate()
Select s.ProgramId
,s.ProgramName
From schedules As s With (Nolock)
Where (Cast(s.startdate As Datetime) + Cast(s.StartTime As Datetime)) >= #Today
Order By s.repeated
,s.ProgramId
Example:
SELECT column_name
FROM table_name
WHERE column_name IN (value1, value2, value3)
You mean something like that?

Sum on case expression when working with dates

I'm looking to create a view which will output the data in the following format
AgedPeriod BillValue Status
<1 35000 Outstanding
1-3 23386 Outstanding
3-6 5000 Outstanding
I can use the code below to SUM each case statement into a new column and I could name the column headings after the AgedPeriod listed above but even though the SUMS are right the format is wrong I would like to have the code below nested in another CASE statement that does not have to be GROUPED by b.BILL_DATE as grouping with the bill date defeats the purpose of my SUM. All attempts as using another CASE statement always bring the b.BILL_DATE out of the SUM and into the WHEN condition requiring it to be grouped.
SELECT
SUM(CASE WHEN (b.BILL_DATE <= GetDate()
AND b.BILL_DATE >= DateAdd(mm,-1, GetDate()))
THEN b.OUTSTANDING END),
SUM(CASE WHEN (b.BILL_DATE <= DateAdd(mm,-1, GetDate())
AND b.BILL_DATE >= DateAdd(mm,-3, GetDate()))
THEN b.OUTSTANDING END),
SUM(CASE WHEN (b.BILL_DATE <= DateAdd(mm,-3, GetDate())
AND b.BILL_DATE >= DateAdd(mm,-6, GetDate()))
THEN b.OUTSTANDING END)
FROM dbo.Tables
I understand this may not be achievable with the route that I have taken at present but is there be any other way I can SUM the outstanding amount on each time period? I can deal with the status column (no advice needed there)
I have added a table and some sample data and left a query to show how I would want the data split up but it would want it to be formatted as above (in a column)
Example on Sql Fiddle
Thanks
You want a group by rather than conditional aggregation. The query you want is something like this:
SELECT (CASE WHEN b.BILL_DATE >= DateAdd(month,-1, GetDate())
THEN '<1'
WHEN b.BILL_DATE >= DateAdd(month, -3, GetDate())
THEN '1-3'
WHEN b.BILL_DATE >= DateAdd(month, -6, GetDate())
THEN '3-6'
ELSE '6+'
END) as AgedPeriod,
SUM(Outstanding)
FROM dbo.Tables b
WHERE b.BILL_DATE <= GetDate()
GROUP BY (CASE WHEN b.BILL_DATE >= DateAdd(month,-1, GetDate())
THEN '<1'
WHEN b.BILL_DATE >= DateAdd(month, -3, GetDate())
THEN '1-3'
WHEN b.BILL_DATE >= DateAdd(month, -6, GetDate())
THEN '3-6'
ELSE '6+'
END);
Notes:
The groups are defined by a CASE statement. Because this is evaluated in order, you can simplify the logic.
The common condition b.BILL_DATE <= GetDate() is moved to the WHERE clause.
I added an extra condition for longer than six months. It seems like you wouldn't want to ignore these.
I don't know what the final column is supposed to be.
You could either use a subquery or CTE to perform the case when statement and then join back to the base table to get the sum for the outstanding column like this:
SELECT a.AgedPeriod
,sum(t1.Outstanding) BillValue
,a.[Status]
FROM dbo.Bill t1
JOIN (
SELECT (
CASE
WHEN b.BILLDATE >= DateAdd(month, - 1, GetDate())
THEN '<1'
WHEN b.BILLDATE >= DateAdd(month, - 3, GetDate())
THEN '1-3'
WHEN b.BILLDATE >= DateAdd(month, - 6, GetDate())
THEN '3-6'
ELSE '6+'
END
) AS AgedPeriod
,b.[ID]
,'Outstanding' [Status]
FROM dbo.Bill b
WHERE b.BILLDATE <= GetDate()
) a ON a.[ID] = t1.[ID]
GROUP BY a.AgedPeriod
,a.[Status]
Hope this helps! Here is a SQL Fiddle Demo for this:
SQL Fiddle Solution Demo

Hourly average except when hour has only one value

I have the following query which works on SQL Server 2012 to give me the hourly average of the values in the CounterValue column:
select
dateadd(hour, datediff(hour, 0, CounterDateTime), 0),
avg(CounterValue)
from mopsxactthroughput
group by
dateadd(hour, datediff(hour, 0, CounterDateTime), 0)
I would like to, if possible, modify it so that it returns no row for an hour if there was only ONE record matching that hour. For example, if between 2013-06-06T20:00:00 and 2013-06-06T21:00:00 there was only one record, I want no row returned for that one hour period.
It strikes me as a little much to ask for of a SELECT, but maybe there is a way. (My fallback will be to return all records grouped by hour and then iterate the row set myself in Java and apply the averaging/discarding there.)
Simply add a having clause:
select
dateadd(hour, datediff(hour, 0, CounterDateTime), 0),
avg(CounterValue)
from mopsxactthroughput
group by
dateadd(hour, datediff(hour, 0, CounterDateTime), 0)
having count(*) > 1
Perhaps this CTE works:
with cte as(
select dateadd(hour, datediff(hour, 0, CounterDateTime), 0) as Hour,
avg(CounterValue)over(partition by dateadd(hour, datediff(hour, 0, CounterDateTime), 0)) as AvgCounter,
Count(*)over(partition by dateadd(hour, datediff(hour, 0, CounterDateTime), 0)) as HourCount,
CounterValue,
CounterDateTime
from mopsxactthroughput
)
select Hour, CounterValue, CounterDateTime, AvgCounter
from cte
where HourCount > 1

Sql Server 2005 using count and distinct together

I'm trying to get the count of each distinct field in my database. For example, we are using something called sourceCodes - I want to be able to see how many of each different sourceCode there is in my database. So far, I have this
SELECT sourceCode, COUNT(DISTINCT sourceCode)
FROM [SecureOrders]
WHERE DateTime >= DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)
AND DateTime < DATEADD(day, DATEDIFF(day, 0, GETDATE()), 1)
GROUP BY sourceCode
(I'm trying to display the name of the sourceCode first, and then the count). So far though, the only thing I ever get in my second column is "1"...and I'm positive there are more than one. I know I worded this question really poorly, but I can't really figure out any other way to say it. Can anybody see why this is happening?
The "distinct" in your sample is not being applied at the correct place. By grouping by SourceCode, you are already getting distinct values from that column.
So, you only need to count the rows in each group:
SELECT sourceCode, COUNT(*)
FROM [SecureOrders]
WHERE DateTime >= DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)
AND DateTime < DATEADD(day, DATEDIFF(day, 0, GETDATE()), 1)
GROUP BY sourceCode
Remove the DISTINCT:
SELECT sourceCode, COUNT(sourceCode)
FROM [SecureOrders]
WHERE DateTime >= DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)
AND DateTime < DATEADD(day, DATEDIFF(day, 0, GETDATE()), 1)
GROUP BY sourceCode