Sql grouping trouble - sql

I got in trouble with a sql script as this:
SELECT A, B, C,
CASE WHEN D < 21 THEN '0<20'
WHEN D < 51 THEN '21-50'
WHEN D < 101 THEN '51-100'
ELSE '>101' END AS E
COUNT(*) FROM TABLE_X
GROUP BY A, B, C, D;
Resultset like this;
A B C D count(*)
CAR 1 2 21-50 1
CAR 1 2 21-50 1
BIKE 1 3 0-20 1
At first row is CAR has a D=25.So it is between 21-50.
And then second row is CAR has D=32.So it is between 21-50 too.
Shortly I want to resultset like above:
A B C D count(*)
CAR 1 2 21-50 2
BIKE 1 3 0-20 1
So CAR must be 2 by grouping as using D column.
How can I assure this ?

The problem here is that you're grouping by D first and only then applying the case logic. If you add D to the select list, you'd see results that probably look like this:
A B C D E count(*)
CAR 1 2 20 21-50 1
CAR 1 2 30 21-50 1
BIKE 1 3 7 0-20 1
In order to avoid this, you could apply the case first and only then the group by clause, by using a subquery:
SELECT A, B, C, E, COUNT(*)
FROM (SELECT A, B, C,
CASE WHEN D < 21 THEN '0<20'
WHEN D < 51 THEN '21-50'
WHEN D < 101 THEN '51-100'
ELSE '>101' END AS E
FROM TABLE_X) t
GROUP BY A, B, C, E;

The below query should work. Basically, I am just pulling the count(1) function and hence the group by clause to an outer query while leaving all the rest functionality to the inner query.
SELECT A,B,C,E, count(1) from
(
SELECT A, B, C,
CASE WHEN D < 21 THEN '0<20'
WHEN D < 51 THEN '21-50'
WHEN D < 101 THEN '51-100'
ELSE '>101' END AS E
FROM TABLE_X
)
GROUP BY A, B, C, E;

Group by the calculation for D, not D itself, like this:
SELECT A, B, C,
CASE WHEN D < 21 THEN ' 0-20'
WHEN D < 51 THEN '21-50'
WHEN D < 101 THEN '51-100'
ELSE '>101' END AS E
,COUNT(*) as "Coun"
FROM TABLE_X
GROUP BY A, B, C,
CASE WHEN D < 21 THEN ' 0-20'
WHEN D < 51 THEN '21-50'
WHEN D < 101 THEN '51-100'
ELSE '>101' END
yields this
A B C E Count
---- ----------- ----------- ------ -----------
BIKE 1 3 0-20 1
CAR 1 2 21-50 2
when run in SQL Server 2012 on a table loaded with these values:
values
('CAR', 1,2,22)
,('CAR', 1,2,23)
,('BIKE',1,3,2)

You can also try below query:
SELECT A, B, C,
CASE WHEN D < 21 THEN '0<20'
WHEN D < 51 THEN '21-50'
WHEN D < 101 THEN '51-100'
ELSE '>101' END AS E
COUNT(*) FROM TABLE_X
GROUP BY 1,2,3,4;
We use the SQL GROUP BY clause to group by relative position in the result set, where the first field in the result set is 1. The next field is 2, and so on.

Related

Oracle SQL: Transfer certain records from one table to another filtering rows based on condition

