Subquery can return 1 column only - sql-server

The purpose of this query would be to do a calculation based on if today's date is a weekday or a weekend and use it after with Union All, however I am getting
Msg 116 error:
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
It looks like that I can return only 1 column with this:
My query:
SELECT (SELECT CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday')
THEN
(SELECT --WeekEND
'X' AS Table_name
,CAST(MAX(date_1) as date) AS max_date
,DATEDIFF(DD,(CAST(MAX(date_1)as date)),GETDATE()) as NrOfDays
,CASE
WHEN DATEDIFF(DD,(CAST(MAX(date_1)as date)),GETDATE()) <= 3 THEN 'good'
ELSE 'bad'
END AS Status
FROM [Table_1])
ELSE
(SELECT --WeekDAY
'X' AS Table_name
,CAST(MAX(date_1) as date) AS max_date
,DATEDIFF(DD,(CAST(MAX(date_1)as date)),GETDATE()) as NrOfDays
,CASE
WHEN DATEDIFF(DD,(CAST(MAX(date_1)as date)),GETDATE()) <= 1 THEN 'good'
ELSE 'bad'
END AS Status
FROM [Table_1])
END
)
Same issue with the following just a simplified version (if I delete the 'good', 'bad' part it works):
SELECT (SELECT CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
(SELECT 'Weekend', 'good')
ELSE
(SELECT 'Weekday', 'bad')
END
)

You are trying to return two columns from your subquery, into a single column of your main query.
Instead, you need to make two (very similar) subqueries. One for each column in your main query.
SELECT(
SELECT(
CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
(SELECT 'Weekend')
ELSE
(SELECT 'Weekday')
END AS Column1,
CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
(SELECT 'good')
ELSE
(SELECT 'bad')
END AS Column2
)
)
There are other (better) ways to do this, depending upon your expected output. But this is probably the easiest/simplest to understand.
Also, not that it really matters but you have a lot of SELECTs in your query that you don't really need. The above can be simplified to:
SELECT
CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
'Weekend'
ELSE
'Weekday'
END AS Column1,
CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
'good'
ELSE
'bad'
END AS Column1
I don't know what your "Status" column is really doing, so I can't optimise it safely for you. But I think this should do what you want:
SELECT
'X' AS Table_name,
CAST(MAX(date_1) as date) AS max_date,
DATEDIFF(DD,(CAST(MAX(date_1)as date)), GETDATE()) as NrOfDays,
CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
CASE WHEN DATEDIFF(DD,(CAST(MAX(date_1)as date)), GETDATE()) <= 3 THEN
'good'
ELSE
'bad'
END
ELSE
CASE WHEN DATEDIFF(DD,(CAST(MAX(date_1)as date)), GETDATE()) <= 1 THEN
'good'
ELSE
'bad'
END
END AS Status
FROM [Table_1]

Related

Rolling 3 Years of data with desired format

