SQL query count (recursive) - sql

I have the following table on my database which contains some transactions for which I need to calc points and rewards.
Every time a TxType A occurs I should record 10 points.
Then I have to subtract from these points the value of the PP column every time a TxType B occurs.
When the calculation goes to zero a reward is reached.
ID TxType PP
1 A 0
2 B 2
3 B 1
4 B 1
5 B 1
6 B 3
7 B 1
8 B 1
9 A 0
10 B 4
11 B 3
12 B 2
13 B 1
14 A 0
15 B 2
I have created the sql query to calc points as follow
SELECT SUM(
CASE
WHEN TxType = 'A' THEN 10
WHEN TxType = 'B' THEN (PP * -1)
END)
FROM myTable
This query return the value of 8, which is exactly the number of points based on the sample data.
How do I calculate the rewards occurred (2 in the given example)?
thanks for helping

One way to do the calculation (in SQL Server 2008) using a correlated subquery:
select t.*,
(select sum(case when TxType = 'A' then 10
when TxType = 'B' then PP * -1
end)
from mytable t2
where t2.id <= t.id
) as TheSum
from mytable t;
You can then apply the logic of what happens when the value is 0. In SQL Server 2012, you could just use a cumulative sum.

To complete Gordon Linoff's the answer, you just need to count the records where TheSum is 0 to get how many rewards occurred:
SELECT COUNT(1)
FROM (
SELECT ID,
TxType,
PP,
( SELECT SUM(CASE TxType WHEN 'A' THEN 10 WHEN 'B' THEN -PP END)
FROM #myTable t2
WHERE t2.id <= t1.id
) AS TheSum
FROM #myTable t1
) Result
WHERE TheSum = 0

Related

SQL: How to join two columns in a specific way?

I am working with an Oracle Database and I am new to SQL in general.
I have a table with data and month columns. After filtering the data I have just a few rows left. But I want to get two columns: 1-st column with 12 months listed (1,2,3,4,5,6,7,8,9,10,11,12) and second column with values from original data (if exist) or zeroes.
F.e.: Original data:
MONTH VALUE
9 96
What I want:
MONTH VALUE
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 96
10 0
11 0
12 0
I have already tried to use join and union all functions but it didn't work out.
First generate a sequence of 12 months number then use left join
select monthNo, coalesce(Value,0) as value from
(
SELECT 1 MonthNo
FROM dual
CONNECT BY LEVEL <= 12
)A left join originaltable b on A.monthNo=b.month
is this what are you looking for?
WITH tab AS(SELECT LEVEL AS m , null as value FROM DUAL CONNECT BY LEVEL <= 12)
, tab2 AS(SELECT 9 as m, 96 as VALUE FROM DUAL)
select t1.m
,coalesce(t2.value,0) as value
from tab t1
left join tab2 t2 on t1.m = t2.m
order by 1
Bro enjoy...
select months.month ,original_data.VALUE
from original_data
Right JOIN (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) months(month) on
months.month = original_data.MONTH
order by months.month --optional

Datediff with CASE and Group By

I am trying to collapse a table into a single row per id, having trouble including a DATEDIFF function with the GROUP BY and CASE statements:
SELECT
o.id1
,o.id2
,count(case when o.type = 'TEST' and DATEDIFF(o.dte, m.dte) < 30 then id3 end) as win_30
FROM table1 m
LEFT JOIN table2 0
ON (m.id = o.id2)
WHERE o.load_dt BETWEEN '20181001' AND '20181010'
GROUP BY 1,2;
I keep getting a 'Expression not in GROUP BY' error when I run this code, and the problem seems to be with the datediff (when I take out 'and DATEDIFF(o.dte, m.dte) < 30' it runs just fine). Do I need the datediff in the GROUP BY somehow?
Any help is appreciated. Thanks!
I am not getting any error for similar query.
hive> select * from test_d1;
OK
1 2 10
3 4 20
5 6 30
hive> select * from test_d2;
OK
1 5
3 10
Query - hive> select t1.id1, t1.id2, count(case when t2.id3=1 and nvl(t1.dte,t2.dte) < 10 then 1 else 0 end) as col3 from test_d1 t1 left outer join test_d2 t2 on t1.id1=t2.id3 group by 1,2;
Output -
OK
1 2 1
3 4 1
5 6 1
Tried with position in group by instead of columns (you have to set set hive.groupby.orderby.position.alias = true)
hive> select t1.id1, t1.id2, count(case when t2.id3=1 and nvl(t1.dte,t2.dte) < 10 then 1 else 0 end) as col3 from test_d1 t1 left outer join test_d2 t2 on t1.id1=t2.id3 group by 1,2;
OK
1 2 1
3 4 1
5 6 1
One more observation - why do you want to go for left outer join when the columns in select list is from right side of the table

SQL to calculate Net Capacity

