What is best way to merge 2 select statement? - sql

I have a sql server query statement like this:
WITH A
AS (
SELECT (
SUM(CASE
WHEN (t1.price) > 0
THEN (t1.price)
ELSE 0
END)
) AS pr1
,(
ABS(SUM(CASE
WHEN (t1.price) < 0
THEN (t1.price)
ELSE 0
END))
) AS pr2
FROM dbo.price_table AS t1
)
,B
AS (
SELECT (WHEN(pr1 - pr2) < 0 THEN ABS(pr1 - pr2) ELSE 0 END) AS res
FROM A
)
SELECT res
FROM B
in my query, i use 2 select statement to achieve "res" column, but i want achieve to "res" column in 1 select statement.
what is best way for merge 2 select statement to 1 select statement query?

Your calculation seems way to complicated. You are taking the sum of the positive values. Then the sum of the negative values, using ABS() to make that value positive, and subtracting this result. Guess what? That is the same as taking the SUM() of all the values in the first place.
So, I think this statement is equivalent:
SELECT (CASE WHEN SUM(t1.price) < 0
THEN ABS(SUM(t1.price))
ELSE 0
END)
FROM dbo.price_table t1;

Common Table Expressions (CTEs) are a way of writing reusable sub-queries. There's not one method that works with all CTEs (CTEs can be self-referencing which is not possible with subqueries_. In your example, the query
WITH A AS
(
{A query}
)
,B AS
(
SELECT ... res FROM A
)
SELECT res FROM B
could be rearranged to
SELECT res FROM
(
SELECT {expression} res FROM
(
{A query}
)
)
which is just
SELECT {expression} res FROM
(
{A query}
)

your solution is fine.
Even thou you use multipple "Select" statements, they will compile in one query.
So your query is equivalent to the query proposed by Gordon Linoff.
If you have more complicated logic you can create SINGLE query: take your last "Select ..." and substituse "pr1" and "pr2" with subqueries (or expressions). You may receive very complex statement with repeatable parts.
When you use "WITH" you split logical parts with good readability, while performance is not decreased. I always prefere "WITH" in complicate queries.

Related

Why Using COALESCE or CASE keep returning null

I have the following SQL Query :
(SELECT ROUND(SUM(NBTOSUM)/1000000,1) FROM MyTable t2 WHERE t2.ELEMNAME IN ('A','B','C'))
Which works fine.
But Where there is no 'A','B','C' the result of the select is (null)
So to handle it, I did the following :
(SELECT COALESCE(ROUND(SUM(NBTOSUM)/1000000,1),0) FROM MyTable t2 WHERE t2.ELEMNAME IN ('A','B','C'))
And also try :
(SELECT
CASE
WHEN SUM(NBTOSUM)/1000000 IS NULL THEN 0
ELSE ROUND(SUM(NBTOSUM)/1000000,1)
END
FROM MyTable t2 WHERE t2.ELEMNAME IN ('A','B','C'))
But both keep returning null
What am I doing wrong ?
Move the WHERE restrictions to the CASE expression as well:
SELECT ROUND(SUM(CASE WHEN t2.ELEMNAME IN ('A','B','C')
THEN NBTOSUM ELSE 0 END) / 1000000, 1)
FROM MyTable t2;
Note that this trick solves the null problem and also avoids the need for an ugly COALESCE() call.
Your code should work as the SUM aggregation function will generate a single row of output regardless of whether the number of input rows is zero or non-zero. If there are no input rows or the values are all NULL then the output of the SUM will be NULL and then COALESCE would work.
Since you claim it does not then that suggests that there is something else going on in your query that you have not shared in the question.
You have braces around your statement suggesting that you are using it as part of a larger statement. If so, you can try moving the COALESCE to the outer query:
SELECT COALESCE(
(
SELECT ROUND(SUM(NBTOSUM)/1000000,1)
FROM MyTable
WHERE ELEMNAME IN ('A','B','C')
),
0
)
FROM your_outer_query;
That might fix the problem if you are somehow correlating to an outer query but your question makes no mention of that.
fiddle

Using A Count And A Case Statement In One Query