Start Date = 2016-03-01
End Date = 2019-02-15
I need the query/Function which should retrive the below result based on start and end date. I have a query to frame the result .But i need some query/function to retrive result set in 5 to 10 sec with performance
You mean like
SELECT * FROM table WHERE [start date]>='2016-03-01' AND [end date]<='2019-02-15'
Am I missing something? This seems too simple to ask as a question
If your problem is performance perhaps consider indexing start and end date columns, and if you're only getting a couple other values from the table include those in the index too.
CREATE INDEX IX_table_startdate_enddate
ON schema.table ([start date], [end date])
INCLUDE (othercolumn1, othercolumn2);
It means that queries like:
SELECT othercolumn1, othercolumn2 FROM table
WHERE [start date]>='2016-03-01' AND [end date]<='2019-02-15'
Can be answered from the index without having to connect the index to the table to pull the desired data
If you still can't wrong enough out of it after that perhaps you have a design flaw in your app; pilling a db for a massive amount of data in a tight interval could be solved another way like only emitting events when data actually changes
It's just occurred to me you're probably looking for a query to generate that result set from nothing. In sqlserver we can use a recursive cte:
DECLARE #dateFrom DATE = '2016-03-01'
DECLARE #dateTo DATE = '2019-02-15'
with d as (
SELECT #datefrom as m
UNION ALL
SELECT DATEADD(MONTH,1,reqDate)
FROM d
WHERE DATEADD(MONTH,1,reqDate) <= #dateto
),
SELECT
CONCAT(
DATENAME(MONTH, #dateFrom), ' ',
CASE WHEN MONTH(d.m) < MONTH(#dateFrom) THEN YEAR(d.m) - 1 ELSE YEAR(d.m) END, '-',
DATENAME(MONTH, #dateTo), ' ',
CASE WHEN MONTH(d.m) < MONTH(#dateFrom) THEN YEAR(d.m) ELSE YEAR(d.m) +1 END
) as range,
MONTH(d.m) as month,
d.m as startdate,--do not use spaces in column names
CASE WHEN #dateTo < EOMONTH(d.m) then #dateTo ELSE EOMONTH(d.m) END as enddate --or dateadd 1 month then dateadd -1 day if you don't have eomonth
FROM d
OPTION(MAXRECURSION 0);
Same format with your result try it.
DECLARE #StartDate DATETIME, #EndDate DATETIME
SET #StartDate = '2016-03-01'
SET #EndDate = '2038-02-15'
;WITH CTEs AS
(
SELECT #StartDate as [Start Date]
UNION ALL
SELECT DATEADD(MONTH,1,[Start Date])
FROM CTEs WHERE DATEADD(MONTH,1,[Start Date]) <= #EndDate
)
SELECT
CONCAT(
DATENAME(MONTH, #StartDate), ' ',
CASE WHEN MONTH([Start Date]) < MONTH(#StartDate) THEN YEAR([Start Date]) - 1 ELSE YEAR([Start Date]) END, '-',
DATENAME(MONTH, #EndDate), ' ',
CASE WHEN MONTH([Start Date]) < MONTH(#StartDate) THEN YEAR([Start Date]) ELSE YEAR([Start Date]) + 1 END
) AS [Range],
MONTH([Start Date]) AS [Month],
CONVERT(VARCHAR(10),[Start Date],101) AS [Start Date],
CONVERT(VARCHAR(10),(CASE WHEN [Start Date] <> DATEFROMPARTS(YEAR(#EndDate),MONTH(#EndDate),1)
THEN EOMONTH([Start Date])
ELSE #EndDate
END),101) AS [End Date]
FROM CTEs
OPTION(MAXRECURSION 0);

Use result of Case statement in another Case statement

I have quite a long SELECT query but I have pasted the relevant part here.
I need to use the result of the of my CASE statement to use in another CASE statement. I'm doing this in SQL Server.
Would be very grateful for help.
SELECT
CompanyContact.Name AS CompanyName,
CASE
WHEN SUBSTRING(HeadLease.TenantBreakNotice, LEN(HeadLease.TenantBreakNotice), 1) = 'M'
THEN CONVERT(VARCHAR(10), DATEADD(DD, -365 / (12 / SUBSTRING(HeadLease.TenantBreakNotice, 1, LEN(HeadLease.TenantBreakNotice) - 1)), HeadLease.TenantBreakDate), 103)
WHEN SUBSTRING(HeadLease.TenantBreakNotice, LEN(HeadLease.TenantBreakNotice), 1) = 'Y'
THEN CONVERT(VARCHAR(10), DATEADD(DD, -365 * (SUBSTRING(HeadLease.TenantBreakNotice, 1, LEN(HeadLease.TenantBreakNotice) - 1)), HeadLease.TenantBreakDate), 103)
ELSE HeadLease.TenantBreakNotice
END AS [TenantBreakNotice], <-- I need this to be used in the case statement below.
CASE
WHEN [TenantBreakNotice] < CONVERT(varchar(10), getdate(), 103)
THEN 'Expiry'
WHEN [TenantBreakNotice] IS NULL
THEN 'Expiry'
ELSE 'Break'
END AS [LeaseEventType]
FROM
HeadLease
You cannot use a column alias in the same select where it is defined. The usual solution is to repeat the logic (hard to maintain), use a subquery, or CTE. SQL Server offers another elegant solution:
SELECT hl.Name AS CompanyName, v.TenantBreakNotice,
(CASE WHEN v.TenantBreakNotice < CONVERT(varchar(10), getdate(), 103) THEN 'Expiry'
WHEN TenantBreakNotice IS NULL THEN 'Expiry'
ELSE 'Break'
END) AS [LeaseEventType]
FROM HeadLease hl OUTER APPLY
(VALUES (CASE WHEN SUBSTRING(hl.TenantBreakNotice, LEN(hl.TenantBreakNotice), 1) = 'M'
THEN CONVERT(VARCHAR(10), DATEADD(DAY, -365/(12/SUBSTRING(hl.TenantBreakNotice, 1, LEN(hl.TenantBreakNotice) -1)), hl.TenantBreakDate), 103)
WHEN SUBSTRING(hl.TenantBreakNotice, LEN(hl.TenantBreakNotice), 1) = 'Y'
THEN CONVERT(VARCHAR(10), DATEADD(DAY, -365*(SUBSTRING(hl.TenantBreakNotice,1, LEN(hl.TenantBreakNotice)-1)), hl.TenantBreakDate), 103)
ELSE hl.TenantBreakNotice
END) v(TenantBreakNotice);
Of course, the logic is incorrect, because you are comparing dates as strings. However, that is something you need to figure out yourself. Don't convert dates to strings for date operations. And, you should output the results as YYYY-MM-DD so the formats are unambiguous.
As #Juergen pointed out, you can't do exactly what you want, but you could compute the first CASE expression in a subquery, and then use it an outer wrapping query:
WITH cte AS (
SELECT
Name AS CompanyName,
CASE WHEN SUBSTRING(HeadLease.TenantBreakNotice,LEN(HeadLease.TenantBreakNotice), 1) = 'M'
THEN CONVERT(VARCHAR(10), DATEADD(DD,-365/(12/SUBSTRING(HeadLease.TenantBreakNotice,1,LEN(HeadLease.TenantBreakNotice)-1)),HeadLease.TenantBreakDate), 103)
WHEN SUBSTRING(HeadLease.TenantBreakNotice,LEN(HeadLease.TenantBreakNotice),1) = 'Y'
THEN CONVERT(VARCHAR(10), DATEADD(DD,-365*(SUBSTRING(HeadLease.TenantBreakNotice,1,LEN(HeadLease.TenantBreakNotice)-1)),HeadLease.TenantBreakDate), 103)
ELSE HeadLease.TenantBreakNotice
END AS [TenantBreakNotice]
FROM HeadLease
)
SELECT
Name,
TenantBreakNotice,
CASE WHEN TenantBreakNotice < CONVERT(varchar(10), getdate(), 103)
THEN 'Expiry'
WHEN TenantBreakNotice IS NULL THEN 'Expiry'
ELSE 'Break'
END AS [LeaseEventType]
FROM cte;
Use CTEs (common table expressions). In CTEs you can refer to the columns from the previous CTE, so you can split the CASE logic like you would like.
Example:
WITH
CTE_1 AS
(
SELECT
*
,CASE
WHEN SUBSTRING(HeadLease.TenantBreakNotice,LEN(HeadLease.TenantBreakNotice),1) = 'M'
THEN CONVERT(VARCHAR(10), DATEADD(DD,-365/(12/SUBSTRING(HeadLease.TenantBreakNotice,1,LEN(HeadLease.TenantBreakNotice)-1)),HeadLease.TenantBreakDate), 103)
WHEN SUBSTRING(HeadLease.TenantBreakNotice,LEN(HeadLease.TenantBreakNotice),1) = 'Y'
THEN CONVERT(VARCHAR(10), DATEADD(DD,-365*(SUBSTRING(HeadLease.TenantBreakNotice,1,LEN(HeadLease.TenantBreakNotice)-1)),HeadLease.TenantBreakDate), 103)
ELSE
HeadLease.TenantBreakNotice
END AS [TenantBreakNotice]
...
),
CTE_2 AS
(
SELECT
*
,CASE
WHEN [TenantBreakNotice] < CONVERT(varchar(10),getdate(),103) THEN 'Expiry'
WHEN [TenantBreakNotice] IS NULL THEN 'Expiry'
ELSE 'Break'
END AS [LeaseEventType]
FROM
CTE_1
)
SELECT * FROM CTE_2
You can move the first case expression into your from by using a derived table/subquery like so:
select
cc.Name as CompanyName
, convert(varchar(10),hl.[TenantBreakNotice],103) as TenantBreakNotice
, case
when hl.[TenantBreakNotice] < getdate() then 'Expiry'
when hl.[TenantBreakNotice] is null then 'Expiry'
else 'Break'
end as [LeaseEventType]
from (
select *,
case
when substring(HeadLease.TenantBreakNotice,len(HeadLease.TenantBreakNotice),1) = 'M'
then dateadd(day,-365/(12/substring(HeadLease.TenantBreakNotice,1,len(HeadLease.TenantBreakNotice)-1)),HeadLease.TenantBreakDate)
when substring(HeadLease.TenantBreakNotice,len(HeadLease.TenantBreakNotice),1) = 'Y'
then dateadd(day,-365*(substring(HeadLease.TenantBreakNotice,1,len(HeadLease.TenantBreakNotice)-1)),HeadLease.TenantBreakDate)
else HeadLease.TenantBreakNotice
end as [TenantBreakNotice]
from HeadLease
) as hl
inner join CompanyContact cc
on cc....
Notes:
Don't compare strings as dates, this is going to lead to problems; especially with the style you specified (103).
Bad Habits to Kick : Using shorthand with date/time operations - Aaron Bertrand
Bad habits to kick : mis-handling date / range queries - Aaron Bertrand

mssql distinct count in subquery

I have sql something like
select
name,col2,col3,date
from
table1 join on few tables
And result is
name col2 col3 date
a a a datetime1
a a a datetime1
b b b datetime2
b b b datetime3
and i dont know how to do, but i need to replace date column with column which will
shows count of working days for every name/row
I though about subquery like
,(select COUNT(distinct DATENAME(dw, date) NOT IN ('Saturday', 'Sunday')) where name = '...' from ... where ...) as WorkingDays
but i need help to get it working, thank you.
Perhaps you want this:
SELECT Name, C2,
WorkingDays = Sum(CASE WHEN Datename(DW, [date])IN( 'Saturday', 'Sunday' )
THEN 0 ELSE 1 END)
OVER (PARTITION BY Name)
FROM dbo.data
Here is the sql-fiddle with your sample-data: http://sqlfiddle.com/#!3/1279f/14/0
if my understanding is not wrong you just need a groupby of other columns and count of 'date'
select
name,col2,col3,count(date) as no_of_days
from
table1 join on few tables
where DATENAME(dw, date) NOT IN ('Saturday', 'Sunday')
group by name,col2,col3

How to properly GROUP BY datetime columns in specific intervals?

I am making a stored procedure in MSSQL and I want to group my results by the interval day, I am unsure if this is correct though:
(this is a portion)
INSERT #Results
(
[Day],
[Month],
[Year],
[Result]
)
SELECT
DATEPART(DD, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(MM, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(YY, DATEADD(MI, #GmtOffset, EventDate)),
Result = CASE WHEN #Metric = 'Take Rate' THEN NULL ELSE COUNT(*) END
FROM BundleEvent
WHERE BundleEventTypeId = CASE WHEN #Metric = 'Take Rate' THEN #TypeTakeId ELSE #BundleEventTypeId END
AND EventDate >= #StartTime AND EventDate <= #EndTime
GROUP BY
DATEPART(YY, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(MM, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(DD, DATEADD(MI, #GmtOffset, EventDate))
My fear is it is ultimately going to group the data by the date where the year or month do not matter. Does anyone know if I am doing this correctly? EventDate is the DateTime field that I want to do a GROUP BY on by interval of day.
Thanks
EDIT:
This is the correct way, a simple format exchange... sometimes I need more coffee.
INSERT #Results
(
[Date],
[Result]
)
SELECT
CAST(DATEADD(MI, #GmtOffset, BundleEvent.EventDate) AS DATE),
Result = CASE WHEN #Metric = 'Take Rate' THEN 0 ELSE COUNT(*) END
FROM dbo.BundleEvent WITH (NOLOCK)
JOIN dbo.BundleUser WITH (NOLOCK)
ON BundleEvent.BundleId = BundleUser.BundleId
JOIN dbo.Bundle WITH (NOLOCK)
ON BundleEvent.BundleId = Bundle.BundleId
WHERE BundleEvent.EventDate >= #StartTimeGmt AND BundleEvent.EventDate <= #EndTimeGmt
AND BundleEvent.BundleEventTypeId = CASE WHEN #Metric = 'Take Rate' THEN #TypeTakeId ELSE #BundleEventTypeId END
AND BundleUser.UserId = CASE WHEN #UserId IS NULL THEN BundleUser.UserId ELSE #UserId END
AND Bundle.BundleType = 1
GROUP BY
CAST(DATEADD(MI, #GmtOffset, BundleEvent.EventDate) AS DATE)
Then I do a sub compare with the format exchange:
CAST(
(SELECT COUNT(*)
FROM dbo.BundleEvent WITH (NOLOCK)
JOIN dbo.BundleUser WITH (NOLOCK)
ON BundleEvent.BundleId = BundleUser.BundleId
JOIN dbo.Bundle WITH (NOLOCK)
ON BundleEvent.BundleId = Bundle.BundleId
WHERE CAST(DATEADD(MI, #GmtOffset, BundleEvent.EventDate) AS DATE) = [Date]
AND BundleEvent.BundleEventTypeId = #TypeTakeId
AND BundleUser.UserId = CASE WHEN #UserId IS NULL THEN BundleUser.UserId ELSE #UserId END
AND Bundle.BundleType = 1)
AS DECIMAL(5,2)
So in essence the query is matching up correctly and using a new format.
Since you are not using and aggregates (except Count(*), Are you sure you want a GROUP BY? Using DISTINCT will produce a single insert entry for all matching dates; perhaps something like:
DECLARE #DATE_GMT datetime
SET #DATE_GMT=DATEADD(MI, #GmtOffset, EventDate)
INSERT #Results
(
[Day],
[Month],
[Year],
[Result]
)
SELECT DISTINCT
DATEPART(DD, #DATE_GMT),
DATEPART(MM, #DATE_GMT),
DATEPART(YY, #DATE_GMT),
Result = CASE WHEN #Metric = 'Take Rate' THEN NULL ELSE COUNT(*) END
FROM BundleEvent
WHERE BundleEventTypeId = CASE WHEN #Metric = 'Take Rate' THEN #TypeTakeId ELSE #BundleEventTypeId END
AND EventDate Between #StartTime AND #EndTime

SQL Server 2008 SSRS Total/Combine values from two tables to output into a dataset

I have a SSRS report that needs to switch between three different datasets i.e. order types = Consumable, Service and Total.
I have two queries one for Consumable and one for Service as shown below. I tried putting a union between them but it doesn't seem to be totalling the results i.e. adding the two together. How can I do this?
SELECT COUNT(orderheader.orderid) AS [Consumable Order Amount],
CONVERT(DATE, orderheader.datecreated) AS [Date],
CASE
WHEN orderheader.webref = '' THEN 'Call Centre'
ELSE 'Web'
END AS [Order Type]
FROM orderheader
WHERE CONVERT(DATE, orderheader.datecreated) >= '21 February 2011'
AND CONVERT(DATE, orderheader.datecreated) <= '20 March 2011'
GROUP BY CONVERT(DATE, orderheader.datecreated),
CASE
WHEN orderheader.webref = '' THEN 'Call Centre'
ELSE 'Web'
END
SELECT COUNT(serviceid) AS [Service Order Amount],
CONVERT(DATE, datecreated) AS [Date],
CASE
WHEN serviceorder.webref = '' THEN 'Call Centre'
ELSE 'Web'
END AS SOURCE
FROM serviceorder
WHERE ( CONVERT(DATE, datecreated) >= '21 February 2011' )
AND ( CONVERT(DATE, datecreated) <= '20 March 2011' )
GROUP BY CONVERT(DATE, datecreated),
CASE
WHEN serviceorder.webref = '' THEN 'Call Centre'
ELSE 'Web'
END
ORDER BY [Date]
Can you try something like this for the combined dataset?
;WITH Combined AS
(
SELECT orderid AS id,
datecreated as [datecreated],
webref as [webref]
FROM orderheader
UNION ALL
SELECT serviceid AS id,
datecreated as [datecreated],
webref as [webref]
FROM serviceorder
)
SELECT COUNT(id) AS [Service Order Amount],
CONVERT(DATE, datecreated) AS [Date],
CASE
WHEN webref = '' THEN 'Call Centre'
ELSE 'Web'
END AS SOURCE
FROM Combined
WHERE ( CONVERT(DATE, datecreated) >= '21 February 2011' )
AND ( CONVERT(DATE, datecreated) <= '20 March 2011' )
GROUP BY CONVERT(DATE, datecreated),
CASE
WHEN webref = '' THEN 'Call Centre'
ELSE 'Web'
END
ORDER BY [Date]

Resources