Sql - conditional subqueries - sql

Say I have a table with three columns,a b and c.
I want to query this table with three values, in such a way:
Select all rows where a = value 1
If there are less than 10 such rows, return those, otherwise select all rows within those where b = value 2
Repeat for c and value 3
Is it possible to do this within a single query?

Try this (assuming SQL Server):
SELECT TOP 10 *
FROM YourTable X
ORDER BY (CASE WHEN X.a = value1 THEN 0 ELSE 1 END),
(CASE WHEN X.b = value2 THEN 0 ELSE 1 END),
(CASE WHEN X.c = value3 THEN 0 ELSE 1 END)

Related

PostgreSQL - Removing NULLS row and column from conditional aggregation results

I have a query for a multidimensional table using conditional aggregation
select A,
SUM(case when D = 3 then D end) as SUM_D1,
SUM(case when D = 4 then D end) as SUM_D2)
The result:
A SUM_D1 SUM_D2
-------------------
a1 100 NULL
a1 200 NULL
a3 NULL NULL
a4 NULL NULL
However, I would like to hide all NULL rows and columns as follows:
A SUM_D1
-----------
a1 100
a1 200
I have looked for similar problems but they are not my expected answer.
Any help is much appreciated,
Thank you
I think this does what you want:
select A,
coalesce(sum(case when D = 3 then D end),
sum(case when D = 4 then D end)
) as sum_d
from t
group by A
having sum(case when d in (3, 4) then 1 else 0 end) > 0;
Note that this returns only one column -- as in your example. If both "3" and "4" are in the data, then the value is for the "3"s.
If you want a query that returns a variable number of columns, then you need to use dynamic SQL -- or some other method. SQL queries return a fixed number of columns.
One method would be to return the values as an array:
select a,
array_agg(d order by d) as ds,
array_agg(sumd order by d) as sumds
from (select a, d, sum(d) as sumd
from t
where d in (3, 4)
group by a, d
) d
group by a;
To filter all-NULL rows you can use HAVING
select *
from
(
select A,
SUM(case when D = 3 then D end) as SUM_D1,
SUM(case when D = 4 then D end) as SUM_D2)
...
) as dt
where SUM_D1 is not null
and SUM_D2 is not null
Of course, if you got simple conditions like the ones in your example you better filter before aggregation:
select A,
SUM(case when D = 3 then D end) as SUM_D1,
SUM(case when D = 4 then D end) as SUM_D2)
...
where D in (3,4)
Now at least one calculation will return a value, thus no need to check for all-NULL.
To filter all-NULL columns you need some Dynamic SQL:
materialize the data in a temporary tabke using Insert/Select
scan each column for all-NULL select 1 from temp having count(SUM_D1) > 0
dynamically create the Select list based on this
run the Select
But why do you think you need this? It will be confusing for a user to run the same Stored Procedure and receive a different number of columns for each run.
I may have misinterpreted your question because the solution seems so simple:
select A,
SUM(case when D = 3 then D end) as SUM_D1,
SUM(case when D = 4 then D end) as SUM_D2)
where D is not null
This is not what you want, is it? :-)
Null appear because the condition that's not handled by case statement
select A,
SUM(case when D = 3 then D end) as SUM_D1,
SUM(case when D = 4 then D end) as SUM_D2
from
Table1
group by
A
having
(case when D = 3 or D = 4 then D end) is not null
As comment said if you want to suppress the null value.. You can use having to suppress null using is not null

Return multiple rows from conditional group by without union

