TSQL - Multiple aliases for CASE WHEN statement - sql

I have a query that selects columns from a table where the condition is met, the problem is this logic is repeated several times (once for each column required) and wondered if there was a better way of doing it.
SELECT
CASE
WHEN (date1 IS NULL) THEN [date2]
WHEN (date1 IS NOT NULL OR ( date1 IS NOT NULL AND date2 IS NOT NULL )) THEN [date1]
ELSE ''
END AS selectedDate
//Repeat above statement for [day], [hour], [minute]
I want to know if its possible to aggregate a number of these case statements under the same statement with separate aliases as they all rely on the same conditions instead of rewriting the same statement multiple times for each column, for example I tried the following:
SELECT
CASE
WHEN (date1 IS NULL) THEN [date2] as "selectedDate", [day2] as "day", [hour2] as "hour", [minute2] as "minute"
WHEN (date1 IS NOT NULL OR ( date1 IS NOT NULL AND date2 IS NOT NULL )) THEN [date1] as "selectedDate", [day1] as "day", [hour1] as "hour", [minute1] as "minute"
ELSE ''

Not in the way you are describing...
An alternative would be to use isnull() or coalesce():
select
SelectedDate = coalesce(date1,date2)
, [Day] = coalesce(day1,day2)
, [Hour] = coalesce(hour1,hour2)
, [Minute] = coalesce(minute1,minute2)
from t

No. Each column needs its own select statement.
For your particular application of this CASE statement, you could be using COALESCE instead.
SELECT COALESCE(date1, date2) AS selectedDate FROM SomeTable

Related

group by issue in sql

i'm trying to get in a new column the sessions who are between 08:00 and 18:00. You can see my last CASE in the CTE. For each date there should be a new column "TotalRestrictedSessions" which indicate how many session were on that particular date. If there are none, in this case i have to write 0. I suspect that my problem is when i convert the DATE?
WITH ParkeonCTE
AS
(
SELECT
OccDate = CONVERT(DATE, OC.LocalStartTime),
TotalOccSessions = COUNT(OC.SessionId),
AuthorityId,
TotalOccDuration = ISNULL(SUM(OC.DurationMinutes),0),
TotalNumberOfOverstay = SUM(CAST(OC.IsOverstay AS INT)),
TotalMinOfOverstays = ISNULL(SUM(OC.OverStayDurationMinutes),0),
(CASE
WHEN OC.OspId IS NULL THEN 'OffStreet' ELSE 'OnStreet'
END
) AS ParkingContextType,
(CASE
WHEN CAST(OC.LocalStartTime AS TIME) >= '08:00:00' AND CAST(OC.LocalStartTime AS TIME) <=
'18:00:00'
THEN COUNT(OC.SessionId)
END
) AS TotalRestrictedSessions
FROM Analytics.OccupancySessions AS OC
WHERE OC.AuthorityId IS NOT NULL
GROUP BY CONVERT(DATE,OC.LocalStartTime), OC.AuthorityId,OC.OspId
)
SELECT OC.OccDate,
OC.ParkingContextType,
OC.AuthorityId,
OC.TotalRestrictedSessions,
SUM(OC.TotalOccSessions) AS TotalOccSessions,
AVG(OC.TotalOccDuration) AS AvgOccMinutesDuration, -- wrong
SUM(OC.TotalOccDuration) AS TotalOccDuration,
SUM(OC.TotalNumberOfOverstay) AS TotalNumberOfOverstay,
SUM(OC.TotalMinOfOverstays) AS TotalMinOfOverstays,
CAST(AVG(OC.TotalMinOfOverstays) AS decimal(10,2)) AS AvgMinOfOverstays -- wrong
FROM ParkeonCTE AS OC
GROUP BY OC.OccDate, OC.AuthorityId, OC.ParkingContextType
ORDER BY OC.OccDate DESC
You just need to move your aggregation outside of your CASE expression, called conditional aggregation.
SUM(CASE
WHEN CAST(OC.LocalStartTime AS TIME) >= '08:00:00'
AND CAST(OC.LocalStartTime AS TIME) <= '18:00:00'
THEN 1
ELSE 0
END
) AS TotalRestrictedSessions
Generally, you should include the current query results and your desired results in your question to make it easier to figure out where the issues are.

Datepart Calculation in Where clause

