Get counts based on a group of dates - sql

I have two tables:
Table 1 which the fields
o_id (Fk),
dt
I have Table 2
o_id (Fk),
attr1 (Number)
My objective is count how many occurrences of attr1 occur where the value is from 0-10, 11-20, 21+. I then need to take these occurrences and Group them by dt from Table 1
To make it more clear I provide a sample output:
| Date |count(0-10) | count(11-20) | count(21+) |
-----------------------------------------------------
| 01/13 | 3 | 5 | 13 |
| 02/13 | 2 | 7 | 10 |
This is the closest I can get but it doesn't quite work:
SELECT
t1.dt,
(select count(tt2.o_id) from table1 tt1, table2 tt2 WHERE attr1 BETWEEN 0 AND 10),
(select count(tt2.o_id) from table1 tt1, table2 tt2 WHERE attr1 BETWEEN 11 AND 20),
(select count(tt2.o_id) from table1 tt1, table2 tt2 WHERE attr1 > 21),
FROM
table1 t1, table2 t2
WHERE
t1.o_id = t2.o_id
GROUP BY
t1.dt;
The results I get now:
| Date |count(0-10) | count(11-20) | count(21+) |
-----------------------------------------------------
| 01/13 | 3 | 5 | 13 |
| 02/13 | 3 | 5 | 13 |
| 02/14 | 3 | 5 | 13 |
| 02/15 | 3 | 5 | 13 |
| 02/16 | 3 | 5 | 13 |
| 02/17 | 3 | 5 | 13 |
| 02/18 | 3 | 5 | 13 |
Just keeps repeating inside the counts column.

You don't need subqueries. This will do what you seem to want:
SELECT
t1.dt,
count(CASE WHEN attr1 BETWEEN 0 AND 10 THEN 1 END),
count(CASE WHEN attr1 BETWEEN 11 AND 20 THEN 1 END),
count(CASE WHEN attr1 > 20 THEN 1 END),
FROM
table1 t1
JOIN table2 t2
ON t1.o_id = t2.o_id
GROUP BY
t1.dt
;
The COUNT() aggregate function counts the number of rows in each group for which the argument expression is not NULL. The CASE expressions that are being counted are non-null only when their (one each) WHEN condition is satisfied.

The issue is that you don't join the tables in the subqueries, but in any case this query can be done as a conditional aggregation which at least I would think look a bit better:
SELECT
t1.dt,
SUM(CASE WHEN attr1 BETWEEN 0 AND 10 THEN 1 ELSE 0 END) AS "count(0-10)",
SUM(CASE WHEN attr1 BETWEEN 11 AND 20 THEN 1 ELSE 0 END) AS "count(11-20)",
SUM(CASE WHEN attr1 > 21 THEN 1 ELSE 0 END) AS "count(21+)"
FROM
table1 t1
JOIN
table2 t2
ON
t1.o_id = t2.o_id
GROUP BY
t1.dt;

This can be done using SUM(CASE):
SELECT
t1.dt,
count(case when attr1 BETWEEN 0 AND 10 then tt2.o_id end),
count(case when attr1 BETWEEN 11 AND 20 thent t2.o_id end),
count(case when attr1 > 21 thent t2.o_id end)
FROM
table1 t1, table2 t2
WHERE
t1.o_id = t2.o_id
GROUP BY
t1.dt;

SELECT
t1.dt
,(select count(tt2.o_id) from table1 tt1, table2 tt2 WHERE attr1 BETWEEN 0 AND 10 AND t1.o_id=t2,o_id)
,(select count(tt2.o_id) from table1 tt1, table2 tt2 WHERE attr1 BETWEEN 11 AND 20 AND t1.o_id=t2,o_id)
,(select count(tt2.o_id) from table1 tt1, table2 tt2 WHERE attr1 > 21 AND t1.o_id=t2,o_id)
FROM table1 t1, table2 t2
WHERE t1.o_id = t2.o_id
GROUP BY t1.dt;

Related

GROUP BY Aggregation with COUNT

