Improving SQL Query to check condition - sql

Currently, the sql code that I have is to check for the three fee_code records do any of them have election_code = NA.
I'm trying to figure out how to change the code to check that all of the 3 records have election_code = NA.
Original Query:
DECLARE #fee_code1 tinyint = 1
,#fee_code2 tinyint = 2
,#fee_code3 tinyint = 3
,#election_code tinyint = 0
IF EXISTS(SELECT *
FROM Product
WHERE
product_id = #product_id
AND fee_code in (#fee_code1, #fee_code2, #fee_code3)
AND election_code = #election_code)
The solution I have is to do a count check, but I know count has a performance impact and there's probably a better way to do it. What's a better way to do it?
Count solution:
IF ((SELECT COUNT(*)
FROM Product
WHERE
product_id = #product_id
AND fee_code in (#fee_code1, #fee_code2, #fee_code3)
AND election_code = #election_code) = 3)

Related

i want to optimize this query

i have the following tables :
dbo.Details
Name Type SubType SerialNumber
D_01 TxA STxA1 4
D_02 TxB STxB2 3
D_03 TxC STxC1 2
D_04 TxD STxD1 7
D_05 TxD STxD1 1
D_06 TxD STxD1 9
dbo.DetailsType
Code Name
TxA A
TxB B
TxC C
...
dbo.DetailsSubType
Code Type Name CustomOR
STxA1 TxA A1 1
STxA2 TxA A2 0
STxB1 TxB B1 1
STxB2 TxB B2 0
STxC1 TxC C1 1
STxC2 TxC C2 0
STxD TxD D1 1
I want to know what query (A or B) is optimal in your opinion, with explanation please:
QUERY A
CREATE PROCEDURE XXX
(
#type nvarchar(10),
#subType nvarchar(10) = null
)
AS
BEGIN
declare #custom bit = 0;
if (#subType is not null)
begin
select #custom = CustomOR from dbo.DetailsSubType where SubType = #subType
end
select
DTST.SubType,
DT.SerialNumber
from dbo.Details as DT
left join DetailsSubType as DTST
on DT.SubType = DTST.Code
where
DT.Type = #type
and
(
#subType is null or
(#custom = 0 and DTST.CustomOR= 0) or
(#custom = 1 and DT.SubType = #subType)
)
END
QUERY B
declare #custom bit = 0;
if (#subType is not null)
begin
select #custom = CustomOR from dbo.DetailsSubType where SubType = #subType
end
if (#custom = 0)
begin
select
DTST.SubType,
DT.SerialNumber
from dbo.Details as DT
left join DetailsSubType as DTST
on DT.SubType = DTST.Code
where
DT.Type = #type
and
DTST.CustomOR = 0
end
else
begin
select
DTST.SubType,
DT.SerialNumber
from dbo.Details as DT
left join DetailsSubType as DTST
on DT.SubType = DTST.Code
where
DT.Type = #type
and
(DTST.CustomOR = 1 and DT.SubType = #subType)
end
Unfortunately, neither may be optimal. I am guessing that your concern is related to performance and the execution plan of the query. The second method definitely gives SQL Server better opportunities for the optimization plan -- simply because OR is really hard to optimize.
But this doesn't take into account "parameter sniffing". There are lots of articles about this subject (here is a reasonable one).
Parameter sniffing means that SQL Server compiles the query the first time the stored procedure is called. This saves the overhead of recompiling the queries -- a savings that is important if you have lots of "small" queries. But a fool's bargain for larger queries -- because it does not take statistics on the table into account.
I would suggest that you look into articles about this. You may find that the second solution is sufficient. You may find that simply adding option recompile is sufficient. You may find that you want to construct the query as dynamic SQL -- hey, you know it will be recompiled anyway. But you'll be able to make a more informed decision.
You could consider writing three queries that partition your results, where each query handles exactly one of your OR predicates, and UNION ALL the results.
In pseudo code:
SELECT ... FROM ... WHERE #subType is null
UNION ALL
SELECT ... FROM ... WHERE #subType is NOT null AND DTST.CustomOR = 0 AND #custom = 0
UNION ALL
SELECT ... FROM ... WHERE #subType is NOT null AND DT.SubType = #subType AND #custom = 1
Having said that, what I actually think is that you should change your data model. It is extremely had (and very slow) to move forward with this setup. You probably haven't normalized your database properly.

How to retrieve data if not exist with certain condition in SQL Server?

I have 3 values
SecurityLevel - ex:1
ReportName - ex:'TotalSales'
UserID - ex:'faisal.3012'
I have 2 tables:
SecurityLevelDetails
SecurityUserDetails
I want to check the data whether it is already exists or not in SecurityUserDetails. If exist, I want to retrieve that exist record, if not I want to retrieve record from SecurityLevelDetails.
I try to make it as a single query, I can do using if condition. But I don't want to do.
I tried this. I know this is wrong.
Select
ReportHide, RColumnName, RFilterName
From
HQWebMatajer.dbo.SecurityLevelDetails sld
Where
SecurityLevel = 1
and not exists(select top 1
UserID, ReportHide, RColumnName, RFilterName
from
[HQWebMatajer].[dbo].[SecurityUserDetails]
where
[UserID] = 'faisal.3012'
and [ReportName] = 'TotalSales')
It's retrieving a record if it does not exist in SecurityUserDetails. But I want to retrieve the record from SecurityUserDetails if it exists
UPDATED
I got the result from below code. But I am trying to make in single query
declare #flags int = 0;
select top 1 #flags=count(*)
from [HQWebMatajer].[dbo].[SecurityUserDetails]
where [UserID]='faisal.3012' and [ReportName]='TotalSales';
if(#flags>0)
BEGIN
select top 1 UserID,ReportHide,RColumnName,RFilterName
from [HQWebMatajer].[dbo].[SecurityUserDetails]
where [UserID]='faisal.3012' and [ReportName]='TotalSales'
END
ELSE
BEGIN
select SecurityLevel,ReportHide,RColumnName,RFilterName
From HQWebMatajer.dbo.SecurityLevelDetails sld
where SecurityLevel=1 and ReportName='TotalSales'
END
One way to approach this is with UNION ALL
You have both sides defined and a UNION ALL joins them up
I'll stick together all of the code you have posted so far:
select top 1 UserID,ReportHide,RColumnName,RFilterName
from [HQWebMatajer].[dbo].[SecurityUserDetails]
where [UserID]='faisal.3012' and [ReportName]='TotalSales'
UNION ALL
select SecurityLevel,ReportHide,RColumnName,RFilterName
From HQWebMatajer.dbo.SecurityLevelDetails sld
where SecurityLevel=1
and ReportName='TotalSales'
and not exists(select *
from
[HQWebMatajer].[dbo].[SecurityUserDetails]
where
[UserID] = 'faisal.3012'
and [ReportName] = 'TotalSales')

SQL Query that uses GEOGRAPHY to select record where distances match

On this thread, I found this example:
DECLARE #source geography = 'POINT(-94.25 45.46)'
DECLARE #target geography = 'POINT(-94.19 45.57)'
SELECT (#source.STDistance(#target)/1000) * 0.62137
This accurately tells me that there are
~8+ miles between the two points. That's VERY helpful. But, now what I am trying to do is a bit more complex.
I have a table, Criteria that looks like this:
ID State Zip Lat Long Radius
------------------------------------------------
1 MN 56301 45.46 -94.25 25
There are more records that this, but that's enough for our purposes. Now, I need to query for record where either there is a direct State match, or a direct Zip match, or the range matches. So...
DECLARE #CompareState VARCHAR(2) = NULL
DECLARE #CompareZip VARCHAR(5) = NULL
DECLARE #CompareLon DECIMAL = -94.19
DECLARE #CompareLat DECIMAL = 45.57
SELECT
*
FROM
Criteria c
WHERE
c.State = #CompareState
OR c.Zip = #CompareZip
OR (Distance between two sets of Lat and Long is <= c.Radius)
In the query above, the row with ID of 1 should be returned. I'm struggling with the syntax.
Got it.
DECLARE #CompareState VARCHAR(2) = NULL
DECLARE #CompareZip VARCHAR(5) = NULL
DECLARE #CompareLon DECIMAL = -94.19
DECLARE #CompareLat DECIMAL = 45.57
SELECT
*
FROM
LeadSalesCampaignCriterias c
JOIN LeadSalesCampaignCriterias c2
ON c.LeadSalesCampaignCriteriaID = c2.LeadSalesCampaignCriteriaID
AND c2.Latitude IS NOT NULL
AND c2.Longitude IS NOT NULL
WHERE
c.State = #CompareState
OR c.Zip = #CompareZip
OR
(
((geography::Point(c2.Latitude, c2.Longitude, 4326).STDistance(geography::Point(#CompareLat, #CompareLon, 4326))/1000) * 0.62137) < c.Radius
)
I honestly don't know what 4326 is.
See: https://msdn.microsoft.com/en-us/library/bb933811.aspx
I cribbed your answer and changed it a bit for efficiency.
DECLARE #CompareState VARCHAR(2) = NULL
DECLARE #CompareZip VARCHAR(5) = NULL
DECLARE #CompareLon DECIMAL = -94.19
DECLARE #CompareLat DECIMAL = 45.57
-- you appear to be wanting to find things within 1 mile
-- the magic number 1609.34 is the number of meters in a mile
DECLARE #RangeDisk geography = geography::Point(#CompareLat, #CompareLon, 4326).STBuffer(1609.34);
SELECT
*
FROM
LeadSalesCampaignCriterias c
JOIN LeadSalesCampaignCriterias c2
ON c.LeadSalesCampaignCriteriaID = c2.LeadSalesCampaignCriteriaID
AND c2.Latitude IS NOT NULL
AND c2.Longitude IS NOT NULL
WHERE
c.State = #CompareState
OR c.Zip = #CompareZip
OR geography::Point(c2.Latitude, c2.Longitude, 4326).STIntersects(#RangeDisk) = 1
A couple of other notes. If you can alter your table to have the geography column pre-computed, that will make this even better as you won't have to convert it on the fly in the where clause (that predicate would take the form of new_column.STIntersects(#RangeDisk) = 1). A spatial index on that new column will do wonders for the efficiency of the query!
I'm also a little confused by the self join. Is LeadSalesCampaignCriteriaID the primary key in the table? If so, I don't think that join is necessary (and is very likely hurting your performance).
Lastly, in your self-answer, you mentioned not knowing what the magic number 4326 was. It's called a spatial reference id (aka SRID). Essentially, there have been multiple attempts historically to model the earth. When you get representations of geographic features from external sources, they will have been created with one of those systems in mind. Even if you're creating them whole cloth though, you need to know what the unit of measure is (when you compute something like distance, for example). You can see properties of the SRIDs that SQL knows about in sys.spatial_reference_systems.

Alternative to relational DB for transactional duplicate validation processing?

I'm currently using SSMS, working with two tables both which have millions of records; let's call them: latest_status table and dupe_hash table.
latest_status:
latest_status_PK - bigint - identity(1,1)
status - bigint
50 => stage-zero
100 => stage-one
200 => stage-two
999 => duplicate-failed
dupe_hash:
dupe_hash_PK - bigint - identity(1,1)
latest_status_FK (relationship with latest_status) - bigint
md5 - varchar(256)
batch_id - uniqueidentifier
My objective is to look through dupe_hash records (where latest_status.status = 100) and see if this record's md5 is the same as another record's md5; I update the latest_status.status of the these depending on their statuses. Something along the lines of:
*record_1's status = 100 & record_2's status = 200 => update record_1's status to 999
*record_1's status = 100 & record_2's status = 50 => update record_1's status to 200 and record_2's status to 999
I'm currently using set-based operations, saving this query into a table variable and updating the latest_status table with a case statement. In my partition by md5, I am ordering the subsets based on their status because in the end, I only want to keep one record (rowid = 1) and the others would be set to 999:
SELECT ROW_NUMBER() OVER(PARTITION BY DUPHASH.MD5 ORDER BY CASE WHEN LATEST_STAT.STATUS = 200 THEN 0 WHEN LATEST_STAT.STATUS = 50 THEN 1 ELSE 1 END, LATEST_STAT.latest_status_PK DESC) ROWID,
LATEST_STAT.CLAIM_OUTBOUND_LATEST_STATUS_KEY,
LATEST_STAT.STATUS,
DUPHASH.DUPLICATE_KEY,
DUPHASH.BATCH_ID,
DUPHASH.MD5
FROM DBO.DUPE_HASH DUPHASH
INNER JOIN DBO.LATEST_STATUS LATEST_STAT
WHERE MD5 IN (SELECT DISTINCT MD5
FROM DBO.DUPE_HASH DUPHASH
INNER JOIN DBO.LATEST_STATUS LATEST_STAT
ON DUPHASH.LATEST_STATUS_FK = LATEST_STAT.LATEST_STATUS_PK
WHERE STATUS = 105) ON DUPHASH.LATEST_STATUS_FK = LATEST_STAT.LATEST_STATUS_PK
This brings me to my actual question: Are there any alternatives/improvements to this approach that could support realtime/transactioal processing?
table partitioning?
non-relational DBs?
I'm really all ears. Thank you!

Help replace this SQL cursor with better code

Can anyone give me a hand improving the performance of this cursor logic from SQL 2000. It runs great in SQl2005 and SQL2008, but takes at least 20 minutes to run in SQL 2000. BTW, I would never choose to use a cursor, and I didn't write this code, just trying to get it to run faster. Upgrading this client to 2005/2008 is not an option in the immediate future.
-------------------------------------------------------------------------------
------- Rollup totals in the chart of accounts hierarchy
-------------------------------------------------------------------------------
DECLARE #B_SubTotalAccountID int, #B_Debits money, #B_Credits money, #B_YTDDebits money, #B_YTDCredits money
DECLARE Bal CURSOR FAST_FORWARD FOR
SELECT SubTotalAccountID, Debits, Credits, YTDDebits, YTDCredits FROM xxx
WHERE AccountType = 0 AND SubTotalAccountID Is Not Null and (abs(credits)+abs(debits)+abs(ytdcredits)+abs(ytddebits)<>0)
OPEN Bal
FETCH NEXT FROM Bal INTO #B_SubTotalAccountID, #B_Debits, #B_Credits, #B_YTDDebits, #B_YTDCredits
--For Each Active Account
WHILE ##FETCH_STATUS = 0
BEGIN
--Loop Until end of subtotal chain is reached
WHILE #B_SubTotalAccountID Is Not Null
BEGIN
UPDATE xxx2
SET Debits = Debits + #B_Debits,
Credits = Credits + #B_Credits,
YTDDebits = YTDDebits + #B_YTDDebits,
YTDCredits = YTDCredits + #B_YTDCredits
WHERE GLAccountID = #B_SubTotalAccountID
SET #B_SubTotalAccountID = (SELECT SubTotalAccountID FROM xxx2 WHERE GLAccountID = #B_SubTotalAccountID)
END
FETCH NEXT FROM Bal INTO #B_SubTotalAccountID, #B_Debits, #B_Credits, #B_YTDDebits, #B_YTDCredits
END
CLOSE Bal
DEALLOCATE Bal
Update xx2
Set Credits = Credits + X1.CreditTotal
, Debits = Debits + X1.DebitTotal
, YtdDebits = YtdDebits + X1.YtdDebitTotal
, YtdCredits = YtdCredits + X1.YtdDebitTotal
From xx2 As X2
Join (
Select SubTotalAccountID, Sum(Debits) As DebitTotal, Sum(Credits) As CreditTotal
, Sum(YtdDebits) As YtdDebitTotal, Sum(YtdCredits) As YtdCreditTotal
From xxx
Where AccountType = 0
And SubTotalAccountID Is Not Null
And (
Credits <> 0
Or Debits <> 0
Or YtdCredits <> 0
Or YtdDebits <> 0
)
Group By SubTotalAccountID
) As X1
On X1.SubTotalAccountID = X2.GLAccountID
Without schema, I could not tell if the xxx table would return multiple rows for a given SubTotalAccountId. I assumed that it could and grouped the values by this column so that I get one row per SubTotalAccountId.
I also replaced your use of ABS in the WHERE clause with simply checks against zero. This should be substantially faster.
This UPDATE statement should be a complete replacement for your cursor.
A couple of suggestions:
1 - use the profiler to tell you which part of it runs slowly - you can get a duration for each statement
2 - run the initial select statement (the cursor declaration) outside of the procedure and check the query plan. Does it run quickly? Is it using indexes properly?
3 - same thing with the update statement - check the query plan and index usage
4 - the 'set' statement after the update looks odd - it seems to be getting a value into #B_SubTotalAccountID which is then replaced immediately by the 'fetch next'