How to write a SQL to get the Net change in capacity by using the capacity (when status is 1 or 2) and minus the total capacity (when status is 3) for each month? Thanks. Here is the table:
STATUS MONTH CAPACITY
1 01/16 5
3 01/16 2
1 02/16 11
3 02/16 20
1 03/16 8
3 03/16 12
1 04/16 4
2 04/16 10
3 04/16 18
2 05/16 14
3 05/16 37
2 06/16 4
3 06/16 8
For example, the net change in capacity for Jan. 16 is 5 minus 2 equals 3.
You need a conditional sum:
SUM(CASE WHEN STATUS IN (1,2) THEN CAPACITY ELSE 0 END) -
SUM(CASE WHEN STATUS IN (3) THEN CAPACITY ELSE 0 END)
dnoeth answer can be simplified to
SUM(CASE WHEN STATUS IN (1,2) THEN CAPACITY WHEN STATUS IN (3) THEN -CAPACITY ELSE 0 END)
Builds on 1,2 < 3
select MONTH, [Net change]=SUM(CASE STATUS/3 WHEN 0 THEN CAPACITY ELSE -CAPACITY END)
from t
group by MONTH;
no CASE statement:
select month, sum(capacity)-2*sum((status/3)*capacity) from table group by month;
Here is an example
You can join the table to itself and perform the calculation like so:
SELECT
a.status,
a.month,
a.capacity,
b.capacity AS total_capacity,
a.capacity - b.capacity AS net_capacity
FROM
table a
JOIN
table b
ON (a.month = b.month)
AND (b.status = 3)
WHERE
a.status IN (1,2);
-- If you don't want to have the status and instead aggregate in the event there are two within the same month:
SELECT
a.month,
SUM(a.capacity) AS capacity,
SUM(b.capacity) AS total_capacity,
SUM(a.capacity) - MAX(b.capacity) AS net_capacity
FROM
table a
JOIN
table b
ON (a.month = b.month)
AND (b.status = 3)
WHERE
a.status IN (1,2)
GROUP BY
a.month;
SELECT
"Status",
"Month",
SUM(Capacity) AS Capacity
FROM ( SELECT
"Status",
"Month",
CASE WHEN Status = 3 THEN -1 * Capacity ELSE Capacity END AS Capacity FROM tbl
) t
GROUP BY
"Status",
"Month"

SQL group numbers that are 'close' together using a threshold value

Consider the table:
id value
1 2
2 4
3 6
4 9
5 10
6 12
7 19
8 20
9 22
I want to group them by a threshold value so that I can find values that are 'close' together.
To do this I want another column that groups these numbers together. For this example use 2 as the
threshold. The result should be like this. It does not matter what is used as the group label, just
as long as it makes it easy to query later.
id value group_label
1 2 A
2 4 A
3 6 A
4 9 B
5 10 B
6 12 B
7 19 C
8 20 C
9 22 C
I couldn't get the version using lag() to work but here's a mysql query using variables
select id, value,
(case
when (value - #value) > 2
then #groupLabel := #groupLabel + 1
else #groupLabel
end) groupLabel, #value := value
from data cross join (
select #value := -1, #groupLabel := 0
) t1
order by value
SQLFiddle
Update
Here's a query using lag
select t1.id, t1.value, count(t2.id)
from data t1 left join (
select id, value,
case when
(value - lag(value) over (order by value)) > 2
then 1 else 0
end groupLabel
from data
) t2 on t2.groupLabel = 1
and t2.id <= t1.id
group by t1.id, t1.value
order by t1.value
SQLFiddle

SQL to solve this

I have the following data in a table I'll call TableA:
ID Status Date
5 0 1000
20 0 900
10 1 800
30 1 700
4 1 600
8 0 500
22 1 400
1 1 300
3 0 200
The records are sorted by Date descendingly. I want to get only those records where Status is equal to 1 BUT only up to the first record where the Status is no longer 1. So in the sample data, records with ID: 10,30,4 would be selected but but 22 and 1 would not be because ID 8 appears and separates the sets. Preferrably the SQL should run in Sqlite. The result for this sample data should return:
ID Status Date
10 1 800
30 1 700
4 1 600
EDIT
I replaced the ID values with random values and changed the date from TEXT to Integer.
I suggest
select * from tableA a1 where a1.status = 1 and not exists
(select 1 from tableA a2 where a2.status = 0 and a2.date > a1.date and a2.date <
(select max(date) from tableA a3 where a3.status = 1
)
)
Doubly nested subquery. Select rows where the status is 1 that have no rows before them with (status is 0 and that are after the earliest row where status is 1).
No idea how efficient this is.
Here you go:
SELECT *
FROM
TableA A
INNER JOIN (
SELECT *
FROM TableA S
WHERE S.Status = 1
ORDER BY S.Date DESC
LIMIT 1
) S ON A.Date <= S.Date
WHERE
A.Status = 1
AND A.Date > (
SELECT E.Date
FROM TableA E
WHERE
E.Status = 0
AND S.Date > E.Date
ORDER BY Date DESC
LIMIT 1
)
;
See a Live Demo at SQL Fiddle
This should be pretty efficient because of the LIMIT clauses. If there are many rows in the table it theoretically won't be scanning them all--but big disclaimer: I don't work with sqlite much at all.
this is not tested, but will give an idea.
It's for MSSQL and uses subqueries; I dont know if it works for sqlite.
select RowNumber() r, *
from (select * from TableA where status = 1), (select top 1 id from TableA where status = 1) diff
where id - r = diff - 1