SQL query cleanup - and/or based on condition - sql

What I'm trying to do is write a query which, based on a boolean will select rows which satisfy two conditions or at least one of the two.
The following is a simplified query that gets the job done. Thing is, the where clause in my query is much more complex so I'm looking for a way to rewrite it in a more readable fashion.
Is this possible?
declare #AndParam bit
set #AndParam = 1
SELECT * FROM Table
Where
(#AndParam = 1
AND
(Table.CategoryID = 3
AND
Table.Age < 30))
OR
(#AndParam = 0
AND
(Table.CategoryID = 3
OR
Table.Age < 30))
EDIT:
This question really is about readability and esthetics.
In the stead of Table.Age < 30 there are five additional checks including subqueries. Practically, this means that I have a large section of the code duplicated with only one difference; The And/Or.
Seeing as how SQL can be difficult to read I'm looking to fix it up.

Maybe something like this:
Where
CASE WHEN Table.CategoryID = 3 THEN 1 ELSE 0 END +
CASE WHEN Table.Age < 30 THEN 1 ELSE 0 END -- + more CASEs for each condition
>=
CASE WHEN #AndParam = 1 THEN 2 ELSE 1 END
--For more conditions, change 2 above to however many conditions there are
You basically make each condition give you a 1 or 0, and then add all of these up - then you do a final check, depending on #AndParam on whether you hit the total number of conditions or just at least 1.

Related

Return NULL instead of 0 when using COUNT(column) SQL Server

I have query which running fine and its doing two types of work, COUNT and SUM.
Something like
select
id,
Count (contracts) as countcontracts,
count(something1),
count(something1),
count(something1),
sum(cost) as sumCost
from
table
group by
id
My problem is: if there is no contract for a given ID, it will return 0 for COUNT and Null for SUM. I want to see null instead of 0
I was thinking about case when Count (contracts) = 0 then null else Count (contracts) end but I don't want to do it this way because I have more than 12 count positions in query and its prepossessing big amount of records so I think it may slow down query performance.
Is there any other ways to replace 0 with NULL?
Try this:
select NULLIF ( Count(something) , 0)
Here are three methods:
1. (case when count(contracts) > 0 then count(contracts) end) as countcontracts
2. sum(case when contracts is not null then 1 end) as countcontracts
3. nullif(count(contracts), 0)
All three of these require writing more complicated expressions. However, this really isn't that difficult. Just copy the line multiple times, and change the name of the variable on each one. Or, take the current query, put it into a spreadsheet and use spreadsheet functions to make the transformation. Then copy the function down. (Spreadsheets are really good code generators for repeated lines of code.)

Is it possible to use the result of a subquery in a case statement of the same outer query?