I am having problem with the datepart calculation in the Where clause of a query. The query returns result without the calculation but nothing if i add the condition.
DECLARE #StatusId INT;
SELECT #StatusId = Id FROM company.Status WHERE Name = 'Signed' AND CompanyId = 1;
SELECT FORMAT(CAST(cont.CreatedDate AS DATE), 'MM/dd') AS newDate,
SUM(CASE WHEN cont.UpdatedDate IS NOT NULL THEN 1 ELSE 0 END) AS TotalSignedLeads
FROM client.testw cont
WHERE cont.CompanyId = 1
AND cont.AffiliateId = 1
AND cont.CreatedDate BETWEEN '7-01-2017' AND '7-09-2017'
AND DATEPART(dw, cont.CreatedDate) NOT IN (1, 7) //This causes problem.
AND cont.StatusId = #StatusId
GROUP BY CAST(cont.CreatedDate AS DATE)
ORDER BY newDate ;
This is the data above query gives without the datepart condition.
newDate TotalSignedLeads
07/08 7
Well since you are casting cont.CreatedDate as a date in the top of the query, I suspect it's actually a varchar... thus you need
...
and datepart(weekday, cast(cont.CreatedDate as date)) not in (1,7)
...
bad-habits-to-kick-using-shorthand-with-date-time-operations
If you aren't getting an error, then you don't have any rows which meet that condition. Perhaps your DATEFIRST setting isn't what you think it is.
Also, not sure what GROUP BY CAST(#StatusId.CreatedDate AS DATE) is meant to be...
I suspect that you want a query more like this:
SELECT FORMAT(CAST(cont.CreatedDate AS DATE), 'MM/dd') AS newDate,
COUNT(cont.UpdatedDate) AS TotalSignedLeads
FROM client.testw cont
WHERE cont.CompanyId = 1 AND
cont.AffiliateId = 1 AND
cont.CreatedDate BETWEEN '2017-07-01' AND '2017-09-01' AND
DATEPART(dw, CAST(cont.CreatedDate AS DATE)) NOT IN (1, 7) AND //This causes problem.
cont.StatusId = #StatusId
GROUP BY FORMAT(CAST(cont.CreatedDate AS DATE), 'MM/dd')
ORDER BY newDate ;
Changes:
The dates for comparison are in a standard format, so they should be interpreted correctly.
The GROUP BY uses the same structure as the SELECT for the columns.
The SUM(CASE) is replaced by the much simpler COUNT().
The value for CreatedDate is cast as a DATE. To be honest, I'm not sure that will fix any problem, because that should be happening anyway.

SQl apply where clause to only one field

I'd like to apply a WHERE clause to just one field of my select query. The internet told me to use CASE WHEN in the line where I'm selecting my fields and to then remove the where clause. But I was then told that my "selected non aggregate values must be part of the associated group."
The original query looked like this:
SELECT
CAST(EVENT_TIMESTAMP AS DATE) AS Date1,
COUNT(DISTINCT EMAIL) END AS Subs,
SUM(DWELL_MINUTES) AS Dwell
FROM VwNIMEventFct
INNER JOIN VwNIMUserDim ON VwNIMUserDim.NIM_USER_ID = VwNIMEventFct.NIM_USER_ID
INNER JOIN TmpNIMSalesForceDB ON VwNIMUserDim.USER_EMAIL_ADDRESS = EMAIL
WHERE Date1 >= '2013-11-01'
// The problem is here, in the AND clause
AND (SUBSCRIPTION_END_DATE > VwNIMEventFct.EVENT_TIMESTAMP OR SUBSCRIPTION_END_DATE
IS NULL)
GROUP BY Date1
ORDER BY Date1
I then changed the query after doing some searching to this:
SELECT
CAST(EVENT_TIMESTAMP AS DATE) AS Date1,
CASE WHEN (SUBSCRIPTION_END_DATE > Date1 OR SUBSCRIPTION_END_DATE IS NULL)
THEN COUNT(DISTINCT TmpNIMSalesForceDB.EMAIL) END AS Subs,
SUM(VwNIMEventFct.DWELL_MINUTES) AS Dwell
FROM RDMAVWSANDBOX.VwNIMEventFct
INNER JOIN VwNIMUserDim ON VwNIMUserDim.NIM_USER_ID = VwNIMEventFct.NIM_USER_ID
INNER JOIN TmpNIMSalesForceDB ON VwNIMUserDim.USER_EMAIL_ADDRESS = EMAIL
WHERE Date1 >= '2013-11-01'
GROUP BY Date1
ORDER BY Date1
I'd like to select:
1) the date as per "Date1" in the query, then,
2) for each date, the count of distinct email addresses where the SUBSCRIPTION_END_DATE is either NULL or in the future (greater than Date1),
3) Sum of a field (I'm fine here)
How do I do number 2?
EDIT based on answer:
Does this part of the select query ignore and thus not count blank records when
SUBSCRIPTION_END_DATE is null?
SELECT
COUNT(DISTINCT CASE WHEN TmpNIMSalesForceDB.SUBSCRIPTION_END_DATE > Date1 OR TmpNIMSalesForceDB.SUBSCRIPTION_END_DATE IS NULL
THEN TmpNIMSalesForceDB.EMAIL END) AS Subs,
I need to count all records where SUBSCRIPTION_END_DATE is blank/null or where those dates are after Date1.
You need to put your CASE statement inside the COUNT, rather than vice versa, as it needs to be evaluated for each row (which case should this row fall in) and then aggregated across each group (how many rows in that group fell in the non-null group).
COUNT(DISTINCT CASE WHEN (SUBSCRIPTION_END_DATE > Date1 OR SUBSCRIPTION_END_DATE IS NULL)
THEN TmpNIMSalesForceDB.EMAIL END) AS Subs
The COUNT will ignore the NULLs implicitly left by the lack of an ELSE clause in the CASE statement, thus counting only the distinct EMAIL values from rows which met the condition.
Put the case statement inside of the count function.
SELECT
CAST(EVENT_TIMESTAMP AS DATE) AS Date1,
COUNT(DISTINCT(CASE
WHEN SUBSCRIPTION_END_DATE > Date1 OR SUBSCRIPTION_END_DATE IS NULL
THEN TmpNIMSalesForceDB.EMAIL END)) AS Subs,
SUM(VwNIMEventFct.DWELL_MINUTES) AS Dwell
FROM RDMAVWSANDBOX.VwNIMEventFct
INNER JOIN VwNIMUserDim
ON VwNIMUserDim.NIM_USER_ID = VwNIMEventFct.NIM_USER_ID
INNER JOIN TmpNIMSalesForceDB
ON VwNIMUserDim.USER_EMAIL_ADDRESS = EMAIL
WHERE Date1 >= '2013-11-01'
GROUP BY Date1
ORDER BY Date1

SQL Server Case Statement when IS NULL

I'm trying to do an IF statement type function in SQL server.
Where there is a NULL in the field, I want it to take a field from one of the tables and add 10 days to it.
And if possible create another column and add the 30 days.
SELECT DISTINCT
B.[ID],
MAX(A.[START DATE]),
B.[STAT],
C.[POST DATE],
CASE
WHEN (C.[POST DATE] BETWEEN C.[EVENT DATE]+10 AND C.[EVENT DATE]+30) THEN 'GOOD'
END AS [BETTER VISIT],
CASE
WHEN B.[STAT] IS NULL THEN (C.[EVENT DATE]+10)
ELSE '-'
END AS [DATE]
FROM
#TEMP1 A
FULL OUTER JOIN #TEMP2 B
ON A.[ID]=B.[ID]
FULL OUTER JOIN #TEMP3 C
ON A.[ID]=C.[ID]
GROUP BY
B.[ID],
B.[STAT],
C.[POST DATE],
C.[EVENT DATE]
ORDER BY
A.[START DATE] DESC
The result would look sort of like:
ID START DATE STAT POST DATE BETTER VISIT DATE DATE2
---------------------------------------------------------------------------
1 2013-01-01 GOOD 2013-11-01 GOOD - -
2 2013-03-01 NULL NULL NULL 2013-03-11 2013-03-31
CASE WHEN B.[STAT] IS NULL THEN (C.[EVENT DATE]+10) -- Type DATETIME
ELSE '-' -- Type VARCHAR
END AS [DATE]
You need to select one type or the other for the field, the field type can't vary by row.
The simplest is to remove the ELSE '-' and let it implicitly get the value NULL instead for the second case.
I agree with Joachim that you should replace the hyphen with NULL. But, if you really do want a hyphen, convert the date to a string:
(CASE WHEN B.[STAT] IS NULL
THEN convert(varchar(10), C.[EVENT DATE]+10, 121)
ELSE '-'
END) AS [DATE]
Also, the distinct is unnecessary in your select statement. The group by already does this for you.
You can use IIF (I think from SQL Server 2012)
SELECT IIF(B.[STAT] IS NULL, C.[EVENT DATE]+10, '-') AS [DATE]
case isnull(B.[stat],0)
when 0 then dateadd(dd,10,(c.[Eventdate]))
end
you can add in else statement if you want to add 30 days to the same .
In this situation you can use ISNULL() function instead of CASE expression
ISNULL(B.[STAT], C.[EVENT DATE]+10) AS [DATE]
Your hyphen in your ELSE statement isn't accepted in the column which is being defined under the datetime data type. You could either:
a) Wrap a CAST around your [stat] field to convert it to a varchar representation of a date
b) Use a datetime like 9999-12-31 for your ELSE value.
Take a look at the ISNULL function. It helps you replace NULL values for other values. http://msdn.microsoft.com/en-us/library/ms184325.aspx
Slight variation on Mahesh solution for mssql:
case when isnull(B.[stat],0) = 0
then dateadd(dd,10,(c.[Eventdate]))
end