Need to transfer certain records of some columns from Table1 to Table2 but filtering rows based on condition.
Lets say Table1 looks like as shown below, has many columns in it.
Table1
A B C D E F G H ...
1 24-OCT-20 08.22.57.642000000 AM 100 xyz 1 1
2 24-OCT-20 08.22.57.642000000 AM 100 xyz 1 0
13 25-OCT-20 05.47.52.733000000 PM 100 xyz 1 0
34 26-OCT-20 09.22.57.642000000 AM 100 xyz 1 0
25 26-OCT-20 09.25.57.642000000 AM 101 xyz 1 0
26 26-OCT-20 09.25.57.642000000 AM 101 xyz 1 1
6 26-OCT-20 09.25.57.642000000 AM 101 abc 1 1
10 26-OCT-20 09.25.57.642000000 AM 101 xyz 0 1
17 26-OCT-20 04.22.57.642000000 AM 100 xyz 1 0
18 26-OCT-20 06.22.57.642000000 AM 105 xyz 1 1
19 26-OCT-20 06.22.57.642000000 AM 105 xyz 1 0
In Table2, need to insert rows from Table1 based on following:
First, select A, B, C, D, E, F from Table1 where D='xyz' and E=1; and on the result of this query apply the following condition to further filter out unwanted rows:
Condition: For same values in columns B, C, D & E in 2 different rows, if column F has 2 different values then only keep the row with greater value in column A.
So desired output in Table2 is shown as below:
Table2
A B C D E F
2 24-OCT-20 08.22.57.642000000 AM 100 xyz 1 0
13 25-OCT-20 05.47.52.733000000 PM 100 xyz 1 0
34 26-OCT-20 09.22.57.642000000 AM 100 xyz 1 0
26 26-OCT-20 09.25.57.642000000 AM 101 xyz 1 1
17 26-OCT-20 04.22.57.642000000 AM 100 xyz 1 0
19 26-OCT-20 06.22.57.642000000 AM 105 xyz 1 0
How can this be achieved through the simplest and most efficient SQL query?
Any help will be appreciated.
You can use window functions:
insert into table2 (a, b, c, d, e, f)
select a, b, c, d, e, f
from (
select t1.*,
row_number() over(partition by b, c, d, e order by a desc) rn
from table1 t1
where d = 'xyz' and e = 1
) t1
where rn = 1
This can be achieved using the GROUP BY and KEEP clause as follows:
select max(t.a) as a, t.b, t.c, t.d, t.e,
max(t.f) keep (dense_rank last over order by t.a) as f
from t
where t.d = 'xyz' and t.e = 1
group by t.b, t.c, t.d, t.e

Display 0 when count(*) is zero for a group by clause

I am trying to write a SQL query which can display value as 0 if there are no rows for the specified condition
I have tried the following so far but nothing seems to work
coalesce(count(m.a),'0')
isnull(count(m.a),'0')
case when count(*) > 0 then count(*) else '0' end
select M.a, m.b, m.c, m.d, m.e,
--coalesce(count(m.a),'0') as CountOfRecords
--isnull(count(m.a),'0') as CountOfRecords
--case when count(*) > 0 then count(*) else '0' end
from my_table M
left join
(select a, b,c,d,e
from my_table
group by a, b,c,d,e
having count(*) >1 ) B
on M.b = B.b
and M.c = B.c
and M.d = B.d
and M.e = B.e
and m.a <> B.a
where M.a in (1,2)
and M.date<= '1/1/2019'
group by M.a, m.b, m.c, m.d, m.e
Expected Result
A B C D E count
1 1 1 1 1 10
2 2 2 2 2 0
Actual Result
A B C D E count
1 1 1 1 1 10
You need to use a nested request:
select coalesce(nb, 0) from (
select count(*) nb from my_table
group by my_table.a
) nested;
Are you looking for something like this?
select a, b, c, d, e,
sum(case when M.date <= '2019-01-01' then 1 else 0 end) as cnt
from my_table
where a in (1, 2)
group by a, b, c, d, e;
This keeps all rows in the original data that match the condition on a, but not necessarily the condition on the date. It then counts only the rows that match the date.

Combination of group by, order and distinct

