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

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.

Related

Athena looking for records with different start dates

I have a lot of customer files with I customer data that includes a customer id which can have multiple service points. A service point can have a meter and a meter can have a meter install date:
Cust
Service Point
Meter ID
Meter Install Date
1
A1
AM1
20201005
1
A1
AM1
20201005
1
A1
AM1
20201005
1
A1
AM1
20150101
1
A1
AM1
20150101
1
A1
AM1
20150101
1
A2
AM2
20220110
1
A2
AM2
20220110
1
A2
AM2
20220110
1
A2
AM21
20230215
1
A3
AM3
20200509
1
A3
AM3
20200509
1
A3
AM3
20200509
1
A3
AM3
20221013
I'm trying to find the number of meters that have a multiple install dates. It is not uncommon to have multiple rows where these field's information is duplicated. As I try different strategies I get different answers so I'm doing something wrong.
I've tried:
select customer_id, service_point_id, secondary_sp_id
from customer
where secondary_sp_id in (
select secondary_sp_id
from customer
group by secondary_sp_id
having length(secondary_sp_id) > 1 and count(distinct meter_install_date) > 1
select customer_id, service_point_id, secondary_sp_id, meter_install_date
from customer
where secondary_sp_id in (
select secondary_sp_id
from customer
group by secondary_sp_id having count(distinct meter_install_date) > 1 )
select a.service_point_id, a.secondary_sp_id, a.meter_install_date
from customer a, customer b
where a.service_point_id = b.service_point_id
and a.secondary_sp_id = b.secondary_sp_id
and a.meter_install_date != b.meter_install_date
group by a.service_point_id, a.secondary_sp_id, a.meter_install_date
I would expect to get back:
Cust
Service Point
Meter ID
Meter Install Date
1
A1
AM1
20201005
1
A1
AM1
20150101
1
A3
AM3
20200509
1
A3
AM3
20221013
I don't think I'm handling when a service point has multiple meters and one of those meters has multiple start dates. Thanks for your help!
I'm not sure we have enough information of your data or schema, such as how "secondardy_sp_id" fits into this. No details were provided on that column nor the prod_peco_customer table.
If we assume your data appears like your first formatted section in the question, then the following CTE would work as-is.
create table customer (
cust integer,
service_point varchar(5),
meter_id varchar(5),
meter_install_date date
);
insert into customer values
(1, 'A1', 'AM1', '20201005'),
(1, 'A1', 'AM1', '20150101'),
(1, 'A2', 'AM2', '20230110');
with target_meters as (
select meter_id
from customer
group by meter_id
having count(distinct meter_install_date) > 1
)
select c.*
from customer c
join target_meters t
on c.meter_id = t.meter_id;
cust
service_point
meter_id
meter_install_date
1
A1
AM1
2020-10-05T00:00:00.000Z
1
A1
AM1
2015-01-01T00:00:00.000Z
But I kinda doubt your data looks like this even though you formatted it that way in the question. Adjust accordingly, but main point is that you could use a sub-query or CTE for identifying your meters with multiple install dates.
----------Update-----------
Based on the updated sample data, then you would simply need to change select c.* to select distinct c.* such as this...
with target_meters as (
select meter_id
from customer
group by meter_id
having count(distinct meter_install_date) > 1
)
select distinct c.*
from customer c
join target_meters t
on c.meter_id = t.meter_id
order by 1,2,3,4
cust
service_point
meter_id
meter_install_date
1
A1
AM1
2015-01-01T00:00:00.000Z
1
A1
AM1
2020-10-05T00:00:00.000Z
1
A3
AM3
2020-05-09T00:00:00.000Z
1
A3
AM3
2022-10-13T00:00:00.000Z

How to calculate rank with group by in sql?

Suppose if I have table1 as following
Category Brand Value
A A1 4
B B1 7
C C1 8
A A2 3
B B2 4
C C2 6
A A3 9
B B3 10
C C3 1
A A4 5
Now if I want to calculate rank for each brand but grouped by category how do I go about it?
Something like
Select rank() (over value)
from table
group by category
Expected output is this:
Category Brand Value Rank
A A3 9 1
A A4 5 2
A A1 4 3
A A2 3 4
B B3 10 1
B B1 7 2
B B2 4 3
C C1 8 1
C C2 6 2
C C3 1 3
Maybe you are looking for something like this.
See this official documentation on DENSE_RANK for more details
select brand, category, dense_rank() over(partition by category order by value desc) as dr
from table
You may add a PARTITION BY clause to your RANK() call, specifying the category as the partition.
SELECT RANK() OVER (PARTITION BY category ORDER BY value) rnk
FROM yourTable
ORDER BY category, rnk;

Is there a way to create a groupID for a recursive CTE in SSMS?

I'm building a query that outputs an ownership hierarchy for each root in my database. I'm using a recursive CTE with success in that I can achieve the following data output currently:
rootID RootName RelatedName
1 ABA GPS
1 ABA PIG
1 ABA BBY
1 ABA PIG
2 PIG DDS
2 PIG GPS
What I'm trying to achieve is a group ID column in which the data may look like this:
GroupID rootID RootName RelatedName
100 1 ABA GPS
100 1 ABA PIG
100 1 ABA BBY
100 1 ABA PIG
100 2 PIG DDS
100 2 PIG GPS
and likewise for group 200, 300,...etc. for each tree. What part of the recursive CTE can code be injected such to achieve the above result?
;WITH cte_Rel AS (
SELECT
<columns>
FROM #RawRel r
WHERE 1 = 1
AND <initial Conditions>
UNION ALL
SELECT
<Columns>
FROM #RawRel r
JOIN cte_Rel c ON r.RootName = c.RelatedName
)
SELECT DISTINCT * FROM cte_Rel
OPTION (MAXRECURSION 100)
You can add a row number to the anchor part of the recusive CTE. Multiply by 100 and repeat the same column in the second part of the CTE.
Fiddle in case you prefer interactive code.
Sample data
Without your actual query and sample input data it is hard to perfectly replicate your current output so I generated my own sample data.
create table RelData
(
ParentId int,
Id int,
Name nvarchar(3)
);
insert into RelData (ParentId, Id, Name) values
(null, 1, 'A00'), -- tree A
(1, 2, 'A10'),
(2, 3, 'A11'),
(2, 4, 'A12'),
(1, 5, 'A20'),
(5, 6, 'A21'),
(null, 7, 'B00'), -- tree B
(7, 8, 'B10'),
(8, 9, 'B11');
Solution
WITH cte_Rel AS (
SELECT row_number() over(order by rd.Id) * 100 as TreeId, -- number to roots and multiply the root number by 100
rd.Id, rd.Name, rd.ParentId, convert(nvarchar(3), null) as ParentName
FROM RelData rd
WHERE rd.ParentId is null
UNION ALL
SELECT c.TreeId, -- repeat the tree number
rd.Id, rd.Name, rd.ParentId, c.name
FROM RelData rd
JOIN cte_Rel c ON rd.ParentId = c.Id
)
SELECT c.TreeId, c.ParentId, c.ParentName, c.Name
FROM cte_Rel c
where c.ParentId is not null
order by c.ParentId;
Result
TreeId ParentId ParentName Name
------ -------- ---------- ----
100 1 A00 A10
100 1 A00 A20
100 2 A10 A11
100 2 A10 A12
100 5 A20 A21
200 7 B00 B10
200 8 B10 B11

sql server group by different columns

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

ORACLE Special JOIN

Let me try to explain the scenario. I have two tables A (Columns - A1, A2, A3) & B (Columns - B1, B2, B3). I need to join table A with table B on A1.B2. For every join, table B has one or two records with different values for B3(X or Y). I wanna write one query where the JOIN query needs to pick the row with B3=X(if there's no other row with B3=Y); If two rows exists (B3=X & B3=Y), then the query needs to pick only the row with B3=Y (ignoring the row with B3=X).
Let me try to give some values to the tables & explain a little bit more.
Table A
********
A1 A2 A3
1 11 111
2 22 222
3 33 333
4 44 444
Table B
********
B1 B2 B3
6 1 X
7 1 Y
8 2 X
9 3 X
10 3 Y
11 4 X
Again.. JOIN is on A1.B2. The result should be as following,
JOIN Results
*************
A1 A2 A3 B1 B2 B3
1 11 111 7 1 Y
2 22 222 8 2 X
3 33 333 10 3 Y
4 44 444 11 4 X
Let me know if you guys have any clarification about my question.
Thanks in advance.
Yogi
You can pick the rows from table B with the ROW_NUMBER function if you partition by the join column and order by your "picking order" column:
SELECT b1, b2, b3,
ROW_NUMBER() OVER (PARTITION BY b2 ORDER BY b3 DESC) as rn
FROM b;
1 Y 1
1 X 2
2 X 1
3 Y 1
3 X 2
4 X 1
Then you can filter the first row, the one with rn=1:
SELECT b1, b2, b3
FROM (SELECT b1, b2, b3,
ROW_NUMBER() OVER (PARTITION BY b2 ORDER BY b3 DESC) as rn
FROM b)
WHERE rn=1;
7 1 Y
8 2 X
10 3 Y
11 4 X
The filtered rows can then be joined to table a:
SELECT *
FROM a
JOIN (
SELECT b1, b2, b3
FROM (SELECT b1, b2, b3,
ROW_NUMBER() OVER (PARTITION BY b2 ORDER BY b3 DESC) as rn
FROM b
)
WHERE rn=1
) bfilter ON a.a1 = bfilter.b2;
1 11 111 7 1 Y
2 22 222 8 2 X
3 33 333 10 3 Y
4 44 444 11 4 X
If 'X' and 'Y' are not actual values, you can extend the ORDER clause with a CASE statement to allow for general values:
ROW_NUMBER() OVER (PARTITION BY b2 ORDER BY
CASE b3 WHEN 'Y' THEN 1
WHEN 'X' THEN 2
...
END ASC)
Edit:
SELECT a1, a2, a3, b1, b2, b3
FROM (
SELECT a1, a2, a3, b1, b2, b3,
ROWNUMBER() OVER (PARTITION BY a1 ORDER BY
CASE WHEN a2=... AND b3=... THEN 1
WHEN a2=... AND b3=... THEN 2
...
END ASC)
FROM a JOIN b ON a.a1 = b.b2
)
WHERE rn = 1;
You can use left outer joins as follows
select A.A1, A.A2, A.A3,
nvl(BT1.B1, BT2.B1),
nvl(BT1.B2, BT2.B2),
nvl(BT1.B3, BT2.B3) from A
left outer join B BT1 on A.A1 = BT1.B2 and BT1.B3 = 'Y'
left outer join B BT2 on A.A1 = BT2.B2 and BT2.B3 = 'X'
A good explanation of the various joins is at http://www.codinghorror.com/blog/2007/10/a-visual-explanation-of-sql-joins.html
Here is, how I would do it:
Make the join
group by B2
take the max(B3)
That way you ensure that X is only picked, when there is no alphabetically higher value (Y) available
With UNION
select a.*,b.* from a,b
where a.a1=b.b2
and b.b3='Y'
union
select a.*,b.* from a,b
where a.a1=b.b2
and not exists (select bb.br from b bb where bb.b2=a.a1 and bb.b3='Y')
Without UNION
select a.*,b.* from a,b
where a.a1=b.b2
and (b.b3='Y'
or not exists (select bb.b3 from b bb where bb.b2=a.a1 and bb.b3='Y'))
The constraint here is that B has exactly 1 or 2 rows for each A's row