I am trying to build a query which supports conditional group by in SQLite DB.
Here is what I tried so far:
SELECT
case
when A>1 AND B>1 THEN 1
when X>1 AND Y>1 THEN 2
when C>1 AND D>1 THEN 3
END AS data_grp,
SUM(col1) AS col1,
SUM(col2) AS col2
FROM tbl
GROUP BY data_grp;
This Works pretty fine if only single case is true at a time. if multiple cases are true in a row then it returns the first case instead of all satisfying groups.
I have tried this by the union which works well but very slow. Is there any other way to fetch results fast with this conditional group.
Sample Data & Expected results:
DROP TABLE IF EXISTS tbl;
CREATE TABLE tbl
(
A INT,
B INT,
C INT,
D INT,
X INT,
Y INT,
col1 int,
col2 int
);
INSERT INTO tbl(A,B,C,D,X,Y,col1,col2) values (2,3,0,0,0,0,5,10);
INSERT INTO tbl(A,B,C,D,X,Y,col1,col2) values (0,0,0,0,8,10,3,2);
INSERT INTO tbl(A,B,C,D,X,Y,col1,col2) values (5,4,4,9,0,0,3,2);
SELECT
case
when A>1 AND B>1 THEN 1
when X>1 AND Y>1 THEN 2
when C>1 AND D>1 THEN 3
END AS data_grp,
SUM(col1) AS col1,
SUM(col2) AS col2
FROM tbl
GROUP BY data_grp;
Query Output :
"1" "8" "12"
"2" "3" "2"
Expected Output :
"1" "8" "12"
"2" "3" "2"
"3" "3" "2"
You can not use GROUP BY directly because of the overlapping groups.
You can use something like following, although this may also be slow.
WITH RECURSIVE
cnt(x) AS (
SELECT 1
UNION ALL
SELECT x+1 FROM cnt
LIMIT 3
)
SELECT x as data_grp, sum(col1), sum(col2)
FROM cnt,
(SELECT
case when A>1 AND B>1 THEN 1 ELSE 0 END as dg1,
case when X>1 AND Y>1 THEN 2 ELSE 0 END as dg2,
case when C>1 AND D>1 THEN 3 ELSE 0 END as dg3,
col1, col2
FROM tbl) t WHERE x=dg1 or x=dg2 or x=dg3
GROUP BY x
I am wary of summarizing data, where the result is on multiple rows and the totals don't match the original data. Of course, sometimes it is necessary, but here are two alternatives.
If you can be slightly flexible in your results, then you can concat the conditions together to get a more complex group:
SELECT ( (CASE WHEN A > 1 AND B > 1 THEN '1' ELSE '' END) ||
(CASE WHEN X > 1 AND Y > 1 THEN '2' ELSE '' END) ||
(CASE WHEN C > 1 AND D > 1 THEN '3' ELSE '' END)
) AS data_grp,
SUM(col1) AS col1, SUM(col2) AS col2
FROM tbl
GROUP BY data_grp;
I would actually write this as:
SELECT ( (CASE WHEN A > 1 AND B > 1 THEN '1' ELSE '0' END) ||
(CASE WHEN X > 1 AND Y > 1 THEN '1' ELSE '0' END) ||
(CASE WHEN C > 1 AND D > 1 THEN '1' ELSE '0' END)
) AS data_grp,
So data_grp gets a string of 0's and 1's indicating the group.
These results are not the same as your results. They are more what I would want, if I were looking at different groups -- I would want to see the overlaps between the groups.
Or, I would put the values in separate columns:
SELECT SUM(CASE WHEN A > 1 AND B > 1 THEN col1 ELSE 0 END) as sum1_1,
SUM(CASE WHEN X > 1 AND Y > 1 THEN col1 ELSE 0 END) as sum1_2,
SUM(CASE WHEN C > 1 AND D > 1 THEN col1 ELSE 0 END) as sum1_3,
SUM(CASE WHEN A > 1 AND B > 1 THEN col2 ELSE 0 END) as sum2_1,
SUM(CASE WHEN X > 1 AND Y > 1 THEN col2 ELSE 0 END) as sum2_2,
SUM(CASE WHEN C > 1 AND D > 1 THEN col2 ELSE 0 END) as sum2_3
FROM tbl;
These are the same results, but pivoted differently.

conditional aggregation on two different groups in SQL

Here is my data
COUNTYID POLLUTANT TYPE EMISSION
1 A 1
1 A 2
1 B 1
1 B 2
2 A 1
2 A 2
2 B 1
2 B 2
3 A 1
3 A 2
3 B 1
3 B 2
if I do
SELECT sum(EMISSION) from table where POLLUTANT = 'A' group by COUNTYID;
I would get pollution from Polutant 'A'. how can I write a query to get following data:
column 1 with sum of A, column 2 with sum of B, column 3 with sum of A and B?
Thank you
You can use case for filter the value you need
select COUNTYID, sum(case when POLLUTANT='A'then EMISSION else 0 END) tot_a
, sum(case when POLLUTANT='B'then EMISSION else 0 END) tot_b
, sum(EMISSION) tot_a_b
from my_table
group by COUNTYID
You can use conditional aggregation. This moves the filtering conditions from the where clause to the sum()s:
select countyid,
sum(case when emission = 'A' then emission else 0 end) as A,
sum(case when emission = 'B' then emission else 0 end) as B,
sum(emission) as total
from t
group by countyid;
I would like to give some idea. We can use pivot table for answer your question
SELECT * from dbo.[Table_1]
PIVOT
(Sum([type_emmssion]) for [polutant] in ([A], [B]) ) as PivotTable
group by [CountryId] ;
you have to use case statemet:
SELECT
SUM( CASE WHEN POLLUTANT = 'A' THEN EMISSION ELSE 0 END) AS A_EMISSION
SUM( CASE WHEN POLLUTANT = 'B' THEN EMISSION ELSE 0 END) AS B_EMISSION
SUM(EMISSION) AS total_emission
FROM table
GROUP BY COUNTYID;