I'm pretty much out of ideas on how to get this to work.I haven't really used SQL in several years so there's a lot I don't remember.
So here is what I would like to happen:
I return the rows where the Code field from table has the value 1208 AND estnumber = 1187216
Run a count on the selection, if 0 run a subquery
If >0 run a different subquery
I didn't get to the subquery part yet because I can't get this to work correctly at all. Right now I just want it to return text.
Here is the latest attempt, I'm actually using db2 but maybe we can ignore that for now and i'll work that part out later because it says the syntax isnt correct, but other validators disagree (if you dont know anything about db2 just use standard sql when giving advice)
SELECT
count(*) AS t
FROM
table
WHERE
(
ESTNUMBER = 1187216
AND CODE = 1208
)
AND CASE WHEN t = 0 THEN 'it is zero' ELSE 'it is not zero' END;
Are you trying to do something like this?
WITH c AS (
SELECT count(*) AS cnt
FROM table
WHERE ESTNUMBER = 1187216 AND CODE = 1208
)
SELECT s1.*
FROM subquery1 s1
WHERE (SELECT cnt FROM c) = 0
UNION ALL
SELECT s2.*
FROM subquery2 s2
WHERE (SELECT cnt FROM c) > 0;
This assumes that the columns returned by the subqueries are compatible (same number, same types).
There are better ways to write this query (notably using EXISTS and NOT EXISTS), but this conforms directly to how you asked the question.
The string value should come up in the select clause and not in the where filter.
SELECT
count(*) AS t,
(CASE WHEN count(*) = 0 THEN 'it is zero' ELSE 'it is not zero' END) display_str
FROM
table
WHERE
(
ESTNUMBER = 1187216
AND CODE = 1208
)
You're thinking like an imperative programmer, not a declarative one. That is, SQL doesn't have sequential execution: it's all or nothing.
So, here's the start, the bit that works:
SELECT count(*) AS t
FROM table
WHERE ESTNUMBER = 1187216 AND CODE = 1208
Now, to check for the value of count(*), you by now know that WHERE isn't going to work. That's because COUNT is an aggregate function. To look at the result of such of function, you use HAVING.
For your CASE to work, you can move it up into the area that can get count(*) results:
SELECT count(*) AS t
(CASE WHEN count(*) = 0 THEN 'it is zero' ELSE 'it is not zero' END) as msg
FROM table
WHERE ESTNUMBER = 1187216 AND CODE = 1208
Note that "t" is an alias you've given the result of count(*). In most SQL implementations, that alias can't be leveraged in the rest of the statement.
Now, for the either or kind of thing, it would be time to reconsider your approach and what you're really after. You'll probably ultimately have both result sets in your statement and choose how the results are served up.
Something like:
select a.id, a.ct, (case when a.ct=0 then b.amt else c.amt end) as amt
from (select id, count(*) as ct from table1) a
left join (select id, sum(amount) as amt from table2) b on a.id=b.id
left join (select id, sum(amount) as amt from table3) c on a.id=c.id
Hope this helps.

Filter if values provided otherwise return everything

