sql server group by different columns - sql

I have my data looking like this
Amount Officer Branch
100 S1 B1
200 S1 B2
300 S1 B3
100 S2 B1
200 S2 B2
300 S2 B3
I need another column which can show the totals by officer
Amount Officer Branch TotalByOfficer
100 S1 B1 500
200 S1 B2 500
300 S1 B3 500
100 S2 B1 900
200 S2 B2 900
600 S2 B3 900
Once i have this, I can use a having clause to filter by TotalByOfficer.
How do I accomplish such a thing.

You just need to do a SUM() OVER a PARTITION on Officer:
Select Amount,
Officer,
Branch,
Sum(Amount) Over (Partition By Officer) As TotalByOfficer
From YourTable

SELECT amount, officer, branch,
SUM(amount) OVER (PARTITION BY officer) as TotalByOfficer
FROM Table
Note, you can only use "HAVING" if you are using group by which I'm not. Use this as a sub query and add a filter, like this
SELECT *
FROM (
SELECT amount, officer, branch,
SUM(amount) OVER (PARTITION BY officer) as TotalByOfficer
FROM Table
) X
WHERE TotalByOfficer <> 500

Related

Select that returns only new status changes

I would like to select all the accounts that status changed to B1 during the month of December 2020. That is, the accounts that now have B1 status and in the previous record did not have.
Records with VALID_TO_DATE = 31/12/2100 (DD/MM/YYYY) are the most recent records.
However, there are cases, such as account 400, that the change in status occurred in the penultimate record, which is no longer valid. But as the change happened in December and the status is the same as the valid record, my select statement must include account 400.
Desired result: In this sense, according to the data below, I need a select that returns only accont 100 and 400
ROW
ACCOUNT_ID
ACCOUNT_CHANGE_DATE
VALID_TO_DATE
ACCOUNT_STATUS
1
100
21/10/2020
05/12/2020
A1
2
100
05/12/2020
15/12/2020
A1
3
100
15/12/2020
31/12/2100
B1
4
200
09/11/2020
22/12/2020
A1
5
200
22/12/2020
25/12/2020
A1
6
200
25/12/2020
31/12/2100
A1
7
300
11/11/2020
19/11/2020
B1
8
300
19/11/2020
23/12/2020
A1
9
300
23/12/2020
31/12/2100
A1
10
400
16/11/2020
14/12/2020
A1
11
400
14/12/2020
18/12/2020
B1
12
400
18/12/2020
31/12/2100
B1
My source table has records of all changes that affected each of the accounts, which may or may not change the status of the respective account.
This is my incomplete/wrong attempt:
SELECT ACCOUNT_ID
FROM TABLE t1
where ACCOUNT_STATUS ="B1"
AND ACCOUNT_CHANGE_DATE BETWEEN '01/12/2020' AND '31/12/2020'
AND VALID_TO_DATE = '31/12/2100'
AND NOT EXISTS (SELECT * FROM TABLE t2
WHERE t2.ACCOUNT_ID = t1.ACCOUNT_ID
AND t2.ACCOUNT_STATUS = 'B1'
AND t2.ACCOUNT_CHANGE_DATE < t1.ACCOUNT_CHANGE_DATE);
I would like to select all the accounts that status changed to B1 during the month of December 2020.
Use lag():
select t1.*
from (select t1.*,
lag(status) over (partition by account_id order by ACCOUNT_CHANGE_DATE) as prev_status
from t1
) t1
where prev_status <> 'B1' and status = 'B1' and
account_change_date >= '2021-12-01' and
account_change_date < '2022-01-01'

SQL Interleave multiple ordered tables