Insert blank row to result after ORDER BY

I have SQL Query, I want to add insert blank row in result so it is easy to see the result.
I want to insert it after ORDER BY. don't know if it could be done.
Here is my select statement.
SELECT TableName.CREWACTIONFACTID
,TableName.CREWKEY as CrewKey
,TableName.EVENTKEY as EventID
,TableName.ACTIONSEQUENCE
,case TableName.ACTIONTYPE
when 'DISPATCHED' then '2-Dispatched'
when 'ASSIGNED' then '1-Assigned'
when 'ENROUTE' then '3-Entoute'
when 'ARRIVED' then '4-Arrived'
else 'unknown'
end as Type
,TableName.STARTDATETIME as StartTime
,TableName.ENDDATETIME as EndTIme
,TableName.DURATION as Duration
FROM DatabaseName.TableName TableName
where
To_Date(to_char(TableName.STARTDATETIME, 'DD-MON-YYYY')) >= To_Date('?DATE1::?','MM/DD/YYYY')
AND To_Date(to_char(TableName.ENDDATETIME, 'DD-MON-YYYY')) <= To_Date('?DATE2::?','MM/DD/YYYY')
ORDER BY TableName.EVENTKEY, TableName.STARTDATETIME,TableName.ACTIONSEQUENCE
You can, pretty much as Michael and Gordon did, just tack an empty row on with union all, but you need to have it before the order by:
...
and to_date(to_char(t.enddatetime, 'DD-MON-YYYY')) <=
to_date('?DATE2::?','MM/DD/YYYY')
union all
select null, null, null, null, null, null, null, null
from dual
order by eventid, starttime, actionsequence;
... and you can't use the case that Gordon had directly in the order by because it isn't a selected value - you'll get an ORA-07185. (Note that the column names in the order by are the aliases that you assigned in the select, not those in the table; and you don't include the table name/alias; and it isn't necessary to alias the null columns in the union part, but you may want to for clarity).
But this relies on null being sorted after any real values, which may not always be the case (not sure, but might be affected by NLS parameters), and it isn't known if the real eventkey can ever be null anyway. So it's probably safer to introduce a dummy column in both parts of the query and use that for the ordering, but exclude it from the results by nesting the query:
select crewactionfactid, crewkey, eventid, actionsequence, type,
starttime, endtime, duration
from (
select 0 as dummy_order_field,
t.crewactionfactid,
t.crewkey,
t.eventkey as eventid,
t.actionsequence,
case t.actiontype
when 'DISPATCHED' then '2-Dispatched'
when 'ASSIGNED' then '1-Assigned'
when 'ENROUTE' then '3-Enroute'
when 'ARRIVED' then '4-Arrived'
else 'unknown'
end as type,
t.startdatetime as starttime,
t.enddatetime as endtime,
t.duration
from schema_name.table_name t
where to_date(to_char(t.startdatetime, 'DD-MON-YYYY')) >=
to_date('?DATE1::?','MM/DD/YYYY')
and to_date(to_char(t.enddatetime, 'DD-MON-YYYY')) <=
to_date('?DATE2::?','MM/DD/YYYY')
union all
select 1, null, null, null, null, null, null, null, null
from dual
)
order by dummy_order_field, eventid, starttime, action sequence;
The date handling is odd though, particularly the to_date(to_char(...)) parts. It looks like you're just trying to lose the time portion, in which case you can use trunk instead:
where trunc(t.startdatetime) >= to_date('?DATE1::?','MM/DD/YYYY')
and trunc(t.enddatetime) <= to_date('?DATE2::?','MM/DD/YYYY')
But applying any function to the date column prevents any index on it being used, so it's better to leave that alone and get the variable part in the right state for comparison:
where t.startdatetime >= to_date('?DATE1::?','MM/DD/YYYY')
and t.enddatetime < to_date('?DATE2::?','MM/DD/YYYY') + 1
The + 1 adds a day, so id DATE2 was 07/12/2012, the filter is < 2012-07-13 00:00:00, which is the same as <= 2012-07-12 23:59:59.
Your question is rather complicated. SQL only guarantees the ordering of results, through the order by. It does not guarantee what happens afterwards. So, you have to put in the blank row and then add the information afterwords:
<your select query minus the order by>
union all
select NULL as CrewActionFatId, . . .
order by (case when CrewActionFactId is NULL then 1 else 0 end),
TableName.EVENTKEY, TableName.STARTDATETIME,TableName.ACTIONSEQUENCE
In practice, #Michael's solution would normally work. But it is not guaranteed.
Also, you should decide whether you want blanks or NULLs. I'm guessing the first id is a number, so I'm setting it to NULL.
In general, such presentation niceties are handled by the calling application. Perhaps you need a better SQL query tool to see the data more cleanly.
Here is what the full query would look like (with all fields set to NULL, you can change to blank if you prefer):
SELECT TableName.CREWACTIONFACTID, TableName.CREWKEY as CrewKey,
TableName.EVENTKEY as EventID, TableName.ACTIONSEQUENCE,
(case TableName.ACTIONTYPE
when 'DISPATCHED' then '2-Dispatched'
when 'ASSIGNED' then '1-Assigned'
when 'ENROUTE' then '3-Entoute'
when 'ARRIVED' then '4-Arrived'
else 'unknown'
end) as Type,
TableName.STARTDATETIME as StartTime,
TableName.ENDDATETIME as EndTIme,
TableName.DURATION as Duration
FROM DatabaseName.TableName TableName
where To_Date(to_char(TableName.STARTDATETIME, 'DD-MON-YYYY')) >= To_Date('?DATE1::?','MM/DD/YYYY') AND
To_Date(to_char(TableName.ENDDATETIME, 'DD-MON-YYYY')) <= To_Date('?DATE2::?','MM/DD/YYYY')
union all
SELECT NULL AS CREWACTIONFACTID, NULL AS CrewKey, NULL AS EventID,
NULL AS ACTIONSEQUENCE, NULL AS Type, NULL AS StartTime, NULL AS EndTime,
NULL AS Duration
from dual
ORDER BY (case when CrewActionFactId is NULL then 1 else 0 end),
TableName.EVENTKEY, TableName.STARTDATETIME, TableName.ACTIONSEQUENCE
An odd request to be sure, but yes it can be done by making a UNION against a row of literal blank values. To make sure the order-by is applied to the real query, enclose the whole thing in () and then union it against the blank row.
SELECT * FROM
(SELECT TableName.CREWACTIONFACTID
,TableName.CREWKEY as CrewKey
,TableName.EVENTKEY as EventID
,TableName.ACTIONSEQUENCE
,case TableName.ACTIONTYPE
when 'DISPATCHED' then '2-Dispatched'
when 'ASSIGNED' then '1-Assigned'
when 'ENROUTE' then '3-Entoute'
when 'ARRIVED' then '4-Arrived'
else 'unknown'
end as Type
,TableName.STARTDATETIME as StartTime
,TableName.ENDDATETIME as EndTIme
,TableName.DURATION as Duration
FROM DatabaseName.TableName TableName
where
To_Date(to_char(TableName.STARTDATETIME, 'DD-MON-YYYY')) >= To_Date('?DATE1::?','MM/DD/YYYY')
AND To_Date(to_char(TableName.ENDDATETIME, 'DD-MON-YYYY')) <= To_Date('?DATE2::?','MM/DD/YYYY')
ORDER BY TableName.EVENTKEY, TableName.STARTDATETIME,TableName.ACTIONSEQUENCE
)
UNION ALL
SELECT
'' AS CREWACTIONFACTID,
'' AS CrewKey,
'' AS EventID,
'' AS ACTIONSEQUENCE,
'' AS Type,
'' AS StartTime,
'' AS EndTime,
'' AS Duration
FROM dual
Finally, depending on how you are presenting this result, I would look into other methods of spacing out the result. Appending blank rows to a query for presentation purposes flies in the face of separation of business and presentation logic.
The result will be desplayed in an HTML page.
So, use SQL to extract the data, not to format the output.
Depending on the page structure and layout, there are a lot of solutions.
Have a look here.