Oracle query with group

I have a scenario where I need to fetch all the records within an ID for the same source. Given below is my input set of records
ID SOURCE CURR_FLAG TYPE
1 IBM Y P
1 IBM Y OF
1 IBM Y P
2 IBM Y P
2 TCS Y P
3 IBM NULL P
3 IBM NULL P
3 IBM NULL P
4 IBM NULL OF
4 IBM NULL OF
4 IBM Y ON
From the above settings, I need to select all the records with source as IBM within that same ID group.Within the ID group if there is at least one record with a source other than IBM, then I don't want any record from that ID group. Also, we need to fetch only those records where at least one record in that ID group with curr_fl='Y'
In the above scenario even though the ID=3 have a source as IBM, but there is no record with CURR_FL='Y', my query should not fetch the value.In the case of ID=4, it can fetch all the records with ID=4, as one of the records have value='Y'.
Also within the group which has satisfied the above condition, I need one more condition for source_type. if there are records with source_type='P', then I need to fetch only that record.If there are no records with P, then I will search for source_type='OF' else source_type='ON'
I have written a query as given below.But it's running for long and not fetching any results. Is there any better way to modify this query
select
ID,
SOURCE,
CURR_FL,
TYPE
from TABLE a
where
not exists(select 1 from TABLE B where a.ID = B.ID and source <> 'IBM')
and exists(select 1 from TABLE C where a.ID = C.ID and CURR_FL = 'Y') and
(TYPE, ID) IN (
select case type when 1 then 'P' when 2 then 'OF' else 'ON' END TYPE,ID from
(select ID,
max(priority) keep (dense_rank first order by priority asc) as type
from ( select ID,TYPE,
case TYPE
when 'P' then 1
when 'OF' then 2
when 'ON' then 3
end as priority
from TABLE where ID
in(select ID from TABLE where CURR_FL='Y') AND SOURCE='IBM')
group by ID))
I think you can just do a single aggregation over your table by ID and check for the yes flag as well as assert that no non IBM source appears. I do this in a CTE below, and then join back to your original table to return full matching records.
WITH cte AS (
SELECT
ID,
CASE WHEN SUM(CASE WHEN TYPE = 'P' THEN 1 ELSE 0 END) > 0
THEN 1
WHEN SUM(CASE WHEN TYPE = 'OF' THEN 1 ELSE 0 END) > 0
THEN 2
WHEN SUM(CASE WHEN TYPE = 'ON' THEN 1 ELSE 0 END) > 0
THEN 3 ELSE 4 END AS p_type
FROM yourTable
GROUP BY ID
HAVING
SUM(CASE WHEN CURR_FLAG = 'Y' THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN SOURCE <> 'IBM' THEN 1 ELSE 0 END) = 0
)
SELECT t1.*
FROM yourTable t1
INNER JOIN cte t2
ON t1.ID = t2.ID
WHERE
t2.p_type = 1 AND t1.TYPE = 'P' OR
t2.p_type = 2 AND t1.TYPE = 'OF' OR
t2.p_type = 3 AND t1.TYPE = 'ON';

To retrieve records having only two specific values

Have the following Data in the table
Example Table
ID Value
1 a
1 b
1 c
2 a
2 b
2 c
3 a
3 b
I need to retrieve records having ID with only two values a and b.
So i am expecting only the Record with ID 3 .
Can anyone help me with the query
I guess you could do something like
select
ID,
sum(case when value = 'a' then 1
when value = 'b' then 1
else 3 end)
from
table1
group by id
having
sum (case when value = 'a' then 1
when value = 'b' then 1
else 3 end) =2
SQL Fiddle
That will work:
select x.id from
(
select id from mytable where value = 'a'
union all
select id from mytable where value = 'b'
) x
group by x.id
having COUNT(*) = 2
and not exists (select * from mytable t where t.id = x.id and value <> 'a' and value <> 'b')