My query
SELECT a, b, c
FROM table
WHERE
a > 0 AND a < 4 AND
b IN (
SELECT z FROM table2
WHERE x = y
)
produces the following output:
A B C
1 1 Car
1 1 Keyboard
1 2 Apple
1 3 Frog
2 1 Carrot
2 2 Parrot
3 1 Doll
what I want is the following output
A B C
1 1 Car
2 1 Carrot
3 1 Doll
So basically for every A, the lowest B and associated C (as well as other columns).
I tried various join types, group bys, but I am running out of ideas.
How can I accomplish this?
Use a Top N Apply
SELECT a, b, c
FROM table
CROSS APPLY (SELECT top 1 z
FROM table2
WHERE x = y
order by z ) t2
WHERE a > 0 AND a < 4 AND
Do a join on a subquery:
SELECT a, b, c
FROM table t1
INNER JOIN (SELECT a a2, MIN(b) b2 FROM table GROUP BY a) t2
ON t1.a = t2.a2 AND t1.b = t2.b2
WHERE
a > 0 AND a < 4 AND
b IN (
SELECT z FROM table2
WHERE x = y
)

Categorize by columns

Have no idea what I'm to name this request so therefore I have not found any answers to it.
Basically a common statement like:
SELECT A,B,C,D,E FROM TABLE
Example result:
A B C D E
1 1 2 3 4
1 2 3 4 5
1 2 7 8 9
2 1 4 5 6
How do I 'categorize' by certain columns (in example A and B) so column values are omitted?
Preferred result:
A B C D E
1 1 2 3 4
1 2 3 4 5
7 8 9
2 1 4 5 6
In your case I guess it would make sense to have the result ordered by A,B?
In that case you could use:
SELECT DECODE(RN,1,A,NULL) AS A,
DECODE(RN,1,B,NULL) AS B,
C,
D,
E
FROM
(SELECT A,
B,
row_number() over (partition BY A,B order by A,B) AS RN,
C,
D,
E
FROM
(SELECT * FROM TEST_TABLE ORDER BY A,B
)
);
You can use LAG analytic function to access previous row values. See below example:
SELECT case
when LAG(a, 1, NULL)
OVER(ORDER BY a, b, c, d, e) = a and LAG(b, 1, NULL)
OVER(ORDER BY a, b, c, d, e) = b then
null
else
a
end new_a,
case
when LAG(a, 1, NULL)
OVER(ORDER BY a, b, c, d, e) = a and LAG(b, 1, NULL)
OVER(ORDER BY a, b, c, d, e) = b then
null
else
b
end new_b,
c,
d,
e
FROM t_table t
ORDER BY a, b, c, d, e;
SQLFiddle.

SQL grouping

I have a table with the following columns:
A B C
---------
1 10 X
1 11 X
2 15 X
3 20 Y
4 15 Y
4 20 Y
I want to group the data based on the B and C columns and count the distinct values of the A column. But if there are two ore more rows where the value on the A column is the same I want to get the maximum value from the B column.
If I do a simple group by the result would be:
B C Count
--------------
10 X 1
11 X 1
15 X 1
20 Y 2
15 Y 1
What I want is this result:
B C Count
--------------
11 X 1
15 X 1
20 Y 2
Is there any query that can return this result. Server is SQL Server 2005.
I like to work in steps: first get rid of duplicate A records, then group. Not the most efficient, but it works on your example.
with t1 as (
select A, max(B) as B, C
from YourTable
group by A, C
)
select count(A) as CountA, B, C
from t1
group by B, C
I have actually tested this:
SELECT
MAX( B ) AS B,
C,
Count
FROM
(
SELECT
B, C, COUNT(DISTINCT A) AS Count
FROM
t
GROUP BY
B, C
) X
GROUP BY C, Count
and it gives me:
B C Count
---- ---- --------
15 X 1
15 y 1
20 y 2
WITH cteA AS
(
SELECT
A, C,
MAX(B) OVER(PARTITION BY A, C) [Max]
FROM T1
)
SELECT
[Max] AS B, C,
COUNT(DISTINCT A) AS [Count]
FROM cteA
GROUP BY C, [Max];
Check this out. This should work in Oracle, although I haven't tested it;
select count(a), BB, CC from
(
select a, max(B) BB, Max(C) CC
from yourtable
group by a
)
group by BB,CC