Variable in Select Statement - tsql

Here is the query I have built to derive tech number. I can't get it to work. I get this error message:
Subquery returned more than 1 value.
The query:
declare #TechNum int =
(Select Case When CF_Technician='David' Then 9 When
CF_Technician='David N' Then 9 When
CF_Technician='David Nunez' Then 9 When
CF_Technician='Joe Nicholson' Then 8 When
CF_Technician='Joe Nicholson' Then 8 When
CF_Technician='Josh Fogleman' Then 7 When
else 0
End
From OptimazationItems)
Select
ItemID,
TechNum=#TechNum
From
OptimazationItems
What is wrong? Why can't I use a variable in the select statement? What is the best way to accomplish this?
I know I can create another view and join it afterwards. Is there a way to do it in one query?

You can use a variable, but you have assign to it a subquery that returns at most one row. The subquery you assign obviously returns more than one row. Run the subquery in isolation, you'll see that it returns more than one row.
Rewrite the subquery to only return (at most) one row. You'll probably want to add a WHERE clause or a SELECT TOP 1 + ORDER BY some_column to select a specific row.
It looks from the comments that you didn't understand what I was saying. What you probably meant to write is the following.
SELECT
ItemID,
TechNum=CASE CF_Technician
WHEN 'David' THEN 9
WHEN 'David N' THEN 9
WHEN 'David Nunez' THEN 9
WHEN 'Joe Nicholson' THEN 8
WHEN 'Joe Nicholson' THEN 8
WHEN 'Josh Fogleman' THEN 7
ELSE 0
END
FROM
OptimazationItems
You cannot write this select statement using a variable.

Related

Union aggregate datemultiranges in Postgres

I have a table with a datemultirange type.
I want to combine (compute the union) of multiple rows.
SELECT
type_id,
range_agg(some_date_ranges)
FROM foo
GROUP BY type_id
My table foo looks something like this:
id (int)
some_date_ranges(datemultirange)
type_id (int)
1
{[2016-07-02,2016-07-24),[2017-10-05,2017-10-12),[2018-05-23,2021-03-08)}
4
2
{[2016-07-03,2016-07-24),[2017-10-03,2017-10-13),[2018-05-23,2021-04-08)}
4
2
{[2016-07-27,2016-07-28),[2017-10-02,2017-10-14),[2018-05-23,2021-05-08)}
5
If I try to run the function above, I get the error function range_agg(datemultirange) does not exist. Similar with the function aggregate_union.
The only solution I've found so far is with a CTE that uses unnest and then using range_agg after that. But it's inelegant. Is there a better way?
Details
Postgres 14
In Postgres 14 as simple as I could get it is something like:
create table multi_range_test(date_mr datemultirange);
insert into
multi_range_test
values
('{[2016-07-02,2016-07-24),[2017-10-05,2017-10-12), [2018-05-23,2021-03-08)}'::datemultirange),
('{[2016-07-03,2016-07-24),[2017-10-03,2017-10-13),[2018-05-23,2021-04-08)}'::datemultirange) ,
('{[2016-07-27,2016-07-28),[2017-10-02,2017-10-14),[2018-05-23,2021-05-08)}'::datemultirange);
select range_agg(mr) from (select unnest(date_mr) from multi_range_test) as t(mr) ;
range_agg
---------------------------------------------------------------------------------------------------
{[07/02/2016,07/24/2016),[07/27/2016,07/28/2016),[10/02/2017,10/14/2017),[05/23/2018,05/08/2021)}
Compared to range_agg(anymultirange) in Postgres 15:
select range_agg(date_mr) from multi_range_test ;
range_agg
---------------------------------------------------------------------------------------------------
{[07/02/2016,07/24/2016),[07/27/2016,07/28/2016),[10/02/2017,10/14/2017),[05/23/2018,05/08/2021)}

postgres `order by` argument type

