Move Subquery in the where clause to a table in SQL Server - sql

I have to get the top 1 order by using this query:
select top 1 ID
From MyTable
Where userid = #userid
and ((type = 'SomeValue1' and status in (1,2) and convert(date,create_date) < convert(date,getdate())) OR
(type = 'SomeValue2' and status in (1,3) and convert(date,create_date) < convert(date,getdate())) OR
(type = 'SomeValue2' and status not in (1,3) and convert(date,create_date) = convert(date,getdate())))
and active = 1
order by convert(date,create_date)
Is there a way, I can move the conditions in the where clause after the 1st "AND", where I compare the "type", "status" and "Create_Date" to a table and use it in the where clause? The reason I'm asking is, I have to do a number of such comparisons and it would get clunky to have like 30 different lines in there for one query.
Also , let me know, if I'm going this the totally wrong way.
Thanks in advance!

Not an answer. Only my recommendations:
DECLARE #today date = cast(sysdatetime() AS date);
select top(1) ID
From MyTable
Where userid = #userid
and ((type = 'SomeValue1' and status in (1,2) and cast(create_date AS date) < #today) OR
(type = 'SomeValue2' and status in (1,3) and cast(create_date AS date) < #today) OR
(type = 'SomeValue2' and status not in (1,3) and cast(create_date AS date) = #today))
and active = 1
ORDER BY x
OPTION(RECOMPILE);
Then add filtered index on userid, type, status and create_date WHERE active = 1

Related

How to combine 2 SQL statements into one table

I have 2 SQL statements to look up successful transactions and failed transactions.
SELECT COUNT (code_reseller) as trx_success, kode_reseller
FROM transaksi
where status = '20' AND CAST (date_entri AS DATE) = CAST (GETDATE() AS DATE)
group by code_reseller
ORDER BY trx_success DESC
AND
SELECT COUNT (code_reseller) as trx_fail, kode_reseller
FROM transaksi
where status > '20' AND CAST (date_entri AS DATE) = CAST (GETDATE() AS DATE)
group by code_reseller
ORDER BY trx_fail DESC
How to combine into one table with 3 columns result with code_reseller, trx_success and trx_fail?
Use conditional aggregation and combine the queries:
SELECT
kode_reseller,
COUNT(CASE WHEN status = '20' THEN 1 END) AS trx_success,
COUNT(CASE WHEN status > '20' THEN 1 END) AS trx_fail
FROM transaksi
WHERE
CAST(date_entri AS DATE) = CAST(GETDATE() AS DATE)
GROUP BY
kode_reseller;
The strategy here is to move the filtering on the status column which previously appeared in the two WHERE clauses into the conditional counts in the SELECT clause. The restriction on date_entri can stay there, since both queries have it.
As suggested by #Dale k, you can do it like this.
You cannot add order by inside, so create an alias table and give order by condition.
SELECT *
FROM
(
SELECT COUNT (code_reseller) as trx_success, kode_reseller
FROM transaksi
WHERE status = '20' AND CAST (date_entri AS DATE) = CAST (GETDATE() AS DATE)
GROUP BY code_reseller
UNION ALL
SELECT COUNT (code_reseller) as trx_fail, kode_reseller
FROM transaksi
WHERE status > '20' AND CAST (date_entri AS DATE) = CAST (GETDATE() AS DATE)
GROUP BY code_reseller
) a
ORDER BY a.trx_success DESC --here we get first select query table' column name and datatype and no of column will be same required in union/union all

SQL Server Yesterdays Count

I am trying to get a count of all of yesterdays rows. The query i have runs good but does not pick up null values. Is there a way i can query a count of null and non null values?
Here is my code:
SELECT dateadd(day,datediff(day,0,GETDATE())-1,0) as Received_Date,
COUNT(*) as Enrollments_Completed
FROM Table CD,
CCMDB.dbo.ResolutionLetterDetails RD
WHERE CD.ccid = RD.ccid
and CompletedDate >= DATEADD(d,DATEDIFF(d,1,getdate()),0)
and CompletedDate < DATEADD(d,DATEDIFF(d,0,getdate()),0)
AND CatID in('cat0014')
AND IncomingType <> 'RITS'
AND status = 'Completed'
Convert your CompletedDate to a date with no time and make it equal yesterdays date with no time (from GETDATE()) and use correct JOIN code.
SELECT dateadd(day,datediff(day,0,GETDATE())-1,0) as Received_Date,
COUNT(*) as Enrollments_Completed
FROM Table CD
LEFT JOIN CCMDB.dbo.ResolutionLetterDetails RD ON CD.ccid = RD.ccid
WHERE dateadd(day,datediff(day,1,CompletedDate),0) = dateadd(day,datediff(day,1,GETDATE()),0)
AND CatID IN ('cat0014')
AND IncomingType != 'RITS'
AND status = 'Completed'
Return NULLs:
SELECT dateadd(day,datediff(day,0,GETDATE())-1,0) as Received_Date,
COUNT(*) as Enrollments_Completed
FROM Table CD
LEFT JOIN CCMDB.dbo.ResolutionLetterDetails RD ON CD.ccid = RD.ccid
WHERE dateadd(day,datediff(day,1,CompletedDate),0) = dateadd(day,datediff(day,1,GETDATE()),0)
AND (CatID IN ('cat0014') OR CatID IS NULL)
AND (IncomingType != 'RITS' OR IncomingType IS NULL)
AND (status = 'Completed' OR status IS NULL)
I would fix your query and do:
SELECT CAST(DATEADD(day, -1, GETDATE()) as DATE) as Received_Date,
COUNT(*) as Enrollments_Completed
FROM Table CD JOIN
CCMDB.dbo.ResolutionLetterDetails RD
ON CD.ccid = RD.ccid
WHERE CompletedDate >= CAST(DATEADD(day, -1, GETDATE()) as DATE) AND
CompletedDate < CAST(GETDATE() as DATE) AND
CatID IN ('cat0014') AND
IncomingType <> 'RITS' AND
status = 'Completed';
For the date part, you could also do:
CAST(CompletedDate as DATE) = CAST(DATEADD(day, -1, GETDATE()) as DATE)
This version is even index-safe in SQL Server (although not necessarily in other databases).
Notes:
The DATE data type considerably simplifies your calculations.
Never use commas in the FROM clause. Always use proper, explicit, standard JOIN syntax.
You should qualify all column names so you (and anyone reading the query) knows what table the column comes from.

Multiple Selects into one select

I'm trying to put some data together for a High Charts Bar chart using ASP.NET. Basically, i have three users who i need to track when they have logged into the system. the variants to be used are:
1) Today
2) This Week
3) Last Week
4) Last Month
So, i've created individual tsql scripts for today and and last week, but i'm now a little stuck on how to combine the two statemets, which will eventually be four.
SELECT Count(*) as CountToday from hitsTable WHERE Convert(date,hitDate) =
Convert(date,GETDATE()) Group by UserId
SELECT count(*) as CountLatWeek from hitTable
where hitDate between (DATEADD(week, DATEDIFF (week,0,GETDATE()),-1))
AND getDate() Group by UserId
Searhing on google, leads me to nested select statements, which all seem to form dependacies with the two statements. However, what i need to do is produce a table of results like this:
EDIT
I've set up a SQL Fiddle, so we can test out the examples
http://www.sqlfiddle.com/#!6/a21ec
the fiddle has tsql for today and tsql for last week (which may need some tweaking)
Select Distinct
UserId
, ( Select Count(*) as CountToday from hitsTable h2
Where h2.UserId = h1.UserId
And Convert(date,hitDate) = Convert(date,GETDATE())
) As CountToday
, ( Select count(*) as CountLatWeek from hitsTable h2
Where h2.UserId = h1.UserId
And hitDate Between DATEADD(dd, -(DATEPART(dw, GetDate())-1)-7, GetDate())
And DATEADD(dd, 7-(DATEPART(dw, GetDate()))-7, GetDate())
) As CountLastWeek
FROM hitsTable h1
Here’s another alternative based on #Avinash comment on the question.
Select
UserId
, CountTodayTable.CountToday
, CountLatWeekTable.CountLatWeek
, ...
FROM hitsTable h1
Inner Join
( Select Count(*) as CountToday from hitsTable h2
Where h2.UserId = h1.UserId
And Convert(date,hitDate) = Convert(date,GETDATE())
) CountTodayTable
On CountTodayTable.UserId = h1.UserId
Inner Join
( Select count(*) as CountLatWeek from hitTable h2
Where h2.UserId = h1.UserId
And hitDate between (DATEADD(week, DATEDIFF (week,0,GETDATE()),-1)) And getDate()
) CountLatWeekTable
On CountLatWeekTable.UserId = h1.UserId
...
Try this query
select
id,
sum(case when Convert(date,hitDate) = Convert(date,GETDATE()) then 1 else 0 end) as as CountToday,
sum(hitDate between (DATEADD(week, DATEDIFF (week,0,GETDATE()),-1)) AND getDate() then 1 else 0 end) as CountLatWeek,
...... -- Add more condition
from
hitsTable
group by
UserId
Edit
select
userid,
sum(case when Convert(date,hitDate) =
Convert(date,GETDATE()) then 1 else 0 end) as cnt
from
hitstable
group by userid
FIDDLE
| USERID | CNT |
|--------|-----|
| User1 | 3 |
| User2 | 0 |