I would like to know how to join tables in order to get a sort of a pivot.
table1 table2
col1 | col2 col1
------+------ -----
1 | A A
1 | B B
1 | C C
2 | A D
2 | A E
2 | A
2 | B
This code will return first two columns as I would like to (It will list every table2 entry for each table1 entry grouped) but I don't know how to continue to get the count for how many col2 occurancies are in table1. I would like to list zeros as well.
SELECT table1.col1, table2.col1
FROM table1, table2
GROUP BY table1.col1, table2.col1;
Expected result:
col1 | col2 | col3
-------+-------+----
1 | A | 1
1 | B | 1
1 | C | 1
1 | D | 0
1 | E | 0
2 | A | 3
2 | B | 1
2 | C | 0
2 | D | 0
2 | E | 0
You can use a Cartesian query:
SELECT
table1.col1,
table2.col1 as col2,
Abs(Sum([table1].[col2]=[table2].[col1])) AS col3
FROM
table1,
table2
GROUP BY
table1.col1,
table2.col1;
You can use such a query with joins, aggregations and correlated subquery as below:
SELECT t1.col1,t1.col2,
(SELECT count(*) FROM table1 WHERE col2=t1.col2 AND col1=t1.col1) as col3
FROM (SELECT t1.col1,max(col2) as col2,count(col2) as ct
FROM table2 t2
JOIN table1 t1
ON t2.col1=t1.col2
GROUP BY t1.col1) t2
RIGHT JOIN ( SELECT tt1.col1,t2.col1 as col2
FROM table2 t2
CROSS JOIN (SELECT distinct col1 FROM table1 ) tt1
) t1
ON t2.col2=t1.col2;
Demo

Oracle SQL - How Can I Join Two Tables, One to Many?

I am looking to do a full outer join in order to get the following results. Mainly I am joining table 1 to table 2.
However in table 1, anything that has a 0 in Column A but has the same available value in Key 1 column (ABC100 Key 1 value in table 1), only use that record (record 1 in table 1), and ignore 0 record (record 2 in table 1)
When joined to table 2, specifically for ABC100, I am expecting to see output lines 1 and 2 in the expected table results.
Any help or ideas going about this?
Example:
Table 1
| Key 1 | Column A |
| ABC100 | 100 |
| ABC100 | 0 |
| ABC300 | 200 |
| ABC400 | 300 |
Table 2
| Key 2 | Column C |
| ABC100 | 100 |
| ABC200 | 50 |
| ABC300 | 200 |
Expected results:
| Key 1 | Column A | Key 2 | Column B | NVL(A,0) - NVL(B,0)
| ABC100 | 100 | ABC100 | 100 | 0
| ABC100 | NULL | NULL | NULL | NULL
| NULL | NULL | ABC200 | 50 | -50
| ABC300 | 200 | ABC300 | 200 | 0
| ABC400 | 300 | NULL | NULL | 300
Your result set suggests that you want something like this:
SELECT t1.key1,
(CASE WHEN t1.a <> 0 THEN t1.a END) as a,
(CASE WHEN t1.a <> 0 THEN t2.key2 END) as key2,
(CASE WHEN t1.a <> 0 THEN t2.c END) as ,
(CASE WHEN t1.a <> 0 THEN COALESCE(t1.A, 0) - COALESCE(t2.B, 0) END) as diff
FROM t1 FULL JOIN
t2
ON t1.Key1 = t2.Key2;
Your description suggests that you want:
SELECT t1.key1, t1.a, t2.key2, t2.c,
COALESCE(t1.A, 0) - COALESCE(t2.B, 0) as diff
FROM (SELECT t1.*
FROM
WHERE t1.A <> 0 OR
NOT EXISTS (SELECT 1 FROM t1 tt1 WHERE tt1.key1 = t1.key1 AND tt1.key1 <> 0)
) t1 FULL JOIN
t2
ON t1.Key1 = t2.Key2;
You could use:
SELECT t1.*, t2.*, NVL(t1.A,0) - NVL(t2.B,0)
FROM tab1 t1
FULL JOIN tab2 t2
ON t1.Key1=t2.Key2

Is there a workaround to the Oracle Correlated Subquery Nesting Limit?