Say I have a table t with 2 columns:
a int
b int
I can do a query such as:
select b
from t
where b > a
and a in(1,2,3)
order by b
where 1,2,3 is provided from the outside.
Obviously, the query can return no rows. In that case, I'd like to select everything as if the query did not have the and a in(1,2,3) part. That is, I'd like:
if exists (
select b
from t
where b > a
and a in(1,2,3)
)
select b
from t
where b > a
and a in(1,2,3)
order by b
else
select b
from t
where b > a
order by b
Is there a way to do this:
Without running two queries (one for exists, the other one the actual query)
That is less verbose than repeating queries (real queries are quite long, so DRY and all that stuff)
Using NOT EXISTS with a Sub Query to Determine if condition exists
SELECT b
FROM
t
WHERE
b > a
AND (
NOT EXISTS (SELECT 1 FROM #Table WHERE a IN (1,2,3))
OR a IN (1,2,3)
)
ORDER BY
b
The reason this works is because if the condition exists then the OR statement will include the rows and if the condition does not exist then the NOT EXISTS will include ALL rows.
Or With Common Table Expression and window Function with Conditional Aggregation.
WITH cte AS (
SELECT
b
,CASE WHEN a IN (1,2,3) THEN 1 ELSE 0 END as MeetsCondition
,COUNT(CASE WHEN a IN (1,2,3) THEN a END) OVER () as ConditionCount
FROM
t
)
SELECT
b
FROM
cte
WHERE
(ConditionCount > 0 AND MeetsCondition = 1)
OR (ConditionCount = 0)
ORDER BY
b
I find it a bit "ugly". Maybe it would be better to materialize output from your query within a temp table and then based on count from temp table perform first or second query (this limits accessing the original table from 3 times to 2 and you will be able to add some flag for qualifying rows for your condition not to repeat it). Other than that, read below . . .
Though, bear in mind that EXISTS query should execute pretty fast. It stops whether it finds any row that satisfies the condition.
You could achieve this using UNION ALL to combine resultset from constrained query and full query without constraint on a column and then decide what to show depending on output from first query using CASE statement.
How CASE statement works: when any row from constrained part of your query is found, return resultset from constrainted query else return everything omitting the constraint.
If your database supports using CTE use this solution:
with tmp_data as (
select *
from (
select 'constraint' as type, b
from t
where b > a
and a in (1,2,3) -- here goes your constraint
union all
select 'full query' as type, b
from t
where b > a
) foo
)
SELECT b
FROM tmp_data
WHERE
CASE WHEN (select count(*) from tmp_data where type = 'constraint') > 0
THEN type = 'constraint'
ELSE type = 'full query'
END
;

Assign null if subquery retrieves multiple records. How can it be done?

I have the following query. I simplified it for demo purpose. I am using SQL Server - t-sql
Select tm.LocID = (select LocID from tblLoc tl
where tl.LocID = tm.LodID )
from tblMain tm
if the subquery returns multiple records, I like to assign tm.LocID to null else if there is only 1 record returned then assign it to tm.LocID. I am looking for a simple way to do this. Any help would be appreciated.
One way I can see is to have a CASE statement and check if (Count * > 1 ) then assign null else return the value but that would require a select statement within a select statement.
You have the right idea about using a case expression for count(*), but it will not require another subquery:
SELECT tm.LocID = (SELECT CASE COUNT(*) WHEN 1 THEN MAX(LocID) END
FROM tblLoc tl
WHERE tl.LocID = tm.LodID )
FROM tblMain tm
or just use a HAVING clause, like
Select tm.LocID = (select LocID from tblLoc tl
where tl.LocID = tm.LodID
group by locID
having count(*) = 1)
)
from tblMain tm
Your query above (and many of the other answers here) is a correlated subquery which will be very slow since it performs a separate aggregation query on each record. This following will address both your problem and potentially perform a bit better since the count happens in a single pass.
SELECT
CASE
WHEN x.locid IS NOT NULL THEN x.locid
ELSE NULL
END
FROM tblMain m
LEFT JOIN (
SELECT
locid
FROM tblLoc
GROUP BY locid
HAVING COUNT(1) = 1
) x
ON x.locid = m.locid
;
The above is in Postgres syntax (what I'm familiar with) so you would have to make it TSQL compatible.

How to count specific values in a table

I've a column that have 15 distinct values. I'd like to count how many there are of a few of them,
I've come up with e.g.
select a,COUNT(IFNULL(b != 1,NULL)),COUNT(IFNULL(b != 2,NULL)) from
mytable group by a
select a,SUM(CASE WHEN a = 1 THEN 1 ELSE 0)),SUM(CASE WHEN a = 2 THEN 1 ELSE 0)) from
mytable group by a
What's the best way of doing this ? (note, I need to pivot those values to columns,
a simple select a,b,count(*) from mytable where b=1 or b=2 group by a,b; won't do.)
Of the two methods suggested in the question, I recommend the second:
select a,
SUM(CASE WHEN b = 1 THEN 1 ELSE 0) b1,
SUM(CASE WHEN b = 2 THEN 1 ELSE 0) b2
from mytable
group by a
- as it is both simpler and (I think) easier to understand, and therefore to maintain. I recommend including column aliases, as they make the output easier to understand.
First of all you misunderstood the IFNULL function (you probably wanted IF). See the documentation http://dev.mysql.com/doc/refman/5.0/en/control-flow-functions.html .
The second query you have in your question will give you what you want. But SUM(a=x) is more than sufficient. In MySQL true is equal to 1 and false is equal to 0.
have u try cross join?
select *
from (
select a, sum(...) as aSum
from mytable
where a...
group
by a
) as forA
cross join (
select b, sum(...) as bsum
from (
select *
from mytable
where b...
group
by b
)
) as forB;