sum of columns and list difference between rows - sql

I am trying to get the difference between rows based on group by SELL_ID on the below table,
table1 - (table formatting courtesy of GitHub)
+---------+---------+----------+----------+------------------+---------+
| seq_ID | REQ_ID | CALL_ID | SELL_ID | REGION | COUNT |
+---------+---------+----------+----------+------------------+---------+
| 1 | 123 | C001 | S1 | AGL | 510563 |
| 2 | 123 | C001 | S1 | USL | 122967 |
| 3 | 123 | C001 | S1 | VALIC | 614106 |
| 4 | 123 | C001 | S2 | Inforce | 1247636 |
| 5 | 123 | C001 | S2 | NB | 0 |
| 6 | 123 | C001 | S3 | Seriatim Summary | 1247636 |
+---------+---------+----------+----------+------------------+---------+
I am trying to get the results as below,
table2 -
+---------+---------+----------+----------+-------+
| seq_ID | REQ_ID | CALL_ID | Summary | COUNT |
+---------+---------+----------+----------+-------+
| 1 | 123 | C001 | S1_vs_S2 | 0 |
| 2 | 123 | C001 | S2_vs_S3 | 0 |
| 3 | 123 | C001 | S3_vs_s1 | 0 |
+---------+---------+----------+----------+-------+
S1_vs_S2 is the difference between (sum(count) from table1 where sell_id='S1') and (sum(count) from table1 where sell_id='S2')
Below is the code that i am using, But couldn't fetch the results,
INSERT INTO table2 (SEQ_ID, REQ_ID,call_id,summary,count)
SELECT min(seq_id) seq_id
, req_id
, call_id
, S1_vs_S2
,((SELECT sum(c2) FROM TABLE_STG_CTRL WHERE source='S1')-
SELECT sum(c2) FROM TABLE_STG_CTRL WHERE source='S2'))
FROM table1
GROUP BY req_ID, Ctrl_ID, c1, source
ORDER BY SEQ_ID ;

Does this do what you want?
select req_id, call_id, sell_id,
lead(sell_id) over (partition by req_id, call_id order by seq_id) as next_sell_id,
(cnt -
lead(cnt) over (partition by req_id, call_id order by seq_id)
) as diff
from (select req_id, call_id, sell_id, sum(count) as cnt, min(seq_id) as seq_id
from t
group by req_id, call_id, sell_id
) t

At first group data on sell_id, req_id, call_id. This is subquery t in my code. Then self join properly this result and show difference. The only problem is to construct join condition carefully:
demo with your sample data
with t as (
select sell_id sid, req_id, call_id, sum(cnt) cnt
from table1
group by sell_id, req_id, call_id )
select case t1.sid when 'S1' then 1 when 'S2' then 2 when 'S3' then 3 end id,
t1.req_id, t1.call_id, t1.sid||'_vs_'||t2.sid call_id, t1.cnt - t2.cnt diff
from t t1
join t t2 on t1.req_id = t2.req_id
and t1.call_id = t2.call_id
and (t1.sid, t2.sid) in (('S1', 'S2'), ('S2', 'S3'), ('S3', 'S1'))
order by id
BTW count is Oracle reserved word, please avoid such names when naming columns etc.

Related

Take the row after the specific row