I am writing a search routine with a ranking algorithm and would like to get this in one pass.
My Ideal query would be something like this....
select *, (select top 1 wordposition
from wordpositions
where recordid=items.pk_itemid and wordid=79588 and nextwordid=64502
) as WordPos,
case when WordPos<11 then 1 else case WordPos<50 then 2 else case WordPos<100 then 3 else 4 end end end end as rank
from items
Is it possible to use WordPos in a case right there? It's generating an error on me , Invalid column name 'WordPos'.
I know I can redo the subquery for each case but I think it would actually re-run the case wouldn't it?
For example:
select *, case when (select top 1 wordposition from wordpositions where recordid=items.pk_itemid and wordid=79588 and nextwordid=64502)<11 then 1 else case (select top 1 wordposition from wordpositions where recordid=items.pk_itemid and wordid=79588 and nextwordid=64502)<50 then 2 else case (select top 1 wordposition from wordpositions where recordid=items.pk_itemid and wordid=79588 and nextwordid=64502)<100 then 3 else 4 end end end end as rank from items
That works....but is it really re-running the identical query each time?
It's hard to tell from the tests as the first time it runs it's slow but subsequent runs are quick....it's caching...so would that mean that the first time it ran it for the first row, the subsequent three times it would get the result from cache?
Just curious what the best way to do this would be...
Thank you!
Ryan
You can do this using a subquery. I will stick with your SQL Server syntax, even though the question is tagged mysql:
select i.*,
(case when WordPos < 11 then 1
when WordPos < 50 then 2
when WordPos < 100 then 3
else 4
end) as rank
from (select i.*,
(select top 1 wpwordposition
from wordpositions wp
where recordid=i.pk_itemid and wordid=79588 and nextwordid=64502
) as WordPos
from items i
) i;
This also simplifies the case statement. You do not need nested case statements to handle multiple conditions, just multiple where clauses.
No. Identifiers introduced in the output clause (the fact that it comes from a sub-query is irrelevant) cannot be used within the same SELECT statement.
Here are some solutions:
Rewrite the query using a JOIN1, This will eliminate the issue entirely and fits well with RA.
Wrap the entire SELECT with the sub-query within another SELECT with the case. The outer select can access identifiers introduced by the inner SELECT's output clause.
Use a CTE (if SQL Server). This is similar to #2 in that it allows an identifier to be introduced.
While "re-writing" the sub-query for each case is very messy it should still result in an equivalent plan - but view the query profile! - as the results of the query are non-volatile. As such the equivalent sub-queries can be safely moved by the query planner which should move the sub-query/sub-queries to a JOIN to avoid any "re-running" in the first place.
1 Here is a conversion to use a JOIN, which is my preferred method. (I find that if a query can't be written in terms of a JOIN "easily" then it might be asking for the wrong thing or otherwise be showing issues with schema design.)
select
wp.wordposition as WordPos,
case wp.wordposition .. as Rank
from items i
left join wordpositions wp
on wp.recordid = i.pk_itemid
where wp.wordid = 79588
and wp.nextwordid = 64502
I've made assumptions about the multiplicity here (i.e. that wordid is unique) which should be verified. If this multiplicity is not valid and not correctable otherwise (and you're indeed using SQL Server), then I'd recommend using ROW_NUMBER() and a CTE.

sql with logical operation

hi here is my small problem . im working on sql and i have some logical operations to get the values from the sql database . i have the screen shot . plz refer it
in that i have four query in different combinations. if you take 1 and 2 both gives me the same answer and 3 and 4 gives me different answer . Now my question in i have two operators 1.OR and 2.And Not and the filters while means the variable may be n . now my question is
i want to get the different combinations for the given variables
and have to eliminate the possibility which gives me same result
any algorithms coding are welcome
can anyone help me soon
update
for more clear
if i have four values namely a,b,c,d
then i have to frame the diff combinations like
1. (a or b) and not( c or d)
2. a or ( b and not c ) or d
i have updated my question .. like this i have to generate different combination and get the answer
If you want to compare multiple rows within a set you can't use the logic you showed in your example, because a single row can't have multiple values within a singe column.
A common solution is to use aggregation over this group of rows and move the conditions into CASEs in HAVING checking if there's any row paasing the check:
e.g. your 2nd select,
(code = 40660 or code = 40900) and not code = 41180
can be simplified to
(code in (40660, 40900)) and code <> 41180
Translated into HAVING:
SELECT grpcol
FROM tab
GROUP BY grpcol
HAVING
-- any row with a code 40660 or 40900 --> result > 0 --> TRUE
SUM(CASE WHEN code IN (40660, 40900) THEN 1 ELSE 0 END) > 0
AND
-- any row with code 41180 --> result > 0 --> FALSE
SUM(CASE WHEN code <> 41180 THEN 1 ELSE 0 END) = 0

Optimize complicated SQL Update