How to determine if two records are 1 year apart (using a timestamp)

I need to analyze some weblogs and determine if a user has visited once, taken a year break, and visited again. I want to add a flag to every row (Y/N) with a VisitId that meets the above criteria.
How would I go about creating this sql?
Here are the fields I have, that I think need to be used (by analyzing the timestamp of the first page of each visit):
VisitID - each visit has a unique Id (ie. 12356, 12345, 16459)
UserID - each user has one Id (ie. steve = 1, ted = 2, mark = 12345, etc...)
TimeStamp - looks like this: 2010-01-01 00:32:30.000
select VisitID, UserID, TimeStamp from page_view_t where pageNum = 1;
thanks - any help would be greatly appreciated.
You could rank every user's rows, then join the ranked row set to itself to compare adjacent rows:
;
WITH ranked AS (
SELECT
*,
rnk = ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY TimeStamp)
FROM page_view_t
),
flagged AS (
SELECT
*,
IsReturnVisit = CASE
WHEN EXISTS (
SELECT *
FROM ranked
WHERE UserID = r.UserID
AND rnk = r.rnk - 1
AND TimeStamp <= DATEADD(YEAR, -1, r.TimeStamp)
)
THEN 'Y'
ELSE 'N'
END
FROM ranked r
)
SELECT
VisitID,
UserID,
TimeStamp,
IsReturnVisit
FROM flagged
Note: the above flags only return visits.
UPDATE
To flag the first visits same as return visits, the flagged CTE could be modified as follows:
…
SELECT
*,
IsFirstOrReturnVisit = CASE
WHEN p.UserID IS NULL OR r.TimeStamp >= DATEADD(YEAR, 1, p.TimeStamp)
THEN 'Y'
ELSE 'N'
END
FROM ranked r
LEFT JOIN ranked p ON r.UserID = p.UserID AND r.rnk = p.rnk + 1
…
References that might be useful:
WITH common_table_expression (Transact-SQL)
Ranking Functions (Transact-SQL)
ROW_NUMBER (Transact-SQL)
The other guy was faster but since I took time to do it and it's a completely different approach I might as well post It :D.
SELECT pv2.VisitID,
pv2.UserID,
pv2.TimeStamp,
CASE WHEN pv1.VisitID IS NOT NULL
AND pv3.VisitID IS NULL
THEN 'YES' ELSE 'NO' END AS IsReturnVisit
FROM page_view_t pv2
LEFT JOIN page_view_t pv1 ON pv1.UserID = pv2.UserID
AND pv1.VisitID <> pv2.VisitID
AND (pv1.TimeStamp <= DATEADD(YEAR, -1, pv2.TimeStamp)
OR pv2.TimeStamp <= DATEADD(YEAR, -1, pv1.TimeStamp))
AND pv1.pageNum = 1
LEFT JOIN page_view_t pv3 ON pv1.UserID = pv3.UserID
AND (pv3.TimeStamp BETWEEN pv1.TimeStamp AND pv2.TimeStamp
OR pv3.TimeStamp BETWEEN pv2.TimeStamp AND pv1.TimeStamp)
AND pv3.pageNum = 1
WHERE pv2.pageNum = 1
Assuming page_view_t table stores UserID and TimeStamp details of each visit of the user, the following query will return users who have visited taking a break of at least an year (365 days) between two consecutive visits.
select t1.UserID
from page_view_t t1
where (
select datediff(day, max(t2.[TimeStamp]), t1.[TimeStamp])
from page_view_t t2
where t2.UserID = t1.UserID and t2.[TimeStamp] < t1.[TimeStamp]
group by t2.UserID
) >= 365