I have the table, where I need to take the next row after the row which has course 'TA' and flag = 1. For this I created the column rnum (OVER DATE) which may help for finding it
| student | date | course | flag | rnum |
| ------- | ----- | ----------- | ---- | ---- |
| 1 | 17:00 | Math | null | 1 |
| 1 | 17:10 | Python | null | 2 |
| 1 | 17:15 | TA | 1 | 3 |
| 1 | 17:20 | English | null | 4 |
| 1 | 17:35 | Geography | null | 5 |
| 2 | 16:10 | English | null | 1 |
| 2 | 16:20 | TA | 1 | 2 |
| 2 | 16:30 | SQL | null | 3 |
| 2 | 16:40 | Python | null | 4 |
| 3 | 19:05 | English | null | 1 |
| 3 | 19:20 | Literachure | null | 2 |
| 3 | 19:30 | TA | null | 3 |
| 3 | 19:40 | Python | null | 4 |
| 3 | 19:50 | Python | null | 5 |
As a result I should have:
| student | date | course | flag | rnum |
| ------- | ----- | ------- | ---- | ---- |
| 1 | 17:20 | English | null | 4 |
| 2 | 16:30 | SQL | null | 3 |
There are many ways to get your desired result, let's see some of them.
1) EXISTS
You can use the EXISTS clause, specifying a subquery to match for the condition.
SELECT T2.*
FROM #MyTable T2
WHERE EXISTS (
SELECT 'x' x
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
AND T1.student = T2.student AND T2.rnum = T1.rnum + 1
)
2) LAG
You ca use window function LAG to access previous row for a given order and then filter your resultset with your conditions.
SELECT w.student, w.date, w.course, w.flag, w.rnum
FROM (
SELECT T1.*
, LAG(course, 1) OVER (PARTITION BY student ORDER BY rnum) prevCourse
, LAG(flag, 1) OVER (PARTITION BY student ORDER BY rnum) prevFlag
FROM #MyTable T1
) w
WHERE prevCourse = 'TA' AND prevFlag = 1
3) JOIN
You can self-JOIN your table on the next rnum and keep only the rows who match the right condition.
SELECT T2.*
FROM MyTable T1
JOIN MyTable T2 ON T1.student = T2.student AND T2.rnum = T1.rnum + 1
WHERE T1.course = 'TA' AND T1.flag = 1
4) CROSS APPLY
You can use CROSS APPLY to specify a subquery with the matching condition. It is pretty similar to EXISTS clause, but you will also get in your resultset the columns from the subquery.
SELECT T2.*
FROM #MyTable T2
CROSS APPLY (
SELECT 'x' x
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
AND T1.student = T2.student AND T2.rnum = T1.rnum + 1
) x
5) CTE
You can use common table expression (CTE) to extract matching rows and then use it to filter your table with a JOIN.
;WITH
T1 AS (
SELECT student, rnum
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
)
SELECT T2.*
FROM #MyTable T2
JOIN T1 ON T1.student = T2.student AND T2.rnum = T1.rnum + 1
Adding the rownumber was a good start, you can use it to join the table with itself:
WITH matches AS (
SELECT
student,
rnum
FROM table
WHERE flag = 1
AND course = 'TA'
)
SELECT t.*
FROM table t
JOIN matches m
on t.student = m.student
and t.rnum = m.rnum + 1

Compare dates and data column

I have tables like this:
TABLE 1 - PERSON:
m_id | name |
-------------
22 | jo |
-------------
77 | john |
--------------
TABLE 2 - AMT_DATA
m_id | amt | activity |
-------------------------
22 | 100 | - |
-------------------------
77 | 300 | n |
-------------------------
TABLE 3 - STATUS_DATA:
m_id | status | s_date |
22 | - | 01.01.2000 |
22 | n | 01.01.2001 |
22 | - | 01.01.2002 |
77 | - | 01.01.2001 |
77 | n | 01.01.2002 |
How can i write a query or procedure that will return me all m_ids which biggest status_data.s_date for that m_id also have status_data.status = '-'?
I need to get result like this:
person.m_id | person.name | amt_data.amt | status | s_date
------------------------------------------------------------------
22 | jo | 100 | - | 01.01.2002
I don't see what amt really has to do with the question. You can just join that in.
One method is:
select p.*, status_date, status
from person p join
(select m_id, max(s_date) as status_date,
max(status) keep (dense_rank first order by s_date desc) as status
from status_data
group by m_id
) s
using (m_id)
where status = '-';
The keep syntax is Oracle's (rather verbose) way of implementing a "first" aggregation function.
You can use the analytical function as follows:
Select * from
(Select p.m_id,
P.name,
A.amt,
S.status,
S.s_date,
Row_number() over (partition by p.m_id order by s.s_date desc) as rn
From person p
join amt_data a on p.m_id = a.m_id
Join status_data s on p.m_id = s.m_id
Where s.status = '-')
Where rn = 1;

Each rows to column values