Somebody at work made this UPDATE some years ago and itt works, the problem is it's taking almost 5 hours when called multiple times in a process, this is not a regular UPDATE, there is no 1 to 1 record matching between tables, this does an update based on accumulative (SUM) of a parituclar field in the same table, and things get more complicated because this SUM is restricted to special conditions based on dates and another field.
I think this is something like an (implicit) inner join with no 1 to 1 match, like ALL VS ALL, so when having for example 7000 records in the table this thing will process 7000 * 7000 records, more than 55 million, in my opinion cursors should have been used here, but now i need more speed and i don't think cursors will get me there.
My question is: Is there any way to rewrite this and make it faster?? Pay attention to the conditions on that SUM, this is not an easy to see UPDATE (at least for me).
More info:
CodCtaCorriente and CodCtaCorrienteMon are primary keys on this table but, as I said before there is no intention to make a 1 to 1 match here that's why this keys are not used in the query, CodCtaCorrienteMon is used in conditions but not as a join condition (ON).
UPDATE #POS SET SaldoDespuesEvento =
(SELECT SUM(Importe)
FROM #POS CTACTE2
WHERE CTACTE2.CodComitente = #POS.CodComitente
AND CTACTE2.CodMoneda = #POS.CodMoneda
AND CTACTE2.EstaAnulado = 0
AND (DATEDIFF(day, CTACTE2.FechaLiquidacion, #POS.FechaLiquidacion) > 0
OR
(DATEDIFF(day, CTACTE2.FechaLiquidacion, #POS.FechaLiquidacion) = 0
AND (#POS.CodCtaCorrienteMon >= CTACTE2.CodCtaCorrienteMon))))
WHERE #POS.EstaAnulado = 0 AND #POS.EsSaldoAnterior = 0
From your query plan it looks like its spending most of the time in the filter right after the index spool.
If you are going to run this query a few times, I would create an index on the 'CodComitente', 'CodMoneda', 'EstaAnulado', 'FechaLiquidacion', and 'CodCtaCorrienteMon' columns.
I don't know much about the Index Spool iterator; but basically from what I understand about it, its used as a 'temporary' index created at query time. So if you are running this query multiple times, I would create that index once, then run the query as many times as you need.
Also, I would try creating a variable to store the result of your sum operation, so you can avoid running that as much as possible.
DECLARE #sumVal AS INT
SET #sumVal = SELECT SUM(Importe)
FROM #POS CTACTE2
WHERE CTACTE2.CodComitente = #POS.CodComitente
AND CTACTE2.CodMoneda = #POS.CodMoneda
AND CTACTE2.EstaAnulado = 0
AND (DATEDIFF(day, CTACTE2.FechaLiquidacion, #POS.FechaLiquidacion) > 0
OR
(DATEDIFF(day, CTACTE2.FechaLiquidacion, #POS.FechaLiquidacion) = 0
AND (#POS.CodCtaCorrienteMon >= CTACTE2.CodCtaCorrienteMon)))
UPDATE #POS SET SaldoDespuesEvento = #sumVal
WHERE #POS.EstaAnulado = 0 AND #POS.EsSaldoAnterior = 0
It is hard to help much without the query plan but I would make the an assumption that if there is not already indexes on the FechaLiquidacion and CodCtaCorrienteMon columns then performance would be improved by creating them as long as database storage space is not an issue.
Found the solution, this is a common problem: Running Totals
This is one of the few cases CURSORS perform better, see this and more available solutions here (or browse stackoverflow, there are many cases like this):
http://weblogs.sqlteam.com/mladenp/archive/2009/07/28/SQL-Server-2005-Fast-Running-Totals.aspx

How can you use COUNT() in a comparison in a SELECT CASE clause in Sql Server?

Let's say you want do something along the following lines:
SELECT CASE
WHEN (SELECT COUNT(id) FROM table WHERE column2 = 4) > 0
THEN 1 ELSE 0 END
Basically just return 1 when there's one or more rows in the table, 0 otherwise. There has to be a grammatically correct way to do this. What might it be? Thanks!
Question: return 1 when there's one or more rows in the table, 0 otherwise:
In this case, there is no need for COUNT. Instead, use EXISTS, which rather than counting all records will return as soon as any is found, which performs much better:
SELECT CASE
WHEN EXISTS (SELECT 1 FROM table WHERE column2 = 4)
THEN 1
ELSE 0
END
Mahmoud Gammal posted an answer with an interesting approach. Unfortunately the answer was deleted due to the fact that it returned the count of records instead of just 1. This can be fixed using the sign function, leading to this more compact solution:
SELECT sign(count(*)) FROM table WHERE column2 = 4
I posted this because I find it an interesting approach. In production I'd usually end up with something close to RedFilter's answer.
You could do this:
SELECT CASE WHEN COUNT(ID) >=1 THEN 1 WHEN COUNT (ID) <1 THEN 0 END FROM table WHERE Column2=4
Reference:
http://msdn.microsoft.com/en-us/library/ms181765.aspx