How to find out sum of a column with respect to another columns maximum and minimum - sql

I have worked with DB2, and I just moved to the SQL Server. I'm a bit confused by a query.
Lets suppose I have table data like
StoreID | Sales
A | 23
B | 50
B | 50
In this data with the stored procedure parameter, I wanted to roll up the sum of Sales. I will get a parameter of StoreID, but in this parameter I can get 'ALL' too.
In DB2 I can get all data using a stored procedure (which has StoreID in a parameter named ParameterStore) with a query like
if(ParameterStore= 'ALL') Then
Set Loc_min = X'00';
Set Loc_max = X'FF';
ELSE
Set Loc_min = ParameterStore;
Set Loc_max = ParameterStore;
END if;
Select Sum(Sales) From Store_data where StoreID between Loc_min and Loc_max;
How can I do this in SQL Server to get the same result?

You could check the value if it's ALL or some other value in OR:
DECLARE #store_id VARCHAR(20) -- or whatever length you have
SET #store_id = 'XYZ'
select sum(sales)
from store_data
where #store_id = 'ALL' or store_id = #store_id;

T-SQL syntax for IF is slightly different. You can use it like this:
/*
DECLARE #ParameterStore VARCHAR(10);
DECLARE #Loc_min INT;
DECLARE #Loc_max INT;
*/
IF #ParameterStore = 'ALL'
BEGIN
Set #Loc_min = 0x00;
Set #Loc_max = 0xFF;
END
ELSE
BEGIN
Set #Loc_min = #ParameterStore;
Set #Loc_max = #ParameterStore;
END;
Having said that, what you are trying to achieve could be done as follows:
-- DECLARE #ParameterStore VARCHAR(10);
SELECT SUM(Sales)
FROM Store_data
WHERE CASE
WHEN #ParameterStore = 'ALL' THEN -1
WHEN #ParameterStore = StoreID THEN -1
END = -1

Try this if you need hexadecimal comparison.
DECLARE
#ParameterStore VARCHAR(10)
SET #ParameterStore = 'ALL'
SELECT SUM(Sales)
FROM Store_data
WHERE StoreID BETWEEN (
CASE
WHEN #ParameterStore = 'ALL'
THEN 0x00
ELSE #ParameterStore
END)
AND (
CASE
WHEN #ParameterStore = 'ALL'
THEN 0xFF
ELSE #ParameterStore
END ) ;
DEMO

Related

SQL Grouping with condition