I have a situation where I'm trying to use a correlated subquery but am running into the nesting limit in Oracle. I might be missing another feature that Oracle has, so I thought I'd post this question here. Does anyone know how to rewrite the below SQL without running into this nesting limit, but also staying within the below constraints?
Constraints:
Only the SQL in the IN clause can be modified (Due to constraints beyond my control)
As shown, the filtering in the parent query needs to be applied to the aggregation subquery before the aggregation occurs.
Filter out 0 on an aggregation of colB after the parent filter is applied
The below code shows my try at this before running into the Oracle limit. Also, the Oracle version I'm on is 11.2.0.2. Any help would be appreciated. Thanks!
SELECT
*
FROM
table1 t1
WHERE
t1.colA BETWEEN XXXX AND XXXX
AND t1.pk_id IN (
SELECT
t2.pk_id
FROM (
SELECT
t3.pk_id,
SUM(t3.amt) OVER (PARTITION BY t3.colB) amt
FROM table1 t3
WHERE t3.colA = t1.colA
) t2
WHERE
t2.amt <> 0
)
Here are some sample input/outputs of what I was looking for when running the above SQL:
Sample table1:
-----------------------------
| pk_id | colA | colB | amt |
-----------------------------
| 1 | 1 | A | 2 |
| 2 | 1 | A | -1 |
| 3 | 1 | B | 1 |
| 4 | 2 | B | 1 |
| 5 | 2 | A | -2 |
| 6 | 2 | A | 1 |
| 7 | 3 | A | 1 |
Results of SUM over t3.colB with t1.colA BETWEEN 1 And 2:
---------------
| pk_id | amt |
---------------
| 1 | 0 |
| 2 | 0 |
| 3 | 2 |
| 4 | 2 |
| 5 | 0 |
| 6 | 0 |
Results of subquery for IN clause with t1.colA BETWEEN 1 And 2:
---------
| pk_id |
---------
| 3 |
| 4 |
Result of top level query with t1.colA BETWEEN 1 And 2:
-----------------------------
| pk_id | colA | colB | amt |
-----------------------------
| 3 | 1 | B | 1 |
| 4 | 2 | B | 1 |
After working through some of the answers provided, I have a way of avoiding the nesting limit in Oracle with a simple CASE statement:
SELECT
*
FROM
table1 t1
WHERE
t1.colA BETWEEN 1 AND 2
AND t1.pk_id IN (
SELECT
CASE
WHEN SUM(t2.amt) OVER (PARTITION BY t2.colB) <> 0 THEN t2.pk_id
ELSE NULL
END
FROM table1 t2
WHERE t2.colA = t1.colA
)
Unfortunately this surfaced the real problem. Because this is a subquery, I can only iterate through one value of the t1.colA range at a time. This appears to make it impossible execute the analytic sum within that range in the subquery. Because I can only modify the SQL within the IN clause, I don't see a solution to this problem. If anyone has any suggestions please let me know. Thanks.
If you know what the between values are and can use those in your subquery, then you can add that to your subquery instead:
SELECT
*
FROM
table1 t1
WHERE
t1.colA BETWEEN 1 AND 2
AND t1.pk_id IN (
SELECT
t2.pk_id
FROM
(
SELECT
t3.pk_id,
SUM(t3.amt) OVER (PARTITION BY t3.colB) amt
FROM table1 t3
WHERE t3.colA BETWEEN 1 AND 2
) t2
WHERE
t2.amt <> 0
)
SQL Fiddle Demo
You can rewrite your query like this:
SELECT *
FROM table1 t1
WHERE t1.colA BETWEEN XXXX AND XXXX and
t1.pk_id IN (
SELECT t2.pk_id
FROM (SELECT t3.pk_id, t3.ColA, SUM(t3.amt) as amt
FROM table1 t3
group by t3.pk_id, t3.ColA
having sum(t3.amt) > 0
) t2
WHERE t2.colA = t1.colA
)
From here, you can rewrite it as:
select t1.*
from table1 t1 join
(SELECT t3.pk_id, t3.ColA, SUM(t3.amt) as amt
FROM table1 t3
group by t3.pk_id, t3.ColA
having sum(t3.amt) > 0
) t2
on t1.pk_id = t2.pk_id and t1.ColA = t3.ColA
WHERE t1.colA BETWEEN XXXX AND XXXX

How to get Previous Value for Null Values