Let's say I have 2 tables with date ordered rows like:
products table:
date
name
09/01/2021
P1
12/01/2021
P2
22/01/2021
P3
and artworks table:
date
name
19/01/2018
A1
27/02/2019
A2
28/02/2021
A3
Is there any way in SQL to design a query that joins the 2 tables by "interleaving" them, but takes the first 2 products, then 1 artwork, then the next 2 products, then the next artwork...and so on
The result would be like:
date
name
09/01/2021
P1
12/01/2021
P2
19/01/2018
A1
22/01/2021
P3
27/02/2019
A2
You can use ROW_NUMBER() to produce interleaving numbering.
For example:
select
date, name
from (
select date, name,
row_number() over(order by date) * 10 as rn
from products
union all
select date, name,
row_number() over(order by date) * 20 + 1 as rn
from artworks
) x
order by rn

How to display Area wise Data for the following Problem statement?

I have following table structure
Area Section Carrying_Engine
A1 S1 Petrol
A2 S1 Petrol
A3 S1 Diesel
A4 S1 Petrol
A5 S2 Diesel
A6 S2 Petrol
Problem statement is we have to display in how much area Under Section S1 and S2 , Engine carried was Petrol and Diesel , Output has to be
Section From_Area To_Area Carrying_Engine
S1 A1 A3 Petrol
S1 A3 A4 Diesel
S1 A4 A5 Petrol
S2 A5 A6 Diesel
PS : Wherever we have a transition in Either Carrying Engine or Section , the carrying Cost is added into account of the previous Section or Carrying Engine account , for instance ,there is a transition in 3rd row , so here while we move from A2 to A3 , we have used Petrol engine and at A3 point we attach Diesel engine and so in Section S1 , we moved Petrol engine from Area A1 to A3 .
Likewise, in 4-5th row , we move from Section S1 to S2 using Petrol Engine and attached Diesel engine at A5 point but here we add the carrying cost into Section S1 Account only , so from area A4 to A5 , we have used petrol engine in Section S1 .
I am unable to get the logic to solve this ,please guide .
You can take advantage of the analytical function but I must say that you must have some column to identify the order of the column as oracle does not provide automatic ordering of the records.
I have used DATETIME column to identify the order of the column in the following solution:
SQL> WITH YOUR_TABLE (Area, Section, Carrying_Engine, DATETIME) AS
2 (SELECT 'A1', 'S1', 'Petrol', SYSDATE - 6 FROM DUAL UNION ALL
3 SELECT 'A2', 'S1', 'Petrol', SYSDATE - 5 FROM DUAL UNION ALL
4 SELECT 'A3', 'S1', 'Diesel', SYSDATE - 4 FROM DUAL UNION ALL
5 SELECT 'A4', 'S1', 'Petrol', SYSDATE - 3 FROM DUAL UNION ALL
6 SELECT 'A5', 'S2', 'Diesel', SYSDATE - 2 FROM DUAL UNION ALL
7 SELECT 'A6', 'S2', 'Petrol', SYSDATE - 1 FROM DUAL)
8 SELECT * -- your query starts from here
9 FROM
10 (
11 SELECT
12 SECTION,
13 AREA AS FROM_AREA,
14 LEAD(AREA) OVER(ORDER BY DATETIME ) AS TO_AREA,
15 CARRYING_ENGINE
16 FROM
17 (
18 SELECT
19 T.*,
20 LAG(CARRYING_ENGINE) OVER(ORDER BY DATETIME) AS LEAD_CARRYING_ENGINE
21 FROM YOUR_TABLE T
22 )
23 WHERE LEAD_CARRYING_ENGINE <> CARRYING_ENGINE
24 OR LEAD_CARRYING_ENGINE IS NULL
25 )
26 WHERE TO_AREA IS NOT NULL;
SECTION | FROM_AREA | TO_AREA | CARRYING_ENGINE
------- | --------- | ------- | ---------------
S1 | A1 | A3 | Petrol
S1 | A3 | A4 | Diesel
S1 | A4 | A5 | Petrol
S2 | A5 | A6 | Diesel
Cheers!!
Whenever section or engine changes mark row with flag 1, otherwise 0.
case when lag(carrying_engine) over (order by area) = carrying_engine
and lag(section) over (order by area) = section
then 0 else 1 end as flag
Then sum these flags in order.
sum(flag) over (order by a) grp
This will assign each row to group. Now easy, find min area, and min area from next row.
min(a) a1, lead(min(a)) over (order by grp) a2
You can remove last row which you don't want, using
where a2 is not null
Together:
with
groups as (
select a, s, e, sum(flag) over (order by a) grp
from (
select area a, Section s, Carrying_Engine e,
case when lag(carrying_engine) over (order by area) = carrying_engine
and lag(section) over (order by area) = section
then 0 else 1 end as flag
from engines))
select *
from (
select s, e, grp, min(a) a1, lead(min(a)) over (order by grp) a2
from groups group by s, e, grp )
where a2 is not null
order by grp
dbfiddle
I used area to determine order of rows, because it's all you provided, but if your table contains other id use it in order by clauses.

