SQL Query with optional aggregate and group possible? - sql

I need to run a huge query with the option to sum up one column. I'm wondering if it would be possible to do something like the following:
declare #sumIt bit
set #sumIt = 1
select ID, Name, CASE WHEN #sumIt=1 THEN sum(Time) ELSE Time END [timeCol]
from Visits
where ID = 123
Group by ID, Name, CASE WHEN #sumIt=1 THEN '' ELSE Time END
Right now I get an error:
Msg 8120, Level 16, State 1, Line 4
Column 'Visits.Time' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

I think you can do the following:
declare #sumIt bit
set #sumIt = 1
select ID, Name,
(CASE WHEN #sumIt=1 THEN sum(Time) ELSE min(Time) END) [timeCol]
from Visits
where ID = 123
Group by ID, Name, (CASE WHEN #sumIt=1 THEN '' ELSE Time END)
You can apply an aggregation function to the group by variables. This isn't commonly done, but it solves your problem.

I haven't tested it, but this should work fine:
select ID, Name, sum([Time]) [timeCol]
from Visits
where ID = 123
AND #sumint = 1
Group by ID, Name
UNION ALL
select ID, Name, [Time] [timeCol]
from Visits
where ID = 123
AND #sumint = 0
Group by ID, Name, [Time]

I guess you could take the dynamic sql approach. Not crazy about it myself, i'd just use the IF
declare #sumIt bit, #sql VARCHAR(1000)
set #sumIt = 0
SET #sql = 'select ID, Name, ' + CASE WHEN #sumIt=1 THEN 'sum(Time)' ELSE 'Time' END + ' [timeCol]
from Visits
where ID = 123
Group by ID, Name' + CASE WHEN #sumIt=1 THEN '' ELSE ',Time' END
EXEC #sql

Related

Add column name to a variable and use it in later calculation in WHERE clause

I have a problem. I need to determine the name of the column under which the calculations will continue. So I wrote a select:
DECLARE #column VARCHAR(MAX)
DECLARE #ColumnA VARCHAR(MAX)
DECLARE #ColumnB VARCHAR(MAX)
SET #ColumnA = 'RegistrationDate'
SET #ColumnB = 'EntryDate'
SET #column = CASE
WHEN CONVERT(DATE,GETDATE()) NOT IN (
'2021-08-04','2021-08-05','2021-08-06','2021-08-07','2021-08-08','2021-08-09','2021-08-10','2021-09-07','2021-09-08','2021-09-09','2021-09-10','2021-09-11',
'2021-09-12','2021-09-13','2021-10-05','2021-10-06','2021-10-07','2021-10-08','2021-10-09','2021-10-10','2021-10-11','2021-11-09','2021-11-10','2021-11-11','2021-11-12','2021-11-13','2021-11-14','2021-11-15','2021-12-07',
'2021-12-08','2021-12-09','2021-12-10','2021-12-11','2021-12-12','2021-12-13'
) THEN
QUOTENAME(#Column)
ELSE
QUOTENAME(#ColumnB)
END
SELECT #column
which returns me [RegistrationDate] or [EntryDate] and stores this in variable #column. Now, when I know under which column should I calculate, I want to insert this variable #column in to my main select one of the WHERE clause:
DECLARE #column VARCHAR(MAX)
DECLARE #ColumnA VARCHAR(MAX)
DECLARE #ColumnB VARCHAR(MAX)
SET #ColumnA = 'RegistrationDate'
SET #ColumnB = 'EntryDate'
SET #column = CASE
WHEN CONVERT(DATE,GETDATE()) NOT IN (
'2021-08-04','2021-08-05','2021-08-06','2021-08-07','2021-08-08','2021-08-09','2021-08-10','2021-09-07','2021-09-08','2021-09-09','2021-09-10','2021-09-11',
'2021-09-12','2021-09-13','2021-10-05','2021-10-06','2021-10-07','2021-10-08','2021-10-09','2021-10-10','2021-10-11','2021-11-09','2021-11-10','2021-11-11','2021-11-12','2021-11-13','2021-11-14','2021-11-15','2021-12-07',
'2021-12-08','2021-12-09','2021-12-10','2021-12-11','2021-12-12','2021-12-13'
) THEN
QUOTENAME(#Column)
ELSE
QUOTENAME(#ColumnB)
END
SELECT
CASE WHEN final.Branch IS NULL THEN 'Total'
ELSE final.Branch
END AS 'Branch',
final.TR
FROM
(
SELECT
CASE
WHEN main.BRANCHNO = 1 THEN 'One'
WHEN main.BRANCHNO = 2 THEN 'Two'
WHEN main.BRANCHNO = 3 THEN 'Three'
WHEN main.BRANCHNO = 4 THEN 'Four'
WHEN main.BRANCHNO = 5 THEN 'Five'
WHEN main.BRANCHNO = 6 THEN 'Six'
END AS 'Branch',
COUNT(*) AS 'TR'
FROM
(
SELECT
*
FROM
[TABLE]
WHERE
Status = 100
AND
BRANCHNO IN (1,2,3,4,5,6)
AND
Type = 'TR'
AND
**#column** = CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) = 'Monday' THEN getdate()-3 ELSE getdate()-1 END
)
) AS main
GROUP BY
main.BRANCHNO WITH ROLLUP
) AS final
But when I execute query it returns me an error:
Msg 241, Level 16, State 1, Line 11 Conversion failed when converting
date and/or time from character string.
I imagined everything very simple: I put a column name into a variable, and then, that name placed at the beginning of the WHERE clause will be recognized as the column name and then *= CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) etc will do all work.
But that did not happen. Maybe someone knows why and maybe they know how to solve this task?
You can't use a variable to reference a column name. #column is just a piece of data, which just so happens to contain a column name as a string, but it's still just a string, not actually a reference to a column in a table.
Some options you have seem to be...
AND CASE #column WHEN 'RegistrationDate' THEN RegistrationDate
WHEN 'EntryDate' THEN EntryDate
END
=
CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) = 'Monday' THEN getdate()-3 ELSE getdate()-1 END)
Or, have two queries which only differ in the column being referenced...
IF (#column = 'RegistrationDate')
<query1>
ELSE IF (#column = 'EntryDate')
<query2>
Or "Dynamic SQL" where you build up a new string with your SQL code and execute that by call sp_executesql (assuming this is SQL Server, which it appears to be).
I recommend reading this : https://www.sommarskog.se/dyn-search.html
EDIT: A pure SQL alternative, assuming SQL Server
DECLARE #mode INT = CASE
WHEN CONVERT(DATE,GETDATE()) NOT IN (
'2021-08-04','2021-08-05','2021-08-06','2021-08-07','2021-08-08','2021-08-09','2021-08-10','2021-09-07','2021-09-08','2021-09-09','2021-09-10','2021-09-11',
'2021-09-12','2021-09-13','2021-10-05','2021-10-06','2021-10-07','2021-10-08','2021-10-09','2021-10-10','2021-10-11','2021-11-09','2021-11-10','2021-11-11','2021-11-12','2021-11-13','2021-11-14','2021-11-15','2021-12-07',
'2021-12-08','2021-12-09','2021-12-10','2021-12-11','2021-12-12','2021-12-13'
) THEN
0
ELSE
1
END;
DECLARE #filter_date DATE = CONVERT(DATE, CASE WHEN DATENAME(dw, getdate()) = 'Monday' THEN getdate()-3 ELSE getdate()-1 END;
WITH
source AS
(
SELECT
*
FROM
[TABLE]
WHERE
Status = 100
AND BRANCHNO IN (1,2,3,4,5,6)
AND Type = 'TR'
),
filtered_source AS
(
SELECT 0 AS mode, * FROM source WHERE RegistrationDate = #filter_date
UNION ALL
SELECT 1 AS mode, * FROM source WHERE EntryDate = #filter_date
)
SELECT
COALESCE(
CASE
WHEN BRANCHNO = 1 THEN 'One'
WHEN BRANCHNO = 2 THEN 'Two'
WHEN BRANCHNO = 3 THEN 'Three'
WHEN BRANCHNO = 4 THEN 'Four'
WHEN BRANCHNO = 5 THEN 'Five'
WHEN BRANCHNO = 6 THEN 'Six'
END,
'Total'
)
AS 'Branch',
COUNT(*) AS 'TR'
FROM
filtered_source
WHERE
mode = #mode
GROUP BY
GROUPING SETS (
(mode),
(mode, BRANCHNO)
);
By always including mode in the GROUPING SETS, the optimiser might be able to yield a better execution plan for the two scenarios.
Still read the link given above though, at the very least to understand why this is necessary, or perhaps why it doesn't quite manage to yield the best execution plan.

SQL Server - aggregate if only one distinct value + nulls without ansi warnings

Suppose I have a data like this
first_name last_name city
John Bon Jovi null
John Lennon null
John Deer null
And I want to create aggregating query which will return json which looks like this
{ "first_name": "John", "city": null }
Essentially, the query should check if there's only one distinct value within each column and if it is, put this value to json. All non-null columns are relatively easy to get with a query like this:
select
case when count(distinct first_name) = 1 then max(first_name) end as first_name,
case when count(distinct last_name) = 1 then max(last_name) end as last_name,
case when count(distinct city) = 1 then max(city) end as city
from ...
for json path, without_array_wrapper
or
select
case when max(first_name) = min(first_name) then max(first_name) end as first_name,
case when max(last_name) = min(last_name) then max(last_name) end as last_name,
case when max(city) = min(city) then max(city) end as city
from ...
for json path, without_array_wrapper
The result of the queries above is json like this {"first_name":"John"}. But then there are problems with nulls. Problem (1) - queries above do not take nulls into account, so if I have data like this
first_name last_name city
----------------------------------
John Lennon null
John Lennon null
John null null
Then last name is also included in the resulting json
{ "first_name": "John", "last_name": "Lennon" }
Ok, that's understandable (cause ...Null value is eliminated by an aggregate...) and I can solve it with a query like this:
select
case when count(distinct first_name) = 1 and count(first_name) = count(*) then max(first_name) end as first_name,
case when count(distinct last_name) = 1 and count(last_name) = count(*) then max(last_name) end as last_name,
case when count(distinct city) = 1 and count(city) = count(*) then max(city) end as city
from ...
for json path, without_array_wrapper
But there are other problems with nulls I can't really solve neatly for now. Problem (2) - I want to have also "city":null in my json. Of course I can do something like this
...
case when count(city) = 0 then 'null' end as city
...
and then replace string null with real nulls, but it's not very neat. Another annoying thing is (3) - I'd really like to get rid of warnings
Warning: Null value is eliminated by an aggregate or other SET operation.
without turning ANSI_WARNINGS off. For now I can only think about using some placeholders with isnull which doesn't look like a clean solution
...
case when count(distinct isnull(city, 'null')) = 1 then max(city) end as city
...
So, any ideas on how to elegantly solve problems (2) and (3)? see examples in db<>fiddle.
Ok, so nobody posted any answers so far, I have thought of one way doing it. It's not perfect, but it seems to work.
So the idea is to use #var = #var + 1 trick inside of select. But it should be a bit more complicated:
declare
#first_name varchar(4), #first_name_state tinyint = 0,
#last_name varchar(4), #last_name_state tinyint = 0,
#city varchar(4), #city_state tinyint = 0,
#country varchar(10), #country_state tinyint = 0,
#result nvarchar(max) = '{}';
select
#first_name_state =
case
when #first_name_state = 0 then 1
when #first_name_state = 1 and #first_name = t.first_name then 1
when #first_name_state = 1 and #first_name is null and t.first_name is null then 1
else 2
end,
#first_name = t.first_name,
#last_name_state =
case
when #last_name_state = 0 then 1
when #last_name_state = 1 and #last_name = t.last_name then 1
when #last_name_state = 1 and #last_name is null and t.last_name is null then 1
else 2
end,
#last_name = t.last_name,
#city_state =
case
when #city_state = 0 then 1
when #city_state = 1 and #city = t.city then 1
when #city_state = 1 and #city is null and t.city is null then 1
else 2
end,
#city = t.city,
#country_state =
case
when #country_state = 0 then 1
when #country_state = 1 and #country = t.country then 1
when #country_state = 1 and #country is null and t.country is null then 1
else 2
end,
#country = t.country
from Table1 as t;
if #first_name_state = 1
set #result = json_modify(json_modify(#result,'$.first_name','null'),'strict $.first_name',#first_name);
if #last_name_state = 1
set #result = json_modify(json_modify(#result,'$.last_name','null'),'strict $.last_name',#last_name);
if #city_state = 1
set #result = json_modify(json_modify(#result,'$.city','null'),'strict $.city',#city);
if #country_state = 1
set #result = json_modify(json_modify(#result,'$.country','null'),'strict $.country',#country);
select #result;
----------------------------------
{"first_name":"John","city":null}
see db<>fiddle with examples.
Please note that, according to Microsoft docs you shouldn't use this variable aggregation assignment trick cause some of the statements can be called more than once.
Don't use a variable in a SELECT statement to concatenate values (that
is, to compute aggregate values). Unexpected query results may occur.
Because, all expressions in the SELECT list (including assignments)
aren't necessarily run exactly once for each output row.
I hope in this case it should work fine cause it's not exactly an aggregation, and it's ok if these statements will be called more than once per row.
Still, you can find some useful links in this answer.

How to write if exist statement that would run a different select depending if it exists or not

I am trying to convert a sql if exists statement into a SSRS valid format to run a report on CRM.
CRM report doesn't accept the report on upload if I have a if exists method, I'm having troubles figuring out what I can use in its place.
IF EXISTS(select * from dbo.FC where dbo.FC.ContactID in (select dbo.AV.so_contactid from dbo.AV))
begin
select [STATEMENT 1]
from dbo.AV CRMAF_so_AV join
dbo.FC c
on CRMAF_so_AV.so_contactid = c.ContactID;
end
else
begin
select [STATEMENT 2]
from dbo.AV CRMAF_so_AV join
dbo.FA c
on CRMAF_so_AV.so_contactid = c.AccountID;
end;
I want to be able to either run the select [STATEMENT 1] if the condition is true else I want to run select [STATEMENT 2]
I have managed to get this to work by doing a LEFT JOIN instead of a JOIN.
select [STATEMENT 1 + 2 all columns needed]
from dbo.AV CRMAF_so_AV
left join dbo.FC c on CRMAF_so_AV.so_contactid = c.ContactID;
left join dbo.FA a on CRMAF_so_AV.so_contactid = a.AccountID;
This now runs if its an account or a contact.
Try this -
You have to put your entire statement in #select1 and #select1.
declare #statement1 as varchar(max);
declare #statement2 as varchar(max);
SET #statement1 = 'SELECT 1'
SET #statement2 = 'SELECT 2'
IF EXISTS(select * from dbo.FC where dbo.FC.ContactID in (select dbo.AV.so_contactid from dbo.AV))
BEGIN
EXEC (#statement1)
END
ELSE
BEGIN
EXEC (#statement2)
END
Instead of using if exists can you not get a count of records that meet the criteria and then if its 1 or greater run a different query as apposed to if it was equal to 0.
let me know if I am missing something what you are trying to achieve.
sorry i am unable to put comments due to having a new account so my reputation is low.
I think you need something like this:
WITH PreSelection AS (
SELECT
AV.ID AS AVID,
(SELECT TOP(1) c.ContactID FROM dbo.FC c WHERE c.ContactID = AV.so_contactid) AS ContactID,
(SELECT TOP(1) c.ContactID FROM dbo.FA c WHERE c.AccountID = AV.so_contactid) AS AccountID
FROM dbo.AV
)
SELECT
AVID,
ISNULL(
CASE WHEN ContactID IS NULL
THEN (SELECT TOP(1) AccountName FROM dbo.FA WHERE FA.AccountID = AccountID)
ELSE (SELECT TOP(1) LTRIM(RTRIM(ISNULL(FirstName, '') + ' ' + ISNULL(LastName, ''))) FROM dbo.FC WHERE FC.ContactID = ContactID)
END, '') AS ContactName
FROM PreSelection
A few things to note:
When SSRS evaluates query it expects the resluts to always have the same structure in terms of column names and types.
So you CANNOT do something like this..
IF #x=#y
BEGIN
SELECT Name, Age FROM employees
END
ELSE
BEGIN
SELECT DeptID, DeptName, DeptEMpCOunt FROM departments
END
... as this will return different types and column names and column counts.
What you CAN DO is this..
DECLARE #t TABLE(resultType int, colA varchar(128), colB int, colC varchar(128), colD int)
IF #x=#y
BEGIN
INSERT INTO #t(resultType, colA, ColB)
SELECT 1 as resultType, Name, Age FROM employees
END
ELSE
BEGIN
INSERT INTO #t(resultType, colB, colC, colD)
SELECT 2 AS resultType, DeptID, DeptName, DeptEmpCount FROM departments
END
SELECT * FROM #t
Al we are doing is creating a table that can handle all variations of the data and putting the results into whatever columns can accommodate that data type.
This will always return the same data structure so SSRS will be happy, then you will need to handle which columns to display your data from based on what gets returned, hence why I added the result type to the results so you can test that from within the report.

Case when rowcount

SELECT distinct ID from table
Result
Letter
1 A
2 B
How can I make the select display the following when the result is as above:
What I was thinking but not working yet:
SELECT CASE WHEN ##ROWCOUNT(ID) = 2 THEN 'AB'
ELSE ID END AS ID
FROM table
Result would be
Letter
1 AB
Your requirements really aren't clear.
Based on various assumptions, this might give you what you want.
SELECT Count(*) OVER (PARTITION BY 937)
, CASE WHEN Count(*) OVER (PARTITION BY 937) = 2 THEN
'AB'
ELSE
id
END As id
, id
FROM your_table
Try:
Select
CASE WHEN (ROW_NUMBER() OVER (ORDER BY [ID])) = 2 THEN 'AB'
ELSE [ID] END AS [ID]
FROM table
group by
[ID]
What's above will also only work if [ID] and 'AB' are the same data type. If not, then you'll need to cast [ID] to a varchar in your ELSE statement. That'd be: ELSE cast([ID] as varchar(8)) END AS [ID]
Am not sure what you are trying to achieve. But to me it looks like this is what you are trying..
Declare a Variable and initialize it with Distinct Count of ID and use it in select
Declare #cnt int
SELECT #cnt=count(distinct ID) from table
SELECT CASE WHEN #cnt = 2 THEN 'AB'
ELSE ID END AS ID
FROM table

SQL Server: issue with adapting Rank statement in Select

I have a dynamic stored procedure that start as follows with the declaration of a temp table and then an insert statement.
Can someone here tell me how I need to adapt the following line so that it creates a rank based on the groupCount (desc) instead of by Count?
When I just say groupCount instead of Count then it returns:
Invalid column name 'groupCount'
The line in question:
RANK() OVER(ORDER BY COUNT(*) desc, <sel>) [Rank],
My procedure (first part):
SET #sql = N' DECLARE #temp AS TABLE
(
ranking int,
item nvarchar(100),
totalCount int,
matchCount int,
groupCount int,
groupName nvarchar(100)
)
INSERT INTO #temp
(
ranking,
item,
totalCount,
matchCount,
groupCount,
groupName
)
SELECT RANK() OVER(ORDER BY COUNT(*) desc, <sel>) [Rank],
<sel>,
COUNT(*) AS totalCount,
SUM(CASE WHEN suggestedAction = recommendation THEN 1 ELSE 0 END) AS matchCount,
ROUND(100 * AVG(CASE WHEN suggestedAction = recommendation THEN 1.0 ELSE 0.0 END), 0) AS groupCount,
''currentMonth'' AS groupName
FROM LogEsc
WHERE dateEsc LIKE ''' + #date0 + '%''
AND EID LIKE ''PE%''
GROUP BY <sel>
ORDER BY groupCount desc, <sel>
-- ...
Many thanks in advance for any help with this, Tim.
You can't use the alias.
use
ORDER BY ROUND(100 * AVG(CASE WHEN suggestedAction = recommendation THEN 1.0 ELSE 0.0 END), 0)