I have the Below Data in my Table.
| Id | FeeModeId |Name | Amount|
---------------------------------------------
| 1 | NULL | NULL | 20 |
| 2 | 1 | Quarter-1 | 5000 |
| 3 | NULL | NULL | 2000 |
| 4 | 2 | Quarter-2 | 8000 |
| 5 | NULL | NULL | 5000 |
| 6 | NULL | NULL | 2000 |
| 7 | 3 | Quarter-3 | 6000 |
| 8 | NULL | NULL | 4000 |
How to write such query to get below output...
| Id | FeeModeId |Name | Amount|
---------------------------------------------
| 1 | NULL | NULL | 20 |
| 2 | 1 | Quarter-1 | 5000 |
| 3 | 1 | Quarter-1 | 2000 |
| 4 | 2 | Quarter-2 | 8000 |
| 5 | 2 | Quarter-2 | 5000 |
| 6 | 2 | Quarter-2 | 2000 |
| 7 | 3 | Quarter-3 | 6000 |
| 8 | 3 | Quarter-3 | 4000 |
Since you are on SQL Server 2012... here is a version that uses that. It might be faster than other solutions but you have to test that on your data.
sum() over() will do a running sum ordered by Id adding 1 when there are a value in the column and keeping the current value for null values. The calculated running sum is then used to partition the result in first_value() over(). The first value ordered by Id for each "group" of rows generated by the running sum has the value you want.
select T.Id,
first_value(T.FeeModeId)
over(partition by T.NF
order by T.Id
rows between unbounded preceding and current row) as FeeModeId,
first_value(T.Name)
over(partition by T.NS
order by T.Id
rows between unbounded preceding and current row) as Name,
T.Amount
from (
select Id,
FeeModeId,
Name,
Amount,
sum(case when FeeModeId is null then 0 else 1 end)
over(order by Id) as NF,
sum(case when Name is null then 0 else 1 end)
over(order by Id) as NS
from YourTable
) as T
SQL Fiddle
Something that will work pre SQL Server 2012:
select T1.Id,
T3.FeeModeId,
T2.Name,
T1.Amount
from YourTable as T1
outer apply (select top(1) Name
from YourTable as T2
where T1.Id >= T2.Id and
T2.Name is not null
order by T2.Id desc) as T2
outer apply (select top(1) FeeModeId
from YourTable as T3
where T1.Id >= T3.Id and
T3.FeeModeId is not null
order by T3.Id desc) as T3
SQL Fiddle
Please try:
select
a.ID,
ISNULL(a.FeeModeId, x.FeeModeId) FeeModeId,
ISNULL(a.Name, x.Name) Name,
a.Amount
from tbl a
outer apply
(select top 1 FeeModeId, Name
from tbl b
where b.ID<a.ID and
b.Amount is not null and
b.FeeModeId is not null and
a.FeeModeId is null order by ID desc)x
OR
select
ID,
ISNULL(FeeModeId, bFeeModeId) FeeModeId,
ISNULL(Name, bName) Name,
Amount
From(
select
a.ID , a.FeeModeId, a.Name, a.Amount,
b.ID bID, b.FeeModeId bFeeModeId, b.Name bName,
MAX(b.FeeModeId) over (partition by a.ID) mx
from tbl a left join tbl b on b.ID<a.ID
and b.FeeModeId is not null
)x
where bFeeModeId=mx or mx is null
SELECT
T.ID,
ISNULL(T.FeeModeId,
(SELECT TOP 1 FeeModeId
FROM TableName AS T1
WHERE ID < T.ID AND FeeModeId IS NOT NULL
ORDER BY ID DESC)) AS FeeModeId,
ISNULL(Name,
(SELECT TOP 1 Name
FROM TableName
WHERE ID < T.ID AND Name IS NOT NULL
ORDER BY ID DESC)) AS Name,
T.Amount
FROM
TableName AS T
try this -
SELECT Id,
CASE
WHEN Feemodeid IS NOT NULL THEN
Feemodeid
ELSE
(SELECT Feemodeid
FROM Table_Name t_2
WHERE t_2.Id = (SELECT MAX(Id)
FROM Table_Name t_3
WHERE t_3.Id < t_1.Id
AND Feemodeid IS NOT NULL))
END Feemodeid,
CASE
WHEN NAME IS NOT NULL THEN
NAME
ELSE
(SELECT NAME
FROM Table_Name t_2
WHERE t_2.Id = (SELECT MAX(Id)
FROM Table_Name t_3
WHERE t_3.Id < t_1.Id
AND NAME IS NOT NULL))
END NAME,
Amount
FROM Table_Name t_1
id name
1 toto
2 NULL
3 NULL
4 titi
5 NULL
6 NULL
7 tutu
8 NULL
9 NULL
SELECT
id_table
,name
FROM
(
SELECT
T_01.id AS 'id_table'
,max(T_02.id) AS 'id_name'
FROM
names AS T_01
cross join
(
SELECT
id
,name
FROM
names
WHERE
name IS NOT NULL
) AS T_02
WHERE
T_02.id <= T_01.id
GROUP BY
T_01.id
) AS tt02
left join names
ON names.id = tt02.id_name
id_table name
1 toto
2 toto
3 toto
4 titi
5 titi
6 titi
7 tutu
8 tutu
9 tutu

get modified result from 2 tables

I have 2 tables:
table1
id | name
-----------------------------------------
1 | abc
2 | def
table2
table1id | nr_name | nr_val
-----------------------------------------
1 | 7 | 123
1 | 9 | 321
2 | 7 | 432
What SQL code do I need to get this result?
result
id | name | nr7 | nr9
-----------------------------------------
1 | abc | 123 | 321
2 | def | 432 | 0
nr_name can only be 7 or 9 and I can't change the structure of table2
(I will add more nr_name types later).
Try this:
sELECT
t1.id,
t1.name,
MAX(CASE WHEN t2.nr_name = 7 THEN t2.nr_val END) AS 'nr7',
MAX(CASE WHEN t2.nr_name = 9 THEN t2.nr_val END) AS 'nr9'
FROM table1 AS t1
INNER JOIN table2 AS t2 ON t1.id = t2.table1id
WHERE t2.name IN (7, 9)
GROUP BY t1.id, t1.name
SELECT t1.id,t1.name,
CASE WHEN t2.nr_name = 7 THEN t2.nr_val ELSE 0 END AS 'nr7',
CASE WHEN t2.nr_name = 9 THEN t2.nr_val ELSE 0 END AS 'nr9'
FROM table1 AS t1
INNER JOIN table2 AS t2 ON t1.id = t2.table1id
WHERE t2.name IN (7,9)
GROUP BY t1.id, t1.name