What is the argument type for the order by clause in Postgresql?
I came across a very strange behaviour (using Postgresql 9.5). Namely, the query
select * from unnest(array[1,4,3,2]) as x order by 1;
produces 1,2,3,4 as expected. However the query
select * from unnest(array[1,4,3,2]) as x order by 1::int;
produces 1,4,3,2, which seems strange. Similarly, whenever I replace 1::int with whatever function (e.g. greatest(0,1)) or even case operator, the results are unordered (on the contrary to what I would expect).
So which type should an argument of order by have, and how do I get the expected behaviour?
This is expected (and documented) behaviour:
A sort_expression can also be the column label or number of an output column
So the expression:
order by 1
sorts by the first column of the result set (as defined by the SQL standard)
However the expression:
order by 1::int
sorts by the constant value 1, it's essentially the same as:
order by 'foo'
By using a constant value for the order by all rows have the same sort value and thus aren't really sorted.
To sort by an expression, just use that:
order by
case
when some_column = 'foo' then 1
when some_column = 'bar' then 2
else 3
end
The above sorts the result based on the result of the case expression.
Actually I have a function with an integer argument which indicates the column to be used in the order by clause.
In a case when all columns are of the same type, this can work: :
SELECT ....
ORDER BY
CASE function_to_get_a_column_number()
WHEN 1 THEN column1
WHEN 2 THEN column2
.....
WHEN 1235 THEN column1235
END
If columns are of different types, you can try:
SELECT ....
ORDER BY
CASE function_to_get_a_column_number()
WHEN 1 THEN column1::varchar
WHEN 2 THEN column2::varchar
.....
WHEN 1235 THEN column1235::varchar
END
But these "workarounds" are horrible. You need some other approach than the function returning a column number.
Maybe a dynamic SQL ?
I would say that dynamic SQL (thanks #kordirko and the others for the hints) is the best solution to the problem I originally had in mind:
create temp table my_data (
id serial,
val text
);
insert into my_data(id, val)
values (default, 'a'), (default, 'c'), (default, 'd'), (default, 'b');
create function fetch_my_data(col text)
returns setof my_data as
$f$
begin
return query execute $$
select * from my_data
order by $$|| quote_ident(col);
end
$f$ language plpgsql;
select * from fetch_my_data('val'); -- order by val
select * from fetch_my_data('id'); -- order by id
In the beginning I thought this could be achieved using case expression in the argument of the order by clause - the sort_expression. And here comes the tricky part which confused me: when sort_expression is a kind of identifier (name of a column or a number of a column), the corresponding column is used when ordering the results. But when sort_expression is some value, we actually order the results using that value itself (computed for each row). This is #a_horse_with_no_name's answer rephrased.
So when I queried ... order by 1::int, in a way I have assigned value 1 to each row and then tried to sort an array of ones, which clearly is useless.
There are some workarounds without dynamic queries, but they require writing more code and do not seem to have any significant advantages.

sp_executesql vs user defined scalar function

In the table below I am storing some conditions like this:
Then, generally, in second table, I am having the following records:
and what I need is to compare these values using the right condition and store the result ( let's say '0' for false, and '1' for true in additional column).
I am going to do this in a store procedure and basically I am going to compare from several to hundreds of records.
What of the possible solution is to use sp_executesql for each row building dynamic statements and the other is to create my own scalar function and to call it for eacy row using cross apply.
Could anyone tell which is the more efficient way?
Note: I know that the best way to answer this is to make the two solutions and test, but I am hoping that there might be answered of this, based on other stuff like caching and SQL internal optimizations and others, which will save me a lot of time because this is only part of a bigger problem.
I don't see the need in use of sp_executesql in this case. You can obtain result for all records at once in a single statement:
select Result = case
when ct.Abbreviation='=' and t.ValueOne=t.ValueTwo then 1
when ct.Abbreviation='>' and t.ValueOne>t.ValueTwo then 1
when ct.Abbreviation='>=' and t.ValueOne>=t.ValueTwo then 1
when ct.Abbreviation='<=' and t.ValueOne<=t.ValueTwo then 1
when ct.Abbreviation='<>' and t.ValueOne<>t.ValueTwo then 1
when ct.Abbreviation='<' and t.ValueOne<t.ValueTwo then 1
else 0 end
from YourTable t
join ConditionType ct on ct.ID = t.ConditionTypeID
and update additional column with something like:
;with cte as (
select t.AdditionalColumn, Result = case
when ct.Abbreviation='=' and t.ValueOne=t.ValueTwo then 1
when ct.Abbreviation='>' and t.ValueOne>t.ValueTwo then 1
when ct.Abbreviation='>=' and t.ValueOne>=t.ValueTwo then 1
when ct.Abbreviation='<=' and t.ValueOne<=t.ValueTwo then 1
when ct.Abbreviation='<>' and t.ValueOne<>t.ValueTwo then 1
when ct.Abbreviation='<' and t.ValueOne<t.ValueTwo then 1
else 0 end
from YourTable t
join ConditionType ct on ct.ID = t.ConditionTypeID
)
update cte
set AdditionalColumn = Result
If above logic is supposed to be applied in many places, not just over one table, then yes you may think about function. Though I would used rather inline table-valued function (not scalar), because of there is overhead imposed with use of user defined scalar functions (to call and return, and the more rows to be processed the more time wastes).
create function ftComparison
(
#v1 float,
#v2 float,
#cType int
)
returns table
as return
select
Result = case
when ct.Abbreviation='=' and #v1=#v2 then 1
when ct.Abbreviation='>' and #v1>#v2 then 1
when ct.Abbreviation='>=' and #v1>=#v2 then 1
when ct.Abbreviation='<=' and #v1<=#v2 then 1
when ct.Abbreviation='<>' and #v1<>#v2 then 1
when ct.Abbreviation='<' and #v1<#v2 then 1
else 0
end
from ConditionType ct
where ct.ID = #cType
which can be applied then as:
select f.Result
from YourTable t
cross apply ftComparison(ValueOne, ValueTwo, t.ConditionTypeID) f
or
select f.Result
from YourAnotherTable t
cross apply ftComparison(SomeValueColumn, SomeOtherValueColumn, #someConditionType) f

Why SingleOrDefault result TOP(2) in SQL?

I am using EF4.0, and I wrote a query:
var query = context.Post.Where(p => p.Id == postId).SingleOrDefault();
I need only One post from this query. I thought SingleOrDefault() will generate "SELECT TOP(1) ...", but when I look into SQL Profiler, It was:
exec sp_executesql N'SELECT TOP (2)
[Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title],
[Extent1].[Slug] AS [Slug],
[Extent1].[PubDate] AS [PubDate],
[Extent1].[PostContent] AS [PostContent],
[Extent1].[Author] AS [Author],
[Extent1].[CommentEnabled] AS [CommentEnabled],
[Extent1].[AttachmentId] AS [AttachmentId],
[Extent1].[IsPublished] AS [IsPublished],
[Extent1].[Hits] AS [Hits],
[Extent1].[CategoryId] AS [CategoryId]
FROM [dbo].[Post] AS [Extent1]
WHERE [Extent1].[Id] = #p__linq__0',N'#p__linq__0 uniqueidentifier',#p__linq__0='ECD9F3BE-3CA9-462E-AE79-2B28C8A16E32'
I wonder why EF result in SELECT TOP (2)? I only need one post.
It selects top 2 so that if there are actually 2 or more than 2 records in the database, an exception would be thrown. If it only selects top 1 there would be no way to error out.
By asking for the SingleOrDefault of a sequence, you are asking for this behaviour:
if the sequence has exactly 0 elements, return the default for the sequence's element type
if the sequence has exactly 1 element, return the element
if the sequence has more than 1 element, throw
Doing a TOP (1) would empower the first two parts of this, but not the third. Only by doing a TOP (2) can we differentiate between exactly 1 record and more than 1 record.
If you don't want or need the third part of the above behviour, instead use FirstOrDefault.

Can someone help me figure out Oracle's (10g) AND/OR short circuitry?

Consider the following query and notice the CALCULATE_INCENTIVE function:
SELECT EMP.* FROM EMPLOYEES EMPS
WHERE
EMP.STATUS = 1 AND
EMP.HIRE_DATE > TO_DATE('1/1/2010') AND
EMP.FIRST_NAME = 'JOHN' AND
CALCULATE_INCENTIVE(EMP.ID) > 1000
ORDER BY EMPS.ID DESC;
I was under the impression that Oracle uses the same (or similar) short-circuitry that .NET uses in its and/or logic. For example, if EMP.STATUS = 2, it won't bother evaluating the rest of the expression since the entire expression would return false anyway.
In my case, the CALCULATE_INCENTIVE function is being called on every employee in the db rather than only on the 9 records that the first three WHERE expressions return. I've even tried putting parenthesis around the specific expressions that I want to group together for short-circuit evaluation, but I can't figure it out.
Anyone have any ideas how to get the CALCULATE_INCENTIVE not to be evaluated if any of the previous expressions return false?
One way is to put the primary criteria into a subquery that Oracle can't optimize away, then put the secondary criteria into the outer query. The easiest way to ensure that Oracle doesn't optimize out the subquery is to include rownum in the select statement:
SELECT * FROM (
SELECT EMP.*, ROWNUM
FROM EMPLOYEES EMPS
WHERE
EMP.STATUS = 1
AND EMP.HIRE_DATE > TO_DATE('1/1/2010')
AND EMP.FIRST_NAME = 'JOHN')
WHERE CALCULATE_INCENTIVE(ID) > 1000
ORDER BY EMPS.ID DESC;
Oracle supports short-circuit evaluation in PL/SQL. In SQL, however, the optimizer is free to evaluate the predicates in whatever order it desires, to push predicates into views and subqueries, and to otherwise transform the SQL statement as it sees fit. This means that you should not rely on predicates being applied in a particular order and makes the order predicates appear in the WHERE clause essentially irrelevant. The indexes that are available, the optimizer statistics that are present, the optimizer parameters, and system statistics are all vastly more important than the order of predicates in the WHERE clause.
In PL/SQL, for example, you can demonstrate this with a function that throws an error if it's actually called.
SQL> ed
Wrote file afiedt.buf
1 create function throw_error( p_parameter IN NUMBER )
2 return number
3 as
4 begin
5 raise_application_error( -20001, 'The function was called' );
6 return 1;
7* end;
SQL> /
Function created.
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_num NUMBER;
3 begin
4 l_num := 1;
5 if( l_num = 2 and throw_error( l_num ) = 2 )
6 then
7 null;
8 else
9 dbms_output.put_line( 'Short-circuited the AND' );
10 end if;
11 if( l_num = 1 or throw_error( l_num ) = 2 )
12 then
13 dbms_output.put_line( 'Short-circuited the OR' );
14 end if;
15* end;
16 /
Short-circuited the AND
Short-circuited the OR
PL/SQL procedure successfully completed.
In SQL, on the other hand, the order of operations is determined by the optimizer, not by you, so the optimizer is free to short-circuit or not short-circuit however it wants. Jonathan Gennick has a great article Subquery Madness! that discusses this in some detail. In your particular case, if you had a composite index on (FIRST_NAME, HIRE_DATE, STATUS) along with appropriate statistics, the optimizer would almost certainly use the index to evaluate the first three conditions and then only call the CALCULATE_INCENTIVE function for the ID's that met the other three criteria. If you created a function-based index on CALCULATE_INCENTIVE(id), the optimizer would likely use that rather than calling the function at all at runtime. But the optimizer would be perfectly free to decide to call the function for every row in either case if it decided that it would be more efficient to do so.