How can i optimize the Sql Server Query
Table name is: Pro_itemmaster
Column name is: itm_Code
Test Data = Download Link
My Query take 17 seconds to complete
Query
; WITH CatItem AS(
SELECT
PIM.itm_Code AS Id
,CASE WHEN LEN(PIM.itm_Code) = 2 THEN NULL ELSE LEFT(PIM.itm_Code, LEN(PIM.itm_Code) - CHARINDEX('-',REVERSE(PIM.itm_Code))) END AS ParentId
,1 AS [Depth]
FROM
Pro_itemmaster AS PIM
WHERE
LEN(PIM.itm_Code) = 2
UNION ALL
SELECT
PIM.itm_Code AS Id
,CASE WHEN LEN(PIM.itm_Code) = 2 THEN NULL ELSE LEFT(PIM.itm_Code, LEN(PIM.itm_Code) - CHARINDEX('-',REVERSE(PIM.itm_Code))) END AS ParentId
,[CatItem].[Depth] + 1 AS [Depth]
FROM
[Pro_itemmaster] AS [PIM]
JOIN
[CatItem]
ON
CASE WHEN LEN(PIM.itm_Code) = 2 THEN NULL ELSE LEFT(PIM.itm_Code, LEN(PIM.itm_Code) - CHARINDEX('-',REVERSE(PIM.itm_Code))) END = CatItem.Id
)
SELECT * FROM CatItem
Query Execution Plan
I am able to reduce the time to 3 seconds by introducing a table variable.
The issue here is that I cannot modify the main table and add ParentId column so needed the work around.
DECLARE #DataTable TABLE (Id VARCHAR(60) PRIMARY KEY NOT NULL, ParentId VARCHAR(60))
INSERT INTO #DataTable
SELECT itm_Code
, CASE WHEN LEN(itm_Code) = 2 THEN NULL ELSE LEFT(itm_Code, LEN(itm_Code) - CHARINDEX('-',REVERSE(itm_Code))) END
FROM Pro_itemmaster
; WITH CatItem AS(
SELECT
PIM.Id
,PIM.ParentId
,1 AS [Depth]
FROM
#DataTable AS PIM
WHERE
LEN(PIM.Id) = 2
UNION ALL
SELECT
PIM.Id
,PIM.ParentId
,[CatItem].[Depth] + 1 AS [Depth]
FROM
#DataTable AS [PIM]
JOIN
[CatItem]
ON
PIM.ParentId = CatItem.Id
)
SELECT *
FROM CatItem
ORDER BY Id
Suppose I have a table like this :
Table 1:
date account name type status open_account_date
31.12.14 1000 20 40 50 31.12.14
2.1.15 1000 10 10 50 31.12.14
3.1.15 1000 5 15 50 31.12.14
and I want to build a summary table like this for the first quarter :
account numOfChanges Changes
1000 4 (name, type)
The first row in table 1 indicats that that the account was opened and somebody enterd for the account some details but the others indicats changes but i want to know which fields has been changed. Is there any suggestion or an idea how to perform this?
DECLARE #StartOfQuarter DATE = '1/1/2015'
;WITH cteRcordStateBeforeQuarter AS (
SELECT
[date]
,account
,name
,[type]
,[status]
,open_account_date
,RowNum = ROW_NUMBER() OVER (PARTITION BY account ORDER BY [date] DESC)
FROM
#Table
WHERE
[date] < #StartOfQuarter
)
, cteRecordStatesDuringQuarter AS (
SELECT
[date]
,account
,name
,[type]
,[status]
,open_account_date
,RowNum = ROW_NUMBER() OVER (PARTITION BY account ORDER BY [date] ASC) + 1
--add 1 because the first row is going to be the last one prior to quarter
,LatestChangeRowNum = ROW_NUMBER() OVER (PARTITION BY account ORDER BY [date] DESC)
FROM
#Table
WHERE
DATEPART(QQ,[date]) = 1
--change to suite ongoing needs such as quater and YEAR([date]) = ??
)
, cteRecursive AS (
SELECT
[date]
,account
,name
,[type]
,[status]
,open_account_date
,RowNum
,LatestChangeRowNum = 0
,NumOfChanges = 0
,[Changes] = CAST('' AS VARCHAR(100))
FROM
cteRcordStateBeforeQuarter
WHERE
RowNum = 1
UNION ALL
SELECT
q.[date]
,q.account
,q.name
,q.[type]
,q.[status]
,q.open_account_date
,q.RowNum
,LatestChangeRowNum = CAST(q.LatestChangeRowNum AS INT)
,NumOfChanges = r.NumOfChanges
+ CASE WHEN ISNULL(q.name,-99999) <> ISNULL(r.name,-99999) THEN 1 ELSE 0 END
+ CASE WHEN ISNULL(q.[type],-99999) <> ISNULL(r.[type],-99999) THEN 1 ELSE 0 END
+ CASE WHEN ISNULL(q.[status],-99999) <> ISNULL(r.[status],-99999) THEN 1 ELSE 0 END
+ CASE WHEN ISNULL(q.open_account_date,'1/1/1900') <> ISNULL(r.open_account_date,'1/1/1900') THEN 1 ELSE 0 END
,[Changes] = CAST(ISNULL(r.[Changes],'')
+ CASE WHEN ISNULL(q.name,-99999) <> ISNULL(r.name,-99999) AND r.[Changes] NOT LIKE '%name%' THEN ',name' ELSE '' END
+ CASE WHEN ISNULL(q.[type],-99999) <> ISNULL(r.[type],-99999) AND r.[Changes] NOT LIKE '%type%' THEN ',type' ELSE '' END
+ CASE WHEN ISNULL(q.[status],-99999) <> ISNULL(r.[status],-99999) AND r.[Changes] NOT LIKE '%status%' THEN ',status' ELSE '' END
+ CASE WHEN ISNULL(q.open_account_date,'1/1/1900') <> ISNULL(r.open_account_date,'1/1/1900') AND r.[Changes] NOT LIKE '%open_account_date%' THEN ',open_account_date' ELSE '' END
AS VARCHAR(100))
FROM
cteRecordStatesDuringQuarter q
INNER JOIN cteRecursive r
ON q.account = r.account
AND q.RowNum = r.RowNum + 1
)
SELECT
account
,NumOfChanges
,[Changes] = REPLACE(IIF(CHARINDEX(',',[Changes]) = 1, RIGHT([Changes],LEN([Changes]) - 1),[Changes]),',',', ')
FROM
cteRecursive
WHERE
LatestChangeRowNum = 1
If you where using SQL 2012 + it would be a little easier because you could use LAG or LEAD window functions and IIF instead of case. But a recursive cte works pretty well too. I assume your recordset will have multiple accounts as well as multiple quarters and there for multiple recordstates prior to the quarter. Due to this you will need to tweak the date logic slightly but this will give you the gist.
First find the record state prior to the quarter. Then find all of the changes during the quarter. Add some row numbers to determine which is the first and which is the last change. Then use a recursive cte to test for changes. In the end you just need to format the string the way you want.
I have the following query:
DECLARE #MyTable TABLE
(
[ID] INT ,
[Col1] INT ,
[Col2] INT
)
INSERT INTO #MyTable
SELECT 1 ,
2 ,
1
UNION
SELECT 1 ,
2 ,
3
UNION
SELECT 2 ,
2 ,
3
UNION
SELECT 2 ,
2 ,
3
UNION
SELECT 3 ,
2 ,
3
UNION
SELECT 3 ,
2 ,
1
DECLARE #ID INT
SET #ID = 1
SELECT *
FROM #MyTable
WHERE ( Col1 = ( CASE WHEN [ID] = #ID THEN 2
END )
OR [Col2] = ( CASE WHEN [ID] != #ID THEN 1
END )
)
WHEN [ID] = #ID I want to match Col1 with constant value equals to 2 and when [ID] != #ID I want to match Col2 with constant value equals to 1. Can the above query be improve so that [ID] equality check can be done only once in the above query, something like this:
SELECT *
FROM #MyTable
WHERE
if([ID] = #ID)
Col1=2
ELSE
[Col2]=1
Is this the logic that you want?
where id = #id and col1 = 2 or
id <> #id and col2 = 1
I don't know why you are concerned about the performance of such a clause. You can do what you want with a case statement:
where 1 = (case when id = #id
then (case when col1 = 2 then 1 end)
else col2 = 1
end)
But this is a needless "optimization". It is not even clear that the nested case statements would be any faster than the first version. Such simple operations are really, really fast on modern computers. And, what slows databases down is the processing of large volumes of data (in general), not such simple operations.
Perhaps just:
Select *
From #MyTable
Where ((id = #id and col1 = 2) or (id <> #id and col2 = 1))
I got a table with some measurements, containing basically records. And now, I need to determine, if the values are monotonically increasing from time to time, decreasing or none of the above.
I achieved the desired result using CTE expression (the code is below), but the solution seems quite reduntant to me.
Is there a better way to determine, if the field value sequence is monotone, or not?
CREATE TABLE [dbo].[Measurements](
[ObjectID] [int] IDENTITY(1,1) NOT NULL,
[measDate] [datetime] NULL,
[measValue] [float] NULL
) ON [PRIMARY];
DECLARE
#ObjectID INT = 1;
with measSet as (
select row_number() over(order by measDate ) rownum, measValue, measDate
from dbo.Measurements M
where M.measDate > convert( datetime, '2013-10-02 08:13:00', 120)
and M.ObjectID = #ObjectID
)
select case when count(b.DiffSign) = 1 then 1 else 0 end as IsMonotone
from (
select DiffSign from
(
select MSS.measDate , MSS.measValue, MSS.measValue- MSSD.measValue as Diff,
case
when MSS.measValue- MSSD.measValue is null then NULL
when MSS.measValue- MSSD.measValue= 0 then NULL
when MSS.measValue- MSSD.measValue< 0
then -1
else 1
end as DiffSign
from measSet MSS
left join measSet MSSD
on MSSD .rownum = MSS.rownum - 1
) a
where a.DiffSign is not null
group by a.DiffSign
) b
If you don't care about knowing what particular records are breaking the monotony, then you could use something like this, which is a little more compact:
SELECT CASE WHEN COUNT(*) = 0 THEN 1 ELSE 0 END AS IsMonotone
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY measDate) AS RowNum, measValue
FROM Measurements
) T1 INNER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY measValue) AS RowNum, measValue
FROM Measurements
) T2 ON T1.RowNum = T2.RowNum
WHERE T1.measValue <> T2.measValue
I tried searching around, but I couldn't find anything that would help me out.
I'm trying to do this in SQL:
declare #locationType varchar(50);
declare #locationID int;
SELECT column1, column2
FROM viewWhatever
WHERE
CASE #locationType
WHEN 'location' THEN account_location = #locationID
WHEN 'area' THEN xxx_location_area = #locationID
WHEN 'division' THEN xxx_location_division = #locationID
I know that I shouldn't have to put '= #locationID' at the end of each one, but I can't get the syntax even close to being correct. SQL keeps complaining about my '=' on the first WHEN line...
How can I do this?
declare #locationType varchar(50);
declare #locationID int;
SELECT column1, column2
FROM viewWhatever
WHERE
#locationID =
CASE #locationType
WHEN 'location' THEN account_location
WHEN 'area' THEN xxx_location_area
WHEN 'division' THEN xxx_location_division
END
without a case statement...
SELECT column1, column2
FROM viewWhatever
WHERE
(#locationType = 'location' AND account_location = #locationID)
OR
(#locationType = 'area' AND xxx_location_area = #locationID)
OR
(#locationType = 'division' AND xxx_location_division = #locationID)
Here you go.
SELECT
column1,
column2
FROM
viewWhatever
WHERE
CASE
WHEN #locationType = 'location' AND account_location = #locationID THEN 1
WHEN #locationType = 'area' AND xxx_location_area = #locationID THEN 1
WHEN #locationType = 'division' AND xxx_location_division = #locationID THEN 1
ELSE 0
END = 1
I'd say this is an indicator of a flawed table structure. Perhaps the different location types should be separated in different tables, enabling you to do much richer querying and also avoid having superfluous columns around.
If you're unable to change the structure, something like the below might work:
SELECT
*
FROM
Test
WHERE
Account_Location = (
CASE LocationType
WHEN 'location' THEN #locationID
ELSE Account_Location
END
)
AND
Account_Location_Area = (
CASE LocationType
WHEN 'area' THEN #locationID
ELSE Account_Location_Area
END
)
And so forth... We can't change the structure of the query on the fly, but we can override it by making the predicates equal themselves out.
EDIT: The above suggestions are of course much better, just ignore mine.
The problem with this is that when the SQL engine goes to evaluate the expression, it checks the FROM portion to pull the proper tables, and then the WHERE portion to provide some base criteria, so it cannot properly evaluate a dynamic condition on which column to check against.
You can use a WHERE clause when you're checking the WHERE criteria in the predicate, such as
WHERE account_location = CASE #locationType
WHEN 'business' THEN 45
WHEN 'area' THEN 52
END
so in your particular case, you're going to need put the query into a stored procedure or create three separate queries.
OR operator can be alternative of case when in where condition
ALTER PROCEDURE [dbo].[RPT_340bClinicDrugInventorySummary]
-- Add the parameters for the stored procedure here
#ClinicId BIGINT = 0,
#selecttype int,
#selectedValue varchar (50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT
drugstock_drugname.n_cur_bal,drugname.cdrugname,clinic.cclinicname
FROM drugstock_drugname
INNER JOIN drugname ON drugstock_drugname.drugnameid_FK = drugname.drugnameid_PK
INNER JOIN drugstock_drugndc ON drugname.drugnameid_PK = drugstock_drugndc.drugnameid_FK
INNER JOIN drugndc ON drugstock_drugndc.drugndcid_FK = drugndc.drugid_PK
LEFT JOIN clinic ON drugstock_drugname.clinicid_FK = clinic.clinicid_PK
WHERE (#ClinicId = 0 AND 1 = 1)
OR (#ClinicId != 0 AND drugstock_drugname.clinicid_FK = #ClinicId)
-- Alternative Case When You can use OR
AND ((#selecttype = 1 AND 1 = 1)
OR (#selecttype = 2 AND drugname.drugnameid_PK = #selectedValue)
OR (#selecttype = 3 AND drugndc.drugid_PK = #selectedValue)
OR (#selecttype = 4 AND drugname.cdrugclass = 'C2')
OR (#selecttype = 5 AND LEFT(drugname.cdrugclass, 1) = 'C'))
ORDER BY clinic.cclinicname, drugname.cdrugname
END
Please try this query.
Answer To above post:
select #msgID, account_id
from viewMailAccountsHeirachy
where
CASE #smartLocationType
WHEN 'store' THEN account_location
WHEN 'area' THEN xxx_location_area
WHEN 'division' THEN xxx_location_division
WHEN 'company' THEN xxx_location_company
END = #smartLocation
Try this:
WHERE (
#smartLocationType IS NULL
OR account_location = (
CASE
WHEN #smartLocationType IS NOT NULL
THEN #smartLocationType
ELSE account_location
END
)
)
CREATE PROCEDURE [dbo].[Temp_Proc_Select_City]
#StateId INT
AS
BEGIN
SELECT * FROM tbl_City
WHERE
#StateID = CASE WHEN ISNULL(#StateId,0) = 0 THEN 0 ELSE StateId END ORDER BY CityName
END
Try this query, it's very easy and useful: Its ready to execute!
USE tempdb
GO
IF NOT OBJECT_ID('Tempdb..Contacts') IS NULL
DROP TABLE Contacts
CREATE TABLE Contacts(ID INT, FirstName VARCHAR(100), LastName VARCHAR(100))
INSERT INTO Contacts (ID, FirstName, LastName)
SELECT 1, 'Omid', 'Karami'
UNION ALL
SELECT 2, 'Alen', 'Fars'
UNION ALL
SELECT 3, 'Sharon', 'b'
UNION ALL
SELECT 4, 'Poja', 'Kar'
UNION ALL
SELECT 5, 'Ryan', 'Lasr'
GO
DECLARE #FirstName VARCHAR(100)
SET #FirstName = 'Omid'
DECLARE #LastName VARCHAR(100)
SET #LastName = ''
SELECT FirstName, LastName
FROM Contacts
WHERE
FirstName = CASE
WHEN LEN(#FirstName) > 0 THEN #FirstName
ELSE FirstName
END
AND
LastName = CASE
WHEN LEN(#LastName) > 0 THEN #LastName
ELSE LastName
END
GO
In general you can manage case of different where conditions in this way
SELECT *
FROM viewWhatever
WHERE 1=(CASE <case column or variable>
WHEN '<value1>' THEN IIF(<where condition 1>,1,0)
WHEN '<value2>' THEN IIF(<where condition 2>,1,0)
ELSE IIF(<else condition>,1,0)
END)
Case Statement in SQL Server Example
Syntax
CASE [ expression ]
WHEN condition_1 THEN result_1
WHEN condition_2 THEN result_2
...
WHEN condition_n THEN result_n
ELSE result
END
Example
SELECT contact_id,
CASE website_id
WHEN 1 THEN 'TechOnTheNet.com'
WHEN 2 THEN 'CheckYourMath.com'
ELSE 'BigActivities.com'
END
FROM contacts;
OR
SELECT contact_id,
CASE
WHEN website_id = 1 THEN 'TechOnTheNet.com'
WHEN website_id = 2 THEN 'CheckYourMath.com'
ELSE 'BigActivities.com'
END
FROM contacts;
This worked for me.
CREATE TABLE PER_CAL ( CAL_YEAR INT, CAL_PER INT )
INSERT INTO PER_CAL( CAL_YEAR, CAL_PER ) VALUES ( 20,1 ), ( 20,2 ), ( 20,3 ), ( 20,4 ), ( 20,5 ), ( 20,6 ), ( 20,7 ), ( 20,8 ), ( 20,9 ), ( 20,10 ), ( 20,11 ), ( 20,12 ),
( 99,1 ), ( 99,2 ), ( 99,3 ), ( 99,4 ), ( 99,5 ), ( 99,6 ), ( 99,7 ), ( 99,8 ), ( 99,9 ), ( 99,10 ), ( 99,11 ), ( 99,12 )
The 4 digit century is determined by the rule, if the year is 50 or more, the century is 1900, otherwise 2000.
Given two 6 digit periods that mark the start and end period, like a quarter, return the rows that fall in that range.
-- 1st quarter of 2020
SELECT * FROM PER_CAL WHERE (( CASE WHEN CAL_YEAR > 50 THEN 1900 ELSE 2000 END + CAL_YEAR ) * 100 + CAL_PER ) BETWEEN 202001 AND 202003
-- 4th quarter of 1999
SELECT * FROM PER_CAL WHERE (( CASE WHEN CAL_YEAR > 50 THEN 1900 ELSE 2000 END + CAL_YEAR ) * 100 + CAL_PER ) BETWEEN 199910 AND 199912
Try this query. Its very easy to understand:
CREATE TABLE PersonsDetail(FirstName nvarchar(20), LastName nvarchar(20), GenderID int);
GO
INSERT INTO PersonsDetail VALUES(N'Gourav', N'Bhatia', 2),
(N'Ramesh', N'Kumar', 1),
(N'Ram', N'Lal', 2),
(N'Sunil', N'Kumar', 3),
(N'Sunny', N'Sehgal', 1),
(N'Malkeet', N'Shaoul', 3),
(N'Jassy', N'Sohal', 2);
GO
SELECT FirstName, LastName, Gender =
CASE GenderID
WHEN 1 THEN 'Male'
WHEN 2 THEN 'Female'
ELSE 'Unknown'
END
FROM PersonsDetail