I'm trying to create a view that shows first table's columns plus second table's first 3 records sorted by date in 1 row.
I tried to select specific rows using offset from sub table and join to main table, but when joining query result is ordered by date, without
WHERE tblMain_id = ..
clause in joining SQL it returns wrong record.
Here is sqlfiddle example: sqlfiddle demo
tblMain
| id | fname | lname | salary |
+----+-------+-------+--------+
| 1 | John | Doe | 1000 |
| 2 | Bob | Ross | 5000 |
| 3 | Carl | Sagan | 2000 |
| 4 | Daryl | Dixon | 3000 |
tblSub
| id | email | emaildate | tblmain_id |
+----+-----------------+------------+------------+
| 1 | John#Doe1.com | 2019-01-01 | 1 |
| 2 | John#Doe2.com | 2019-01-02 | 1 |
| 3 | John#Doe3.com | 2019-01-03 | 1 |
| 4 | Bob#Ross1.com | 2019-02-01 | 2 |
| 5 | Bob#Ross2.com | 2018-12-01 | 2 |
| 6 | Carl#Sagan.com | 2019-10-01 | 3 |
| 7 | Daryl#Dixon.com | 2019-11-01 | 4 |
View I am trying to achieve:
| id | fname | lname | salary | email_1 | emaildate_1 | email_2 | emaildate_2 | email_3 | emaildate_3 |
+----+-------+-------+--------+---------------+-------------+---------------+-------------+---------------+-------------+
| 1 | John | Doe | 1000 | John#Doe1.com | 2019-01-01 | John#Doe2.com | 2019-01-02 | John#Doe3.com | 2019-01-03 |
View I have created
| id | fname | lname | salary | email_1 | emaildate_1 | email_2 | emaildate_2 | email_3 | emaildate_3 |
+----+-------+-------+--------+---------+-------------+---------------+-------------+---------------+-------------+
| 1 | John | Doe | 1000 | (null) | (null) | John#Doe1.com | 2019-01-01 | John#Doe2.com | 2019-01-02 |
You can use conditional aggregation:
select m.id, m.fname, m.lname, m.salary,
max(s.email) filter (where seqnum = 1) as email_1,
max(s.emailDate) filter (where seqnum = 1) as emailDate_1,
max(s.email) filter (where seqnum = 2) as email_2,
max(s.emailDate) filter (where seqnum = 3) as emailDate_2,
max(s.email) filter (where seqnum = 3) as email_3,
max(s.emailDate) filter (where seqnum = 3) as emailDate_3
from tblMain m left join
(select s.*,
row_number() over (partition by tblMain_id order by emailDate desc) as seqnum
from tblsub s
) s
on s.tblMain_id = m.id
where m.id = 1
group by m.id, m.fname, m.lname, m.salary;
Here is a SQL Fiddle.
Here is a solution that should get you what you expect.
This works by first ranking records within each table and joining them together. Then, the outer query uses aggregation to generate the expected output.
This solution will work even if the first record in the main table does not have id 1. Also filtering takes occurs within the JOINs, so this should be quite efficient.
SELECT
m.id,
m.fname,
m.lname,
m.salary,
MAX(CASE WHEN s.rn = 1 THEN s.email END) email_1,
MAX(CASE WHEN s.rn = 1 THEN s.emaildate END) email_date1,
MAX(CASE WHEN s.rn = 2 THEN s.email END) email_2,
MAX(CASE WHEN s.rn = 2 THEN s.emaildate END) email_date2,
MAX(CASE WHEN s.rn = 3 THEN s.email END) email_3,
MAX(CASE WHEN s.rn = 3 THEN s.emaildate END) email_date3
FROM
(
SELECT m.*, ROW_NUMBER() OVER(ORDER BY id) rn
FROM tblMain
) m
INNER JOIN (
SELECT
email,
emaildate,
ROW_NUMBER() OVER(PARTITION BY id ORDER BY emaildate) rn
FROM tblSub
) s
ON m.id = s.tblmain_id
AND m.rn = 1
AND s.rn <= 3
GROUP BY
m.id,
m.fname,
m.lname,
m.salary

Retrieve the minimal create date with multiple rows