why does adding the where statement to this sql make it run so much slower?

I have inherited a stored procedure and am having problems with it takes a very long time to run (around 3 minutes). I have played around with it, and without the where clause it actually only takes 12 seconds to run. None of the tables it references have a lot of data in them, can anybody see any reason why adding the main where clause below makes it take so much longer?
ALTER Procedure [dbo].[MissingReadingsReport] #SiteID INT,
#FormID INT,
#StartDate Varchar(8),
#EndDate Varchar(8)
As
If #EndDate > GetDate()
Set #EndDate = Convert(Varchar(8), GetDate(), 112)
Select Dt.FormID,
DT.FormDAte,
DT.Frequency,
Dt.DayOfWeek,
DT.NumberOfRecords,
Dt.FormName,
dt.OrgDesc,
Dt.CDesc
FROM (Select MeterForms.FormID,
MeterForms.FormName,
MeterForms.SiteID,
MeterForms.Frequency,
DateTable.FormDate,
tblOrganisation.OrgDesc,
CDesc = ( COMPANY.OrgDesc ),
DayOfWeek = CASE Frequency
WHEN 'Day' THEN DatePart(dw, DateTable.FormDate)
WHEN 'WEEK' THEN
DatePart(dw, MeterForms.FormDate)
END,
NumberOfRecords = CASE Frequency
WHEN 'Day' THEN (Select TOP 1 RecordID
FROM MeterReadings
Where
MeterReadings.FormDate =
DateTable.FormDate
And MeterReadings.FormID =
MeterForms.FormID
Order By RecordID DESC)
WHEN 'WEEK' THEN (Select TOP 1 ( FormDate )
FROM MeterReadings
Where
MeterReadings.FormDate >=
DateAdd(d
, -4,
DateTable.FormDate)
And MeterReadings.FormDate
<=
DateAdd(d, 3,
DateTable.FormDate)
AND MeterReadings.FormID =
MeterForms.FormID)
END
FROM MeterForms
INNER JOIN DateTable
ON MeterForms.FormDate <= DateTable.FormDate
INNER JOIN tblOrganisation
ON MeterForms.SiteID = tblOrganisation.pkOrgId
INNER JOIN tblOrganisation COMPANY
ON tblOrganisation.fkOrgID = COMPANY.pkOrgID
/*this is what makes the query run slowly*/
Where DateTable.FormDAte >= #StartDAte
AND DateTable.FormDate <= #EndDate
AND MeterForms.SiteID = ISNULL(#SiteID, MeterForms.SiteID)
AND MeterForms.FormID = IsNull(#FormID, MeterForms.FormID)
AND MeterForms.FormID > 0)DT
Where ( Frequency = 'Day'
And dt.NumberofRecords IS NULL )
OR ( ( Frequency = 'Week'
AND DayOfWeek = DATEPART (dw, Dt.FormDate) )
AND ( FormDate <> NumberOfRecords
OR dt.NumberofRecords IS NULL ) )
Order By FormID
Based on what you've already mentioned, it looks like the tables are properly indexed for columns in the join conditions but not for the columns in the where clause.
If you're not willing to change the query, it may be worth it to look into indexes defined on the where clause columns, specially that have the NULL check
Try replacing your select with this:
FROM
(select siteid, formid, formdate from meterforms
where siteid = isnull(#siteid, siteid) and
meterforms.formid = isnull(#formid, formid) and formid >0
) MeterForms
INNER JOIN
(select formdate from datetable where formdate >= #startdate and formdate <= #enddate) DateTable
ON MeterForms.FormDate <= DateTable.FormDate
INNER JOIN tblOrganisation
ON MeterForms.SiteID = tblOrganisation.pkOrgId
INNER JOIN tblOrganisation COMPANY
ON tblOrganisation.fkOrgID = COMPANY.pkOrgID
/*this is what makes the query run slowly*/
)DT
I would be willing to bet that if you moved the Meterforms where clauses up to the from statement:
FROM (select [columns] from MeterForms WHERE SiteID= ISNULL [etc] ) MF
INNER JOIN [etc]
It would be faster, as the filtering would occur before the join. Also, having your INNER JOIN on your DateTable doing a <= down in your where clause may be returning more than you'd like ... try moving that between up to a subselect as well.
Have you run an execution plan on this yet to see where the bottleneck is?
Random suggestion, coming from an Oracle background:
What happens if you rewrite the following:
AND MeterForms.SiteID = ISNULL(#SiteID, MeterForms.SiteID)
AND MeterForms.FormID = IsNull(#FormID, MeterForms.FormID)
...to
AND (#SiteID is null or MeterForms.SiteID = #SiteID)
AND (#FormID is null or MeterForms.FormID = #FormID)