SQL | Join two tables and get one of the tables column against any of the matching row

Table A stores site-article wise transfer/transit stock.
Table A
site article transfer_Stock transit stock
s1 a1 10 15
s1 a2 20 25
Table B has site-article-storage location wise stock
Table B
site article sloc stock
s1 a1 1000 5
s1 a1 2000 10
s1 a1 3000 15
s1 a2 1000 20
s1 a2 5000 25
I want to get total stock as for site article
Output required -
site article sloc transfer_stock transit_stock stock
s1 a1 1000 10 15 5
s1 a1 2000 0 0 10
s1 a1 3000 0 0 15
s1 a2 1000 20 25 20
s1 a2 5000 0 0 25
I want transfer stock and transit stock against any of the row of sloc.
Table A has 1 billion rows and Table B has 1.5 billion rows.
what is the optimised way to achieve this in sql.
Currently we are achieving this throguh
SELECT A.site,
A.article,
B.sloc,
case when MIN_B.sloc=B.sloc then A.transfer_stock else 0 end as transfer_stock,
case when MIN_B.sloc=B.sloc then A.transit_stock else 0 end as transit_stock,
B.stock
FROM A
LEFT OUTER JOIN B ON A.site = B.site AND A.article = B.article
LEFT OUTER JOIN (select site,article,min(sloc) from B group by site,article) MIN_B on A.site = MIN_B.site AND A.article = MIN_B.article
SELECT A.site, A.article, B.sloc, A.transfer_stock, A.transit_stock, B.stock
FROM A
LEFT JOIN B ON A.site == B.site AND A.article == B.article;

Merging two sql queries columns into one

I have two queries that return:
Query 1
ClassName1 Students1 Teachers1
A1 30 3
A2 20 3
A3 35 4
Query 2
ClassName2 Students2 Teachers2
A1 20 3
A2 22 3
A3 20 4
Result
ClassName1 Students1 Teachers1 ClassName2 Students2 Teachers2
A1 30 3 A1 20 3
A2 20 3 A2 22 3
A3 35 4 A3 20 4
I would like to merge the two dataset into one by columns. SQL 2008
Any ideas?
You would normally do this with a join:
select q1.*, q2.*
from (query1) q1 join
(query2) q2
on q1.ClassName1 = q2.ClassName2;
Thanks i worked this out using this :
**/***********Join **************
select emp.EmpID,emp.FirstName ,emp.LastName,sod.EmpID,sod.HireDate from Employee as emp
join
(
select emp2.EmpID,emp2.HireDate from Employee as emp2
--where emp.EmpID=emp2.EmpID
) sod
on sod.EmpID=emp.EmpID
**/***********cross Apply **************
select emp.EmpID,emp.FirstName ,emp.LastName,sod.EmpID,sod.HireDate from Employee as emp
cross apply
(
select emp2.EmpID,emp2.HireDate from Employee as emp2
where emp.EmpID=emp2.EmpID
) as sod
Any other ideas will be great