I have an issue with an SQL query that I am trying to write. I am trying to retrieve the row that has the minimal create_dt for each inst (see table) and amount (which isn't unique).
Unfortunately I can't use group by as the amount column isn't unique.
+--------------+--------+------+-------------+
| Company_Name | Amount | inst | Create Date |
+--------------+--------+------+-------------+
| Company A | 1000 | 4545 | 01/10/2018 |
| Company A | 400 | 4545 | 01/11/2018 |
| Company A | 200 | 4545 | 31/10/2018 |
| Company B | 2000 | 4893 | 01/10/2016 |
| Company B | 212 | 4893 | 04/10/2016 |
| Company B | 100 | 4893 | 10/10/2017 |
| Company B | 20 | 4893 | 04/10/2018 |
+--------------+--------+------+-------------+
In the above example I expect to see:
+--------------+--------+------+-------------+
| Company_Name | Amount | inst | Create Date |
+--------------+--------+------+-------------+
| Company A | 1000 | 4545 | 01/10/2018 |
| Company B | 2000 | 4893 | 01/10/2016 |
+--------------+--------+------+-------------+
Code:
SELECT
bill_company, bill_name, account_no
FROM
dbo.customer_information;
SELECT
balance_id, balance_id2, minus_balance,new_balance,
create_date, account_no
FROM
dbo.btr
SELECT
balance_id, balance_id2, expired_Date, amount, balance_type, account_no
FROM
dbo.btr_balance
SELECT
balance_ist, expired_date, account_no, balance_type
FROM
dbo.BALANCE_inst
Retrieve the minimal create data for a balance instance with the lowest balance for a balance inst.
(SELECT
bill_company,
bill_name,
account_no,
balance_ist,
amount,
MIN(create_date)
FROM
dbo.mtr btr
LEFT JOIN
btr_balance btrb ON btr.balance_id = btrb.balance_id
AND btr.balance_id2 = btrb.balance_id2
LEFT JOIN
balance_inst bali ON btr.account_no = bali.account_no
AND btrb.expired_date = bali.expired_date
GROUP BY
bill_company, bill_name, account_no,amount, balance_ist)
I have seen some solutions about using correlated query but can't see to get my head around it.
Common Table Expression (CTE) will help you.
;with cte as (
select *, row_number() over(partition by company_name order by create_date) rn
from dbo.myTable
)
select * from cte
where rn = 1;
use row_number() i assumed bill_company is your company name
select * from
( SELECT bill_company,
bill_name,
account_no,
balance_ist,
amount,
create_date,
row_number() over(partition by bill_company order by create_date) rn
FROM dbo.mtr btr left join btr_balance btrb
on btr.balance_id = btrb.balance_id and btr.balance_id2 = btrb.balance_id2
left join balance_inst bali
on btr.account_no = bali.account_no and btrb.expired_date = bali.expired_date
) t where t.rn=1

How to select row with the latest timestamp from duplicated rows in a database table?

I have a table with duplicate & triplicate rows - how do I select the rows that are duplicated but have the latest timestamp as well as the un-duped rows?
-------------------------------------
| pk_id | user_id | some_timestamp |
|-------------------------------------|
| 1 | 123 | 10-Jun-12 14.30 |
| 2 | 123 | 19-Jun-12 21.50 |
| 3 | 567 | 10-Jun-12 09.23 |
| 4 | 567 | 12-Jun-12 09.45 |
| 5 | 567 | 13-Jun-12 08.40 |
| 6 | 890 | 13-Jun-12 08.44 |
-------------------------------------
So that I end up with:
-------------------------------------
| pk_id | user_id | some_timestamp |
|-------------------------------------|
| 2 | 123 | 19-Jun-12 21.50 |
| 5 | 567 | 13-Jun-12 08.40 |
| 6 | 890 | 13-Jun-12 08.44 |
-------------------------------------
SELECT * FROM (
SELECT pk_id,
user_id,
some_timestamp,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY some_timestamp DESC) col
FROM table) x
WHERE x.col = 1
try this
select * from table
where some_timestamp
in (select max(some_timestamp)
from table group by user_id)
Try this, I made a SQLFIDDLE which returns the correct set of data
SELECT * FROM YourTable AS T1
INNER JOIN
( SELECT user_id , MAX(some_timestamp) AS some_timestamp FROM YourTable
GROUP BY user_id
) AS T2
ON T1.User_Id = T2.User_Id AND T1.some_timestamp = T2.some_timestamp
ORDER BY 1
http://sqlfiddle.com/#!6/f7bba/6
Try this:
select * from my_table
where (user_id, some_timestamp) IN (select user_id, max(some_timestamp) from my_table group by user_id);
select YourTable.*
from
YourTable JOIN
(select User_Id, Max(Some_Timestamp) as Mx
from YourTable
group by User_Id) Mx
on YourTable.User_Id=Mx.User_Id
and YourTable.Some_Timestamp=Mx.Mx