I want to sum rows in table. The algorithm is rather simple in theory but hard (at least for me) when I need to build a query.
Generally, I want to sum "values" of a "sub-group". Sub-group is defined as a range of elements starting with first row where type=0 and finishing with last row where type=1. the sub-group should contain only one (first) row with type=0.
The sample below presents correct (left) and incorrect (right) behavior.
I tried several approaches including grouping and partitioning. Unfortunately w/o any success. Anybody had similar problem?
I used MS SQL Server (so T-SQL 'magic' is allowed)
EDIT:
The results I want:
"ab",6
"cdef",20
"ghi",10
"kl",8
You can identify the groups by doing a cumulative sum of zeros. Then use aggregation or window functions.
Note that SQL tables represent unordered sets, so you need a column to specify the ordering. The code below assumes that this column is id.
select min(id), max(id), sum(value)
from (select t.*,
sum(case when type = 0 then 1 else 0 end) over (order by id) as grp
from t
) t
group by grp
order by min(id);
You can use window function with cumulative approach :
select t.*, sum(value) over (partition by grp)
from (select t.*, sum(case when type = 0 then 1 else 0 end) over (order by id) as grp
from table t
) t
where grp > 0;
Solution with a cursor and output-table.
As Gordon wrote it is not defined how the set will be ordered, so ID is also used here.
declare #output as table (
ID_sum nvarchar(max)
,value_sum int
)
DECLARE #ID as nvarchar(1)
,#value as int
,#type as int
,#ID_sum as nvarchar(max)
,#value_sum as int
,#last_type as int
DECLARE group_cursor CURSOR FOR
SELECT [ID],[value],[type]
FROM [t]
ORDER BY ID
OPEN group_cursor
FETCH NEXT FROM group_cursor
INTO #ID, #value,#type
WHILE ##FETCH_STATUS = 0
BEGIN
if (#last_type is null and #type = 0)
begin
set #ID_sum = #ID
set #value_sum = #value
end
if (#last_type in(0,1) and #type = 1)
begin
set #ID_sum += #ID
set #value_sum += #value
end
if (#last_type = 1 and #type = 0)
begin
insert into #output values (#ID_sum, #value_sum)
set #ID_sum = #ID
set #value_sum = #value
end
if (#last_type = 0 and #type = 0)
begin
set #ID_sum = #ID
set #value_sum = #value
end
set #last_type = #type
FETCH NEXT FROM group_cursor
INTO #ID, #value,#type
END
CLOSE group_cursor;
DEALLOCATE group_cursor;
if (#last_type = 1)
begin
insert into #output values (#ID_sum, #value_sum)
end
select *
from #output

how to use declared variable to select data from another table in case when condition?

I made select query in which i want to select data based on condition.For this i declared one variable and set value of that variable in else part.I want to use that variable for further select in same else part how can i achieve this?Please Help
declare #stateid int
select CASE WHEN MstCustomerAddressInfo.StateId is null
THEN 24
ELSE set #stateid = MstCustomerAddressInfo.StateId
select mststate.statecode from mststate where MstState.StateId = #stateid
END AS StateCode
No, you can't have SET inside a CASE expression. Even you can't have multiple statements.
Same query you can write as following.
declare #stateid int
select CASE
WHEN MstCustomerAddressInfo.StateId is null THEN 24
ELSE
-- set #stateid = MstCustomerAddressInfo.StateId
(select mststate.statecode
from mststate
where MstState.StateId = MstCustomerAddressInfo.StateId)
END AS StateCode
from [Your_Table]

SQL performance on ROWCOUNT check - better way?

I have a MS SQL 2005 stored procedure that I pass an ID to from a web page.
The ID is used to read pre-set search criteria from a database of widget category landing pages into a set of variables.
The variables are then used in a SELECT to search a database of widget items. Some variables can be overridden by a visitor's page choices, such as maximum price.
I'd like to check if the SELECT returns records, and, if not, default to the landing page's normal criteria to make sure the visitor always sees some items.
I have tried using ##ROWCOUNT to check the first SELECT but, as the SELECT is fairly involved (I've taken out a lot of fields in the example below), the performance hit of running it twice is unacceptably long. The first SELECT on its own takes around 1 second, whereas checking for ##ROWCOUNT = 0 and running the SELECT again takes around 4 seconds.
Is there a better way to accomplish this check and return of records if the first SELECT returns none?
I also want to return only one recordset, not two, at the end of the stored procedure.
Having researched this, I have found people using UNION ALL on two selects, but I don't think it helps me in this case. I also want to know which SELECT was used by passing through a field with contents True or False, so I can flag up on the website whether no records have been found.
Thanks for your help.
CREATE PROCEDURE dbo.NW_LANDING_GET_Widgets
/* options from the web page */
#WidgetID int,
#PageNumber int,
#WidgetsPerPage int,
#Sort VARCHAR(1),
#MinPrice int,
#MaxPrice int,
#Override_WidgetType varchar(20),
#Override_WidgetInfo1 int
AS
SET NOCOUNT ON
BEGIN
/* ---------- Declare variables for criteria to be gathered from WidgetLanding table ------------ */
DECLARE #WidgetCategory1 varchar(50)
DECLARE #WidgetCategory2 varchar(50)
DECLARE #WidgetType varchar(30)
DECLARE #WidgetInfo1 int
/* ---------- Read default criteria into variables from WidgetLanding table ----- */
SELECT
#WidgetCategory1=Criteria_WidgetCategory1,
#WidgetCategory2=Criteria_WidgetCategory2,
#WidgetType=Criteria_WidgetType
FROM dbo.WidgetLanding
WHERE pk_WidgetID = #WidgetID
/* -------- Set PageNumber variable for SELECT of Widgets ----------- */
SET #PageNumber=(#PageNumber-1)*#WidgetsPerPage
/* Set Minimum and Maximum Prices - if Null, set highest and lowest number possible */
DECLARE #Min int
DECLARE #Max int
SET #Min = ISNULL(#MinPrice,0)
SET #Max = ISNULL(#MaxPrice,999999999)
/* -------- Override variables if visitor has changed the search criteria from default ------------------- */
IF #Override_WidgetType is not null
BEGIN
SET #WidgetType=#Override_WidgetType
END
IF #Override_WidgetInfo1 is not null
BEGIN
SET #WidgetInfo1=#Override_WidgetInfo1
END
/* ------------------- Retrieve widget records based on variables ------ */
SELECT TOP(#WidgetsPerPage) * FROM (SELECT RowID=ROW_NUMBER()
OVER (ORDER BY
CASE WHEN dbo.Widgets.Featured_WidgetLandingID = #WidgetID then 1 else 0 end DESC,
CASE WHEN #Sort = 'D' THEN dbo.Widgets.Price END DESC, /* Price Descending */
CASE WHEN #Sort = 'U' THEN dbo.Widgets.Price END ASC, /* Price Ascending */
CASE WHEN #Sort = 'P' THEN dbo.Widgets.viewed END DESC, /* Popular */
CASE WHEN #Sort = 'L' THEN dbo.Widgets.Date END DESC), /* Latest */
Count(dbo.Widgets.WidgetID) OVER() As TotalRecords,
dbo.Widgets.Price,
dbo.Widgets.WidgetID,
dbo.Widgets.WidgetCategory1,
dbo.Widgets.WidgetCategory2,
dbo.Widgets.WidgetType,
dbo.Widgets.WidgetType2,
dbo.Widgets.WidgetInfo1
FROM dbo.Widgets
WHERE
(WidgetCategory1 = #WidgetCategory1 OR #WidgetCategory1 is null) AND
(WidgetCategory2 = #WidgetCategory2 OR #WidgetCategory2 is null) AND
(Price >= #Min AND Price <= #Max) AND
(WidgetInfo1 >= #WidgetInfo1 OR #WidgetInfo1 is null)
) TAB WHERE TAB.RowId > CAST(#PageNumber AS INT)
/*
-----------------------------
THIS IS WHERE I WANT TO CHECK IF RECORDS ARE RETURNED - IF NOT DO ANOTHER SELECT BUT WITHOUT OVERRIDING VARIABLES SO RECORDS WILL ALWAYS BE RETURNED
-----------------------------
*/
END
SET NOCOUNT OFF
Simpliest way is code like this:
SELECT TOP(#WidgetsPerPage) *
INTO #Res
FROM (SELECT RowID=ROW_NUMBER()
OVER (ORDER BY
CASE WHEN dbo.Widgets.Featured_WidgetLandingID = #WidgetID then 1 else 0 end DESC,
CASE WHEN #Sort = 'D' THEN dbo.Widgets.Price END DESC, /* Price Descending */
CASE WHEN #Sort = 'U' THEN dbo.Widgets.Price END ASC, /* Price Ascending */
CASE WHEN #Sort = 'P' THEN dbo.Widgets.viewed END DESC, /* Popular */
CASE WHEN #Sort = 'L' THEN dbo.Widgets.Date END DESC), /* Latest */
Count(dbo.Widgets.WidgetID) OVER() As TotalRecords,
dbo.Widgets.Price,
dbo.Widgets.WidgetID,
dbo.Widgets.WidgetCategory1,
dbo.Widgets.WidgetCategory2,
dbo.Widgets.WidgetType,
dbo.Widgets.WidgetType2,
dbo.Widgets.WidgetInfo1
FROM dbo.Widgets
WHERE
(WidgetCategory1 = #WidgetCategory1 OR #WidgetCategory1 is null) AND
(WidgetCategory2 = #WidgetCategory2 OR #WidgetCategory2 is null) AND
(Price >= #Min AND Price <= #Max) AND
(WidgetInfo1 >= #WidgetInfo1 OR #WidgetInfo1 is null)
) TAB WHERE TAB.RowId > CAST(#PageNumber AS INT)
IF (##ROWCOUNT>0)
BEGIN
INSERT #Res (......)
SELECT
END
SELECT * FROM #Res
But anyway your query should be rewritten.

Need to incorporate if else statement within my code

I am trying to achieve if else statement or case statement within my code below. I want to use one of these statement (if or case) to see if my RT_Ch_Pres_PX1 values are within certain min or max specs if they are above or below I want to say that if my RT_Ch_Pres_PX1 is below my min value by this much then display that value and indicate by how much is it of by and same this for exceeding max value. for example if my RT_Ch_Pres_PX1 value is 5 and I want to use my min valuse at 6 and max at 10. So my Rt_Ch_pres_px1 value is off by 1 so I would like to display this and say this value is of by 1 value. if RT_Ch_Pres_PX1 is within min and max values do nothing. Please see code below.
DECLARE #Result TABLE
(
RT_DateTime datetime,
RT_Phase_Name varchar(30),
RT_PhaseChangeCount int,
RT_Phase_Type int,
RT_Ch_Pres_PX1 float
);
/* Variables used to track changes to Phase Name */
DECLARE #RT_DateTime datetime;
DECLARE #RT_Phase_Name varchar(30);
DECLARE #RT_PhaseChangeCount int;
DECLARE #RT_Phase_Type int;
DECLARE #RT_Ch_Pres_PX1 float;
DECLARE #PhaseNameHold varchar(30);
DECLARE #PhaseChangeCount int;
SELECT #PhaseNameHold = ' ';
SELECT #PhaseChangeCount = 0;
SELECT #RT_PhaseChangeCount = 0;
/* Declare a cursor for determining when Phases change */
DECLARE ImportCursor CURSOR FAST_FORWARD FOR
SELECT
CONVERT(datetime, dbo.CycleData.Date_Time) as TimeConvert,
[dbo].[LookupPhases].[Phase_Name],
[dbo].[cycledata].[phase_type],
[dbo].[cycledata].[Ch_Pres_PX1]
FROM
CycleData INNER JOIN
CycleDataHeader ON CycleData.Unit_Number = CycleDataHeader.Unit_Number AND CycleData.Cycle_Counter_No = CycleDataHeader.Cycle_Counter_No INNER JOIN
LookupPhases ON CycleData.Phase_Type = LookupPhases.Phase_Type INNER JOIN
LookupEvent ON CycleData.Event_Type = LookupEvent.Event_Id LEFT OUTER JOIN
LookupAlarm ON CycleData.Alarm_Type = LookupAlarm.Alarm_Id
WHERE
[dbo].[CycleDataHeader].[Entered_Load_No1] = 'T14-0008'
ORDER BY
/* Appears to be the order that needs to be reported on */
Cycle_Time
-- dbo.CycleData.Unit_Number,
-- TimeConvert;
OPEN ImportCursor;
FETCH NEXT FROM ImportCursor INTO #RT_DateTime,
#RT_Phase_Name,
#RT_Phase_Type,
#RT_Ch_Pres_PX1
WHILE ##FETCH_STATUS = 0
BEGIN
IF (#RT_Phase_Name <> #PhaseNameHold)
BEGIN
SET #PhaseNameHold = #RT_Phase_Name;
SET #RT_PhaseChangeCount = #RT_PhaseChangeCount + 1;
END
INSERT INTO #Result VALUES(#RT_DateTime, #RT_Phase_Name,#RT_PhaseChangeCount,#RT_Phase_Type,#RT_Ch_Pres_PX1);
FETCH NEXT FROM ImportCursor INTO #RT_DateTime, #RT_Phase_Name,#RT_Phase_Type,#RT_Ch_Pres_PX1;
END
CLOSE ImportCursor;
DEALLOCATE ImportCursor;
SELECT
RT_DateTime,
RT_Phase_Name,
RT_PhaseChangeCount,
RT_Phase_Type,
RT_Ch_Pres_PX1
FROM #Result;
This case will generate the value you want:
case
when RT_Ch_Pres_PX1 < some_min then RT_Ch_Pres_PX1 - some_min
when RT_Ch_Pres_PX1 > some_max then RT_Ch_Pres_PX1 - some_max
else 0
end
The value created for the undershoot is negative (a good idea I think). If you want it to be positive, flip the calculation.

SQL Table Valued Function in Select Statement

SQL is not my best thing but I have been trying to optimize this stored procedure. It had multiple scalar-valued functions that I tried to change to table-valued functions because I read in many places that it's a more efficient way of doing it. And now I have them made but not real sure how to implement or if I maybe just didn't create them correctly.
This is the function I'm calling.
Alter FUNCTION [IsNotSenateActivityTableValue]
(
#ActivityCode int,
#BillId int,
#TextToDisplay varchar(max)
)
returns #T table(result varchar(max))
as
begin
DECLARE #result varchar(max);
declare #countcodes int;
declare #ishousebill int;
select #ishousebill = count(billid)
from BillMaster
where BillID = #BillID and Chamber = 'H'
If (#ishousebill = 0)
begin
SELECT #countcodes = count([ActivityCode])
FROM [HouseCoreData].[dbo].[ActivityCode]
where ActivityDescription not like '%(H)%' and ActivityType = 'S'
and [ActivityCode] = #ActivityCode
if (#countcodes = 0)
begin
set #result = 'test'
end
else
begin
set #result = 'test2'
end
end
else
begin
set #result = #TextToDisplay
end
RETURN
END
And this is how I was trying to call them like this. I would prefer just being able to put them in the top but really anything that works would be good.
SELECT distinct
ActionDates.result as ActionDate
,ActivityDescriptions.result as ActivityDescription
FROM BillWebReporting.vwBillDetailWithSubjectIndex as vw
left outer join [BillWebReporting].[HasHouseSummary] as HasSummary on vw.BillID = HasSummary.BillID
outer APPLY dbo.IsNotSenateActivityDateTableValue(ActivityCode,vw.BillID,[ActionDate]) ActionDates
OUTER APPLY dbo.IsNotSenateActivityTableValue(ActivityCode,vw.BillID,[ActivityDescription]) as ActivityDescriptions
Getting a count just to see if at least one row exists is very expensive. You should use EXISTS instead, which can potentially short circuit without materializing the entire count.
Here is a more efficient way using an inline table-valued function instead of a multi-statement table-valued function.
ALTER FUNCTION dbo.[IsNotSenateActivityTableValue] -- always use schema prefix!
(
#ActivityCode int,
#BillId int,
#TextToDisplay varchar(max)
)
RETURNS TABLE
AS
RETURN (SELECT result = CASE WHEN EXISTS
(SELECT 1 FROM dbo.BillMaster
WHERE BillID = #BillID AND Chamber = 'H'
) THEN #TextToDisplay ELSE CASE WHEN EXISTS
(SELECT 1 FROM [HouseCoreData].[dbo].[ActivityCode]
where ActivityDescription not like '%(H)%'
and ActivityType = 'S'
and [ActivityCode] = #ActivityCode
) THEN 'test2' ELSE 'test' END
END);
GO
Of course it could also just be a scalar UDF...
ALTER FUNCTION dbo.[IsNotSenateActivityScalar] -- always use schema prefix!
(
#ActivityCode int,
#BillId int,
#TextToDisplay varchar(max)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #result VARCHAR(MAX);
SELECT #result = CASE WHEN EXISTS
(SELECT 1 FROM dbo.BillMaster
WHERE BillID = #BillID AND Chamber = 'H'
) THEN #TextToDisplay ELSE CASE WHEN EXISTS
(SELECT 1 FROM [HouseCoreData].[dbo].[ActivityCode]
where ActivityDescription not like '%(H)%'
and ActivityType = 'S'
and [ActivityCode] = #ActivityCode
) THEN 'test2' ELSE 'test' END
END;
RETURN (#result);
END
GO
Table-valued functions return a table, in which, like any other table, rows have to be inserted.
Instead of doing set #result = ....., do:
INSERT INTO #T (result) VALUES ( ..... )
EDIT: As a side note, I don't really understand the reason for this function to be table-valued. You are essentially returning one value.
First of all UDFs generally are very non-performant. I am not sure about MySQL, but in Sql Server a UDF is recompiled every time (FOR EACH ROW OF OUTPUT) it is executed, except for what are called inline UDFs, which only have a single select statement, which is folded into the SQL of the outer query it is included in... and so is only compiled once.
MySQL does have inline table-valued functions, use it instead... in SQL Server, the syntax would be:
CREATE FUNCTION IsNotSenateActivityTableValue
(
#ActivityCode int,
#BillId int,
#TextToDisplay varchar(max)
)
RETURNS TABLE
AS
RETURN
(
Select case
When y.bilCnt + z.actCnt = 0 Then 'test'
when y.bilCnt = 0 then 'test2'
else #TextToDisplay end result
From (Select Count(billId) bilCnt
From BillMaster
Where BillID = #BillID
And Chamber = 'H') y
Full Join
(Select count([ActivityCode]) actCnt
From [HouseCoreData].[dbo].[ActivityCode]
Where ActivityDescription not like '%(H)%'
And ActivityType = 'S'
And [ActivityCode] = #ActivityCode) z
)
GO