SQL: first row of group by after join and order - sql

Assuming we got two below tables:
TravelTimes
OriginId DestinationId TotalJourneyTime
1 1 10
1 2 20
2 2 30
2 3 40
1 3 50
Destinations
DestinationId Name
1 Destination 1
2 Destination 2
3 Destination 3
How do I find the quickest journey between each origin and destination?
I want to join TravelTimes with Destinations by DestinationId and then group them by OriginId and sort each group by TotalJourneyTime and select the first row of each group.
I did try joining and grouping, but it seems group by is not the solution for my case as I don't have any aggregation column in the output.
Expected output
OriginId DestinationId DestinationName TotalJourneyTime
1 1 Destination 1 10
2 3 Destination 3 40

Use a RANK to rank each journey partitioned by the origin and destination and ordered by the travel time
WITH RankedTravelTimes
AS
(
select originid,
destinationId,
totaljourneytime,
rank() over (partition by originid,destinationid order by totaljourneytime ) as r
from traveltimes
)
SELECT rtt.*, d.name
FROM RankedTravelTimes rtt
INNER JOIN Destinations d
ON rtt.destinationId = d.id
WHERE rtt.r=1
The above will include both the journey from 1-2 and 2-2 as separate. If you're only interested in the destination you can remove originId out of the partition.

Not sure I see the problem here with just joining and grouping the data with a MIN on the journey time:
CREATE TABLE #Traveltimes
(
[OriginId] INT ,
[DestinationId] INT ,
[TotalJourneyTime] INT
);
INSERT INTO #Traveltimes
( [OriginId], [DestinationId], [TotalJourneyTime] )
VALUES ( 1, 1, 10 ),
( 1, 2, 20 ),
( 2, 2, 30 ),
( 2, 3, 40 ),
( 2, 3, 50 );
CREATE TABLE #Destinations
(
[DestinationId] INT ,
[Name] VARCHAR(13)
);
INSERT INTO #Destinations
( [DestinationId], [Name] )
VALUES ( 1, 'Destination 1' ),
( 2, 'Destination 2' ),
( 3, 'Destination 3' );
SELECT d.DestinationId ,
d.Name ,
tt.OriginId ,
MIN(tt.TotalJourneyTime) MinTime
FROM #Destinations d
INNER JOIN #Traveltimes tt ON tt.DestinationId = d.DestinationId
GROUP BY tt.OriginId ,
d.DestinationId ,
d.Name
DROP TABLE #Destinations
DROP TABLE #Traveltimes
Gives you:
DestinationId Name OriginId MinTime
1 Destination 1 1 10
2 Destination 2 1 20
2 Destination 2 2 30
3 Destination 3 2 40
Note: why do you travel from destination 1 to itself?

I think you want the following:
;with cte as(select *, row_number() over(partition by DestinationId order by TotalJourneyTime) rn
from TravelTimes)
select * from cte c
join Destinations d on c.DestinationId = d.DestinationId
where c.rn = 1

Related

SQL: Update Column with increment numbers based on 2 Columns

I have 2 Columns and i need to reorder one of them with an Update statement.
Here is an Example:
Date_time---------Priority
20.07.2018 10
21.07.2018 3
21.07.2018 13
21.07.2018 4
22.07.2018 23
23.07.2018 3
23.07.2018 7
And i need to get this:
Date_time---------Priority
20.07.2018 10
21.07.2018 10
21.07.2018 20
21.07.2018 30
22.07.2018 10
23.07.2018 10
23.07.2018 20
I need to change the Priority column based on current order and Date. The new order should be separated by 10... 10, 20, 30, 40, 50...
Can there someone help? Thanks.
you can try below query by using row_number funtion
update A
set Priority= 10*rn
from TableA A inner join
( select date_time, row_number() over(partition by Date_time order by Date_time ) as rn from TableA
) as B
on A.Date_time=B.Date_time
CREATE TABLE #Table1
([Date_time] varchar(10), [Priority] int)
;
INSERT INTO #Table1
([Date_time], [Priority])
VALUES
('20.07.2018', 10),
('21.07.2018', 3),
('21.07.2018', 13),
('21.07.2018', 4),
('22.07.2018', 23),
('23.07.2018', 3),
('23.07.2018', 7)
with cte as
(
SELECT *
,row_number() OVER (
PARTITION BY REPLACE([Date_time], '.', '-') ORDER BY [Priority]
) AS rn
FROM #Table1
) select [Date_time],(10*rn) [Priority] from cte
output
Date_time (No column name)
20.07.2018 10
21.07.2018 10
21.07.2018 20
21.07.2018 30
22.07.2018 10
23.07.2018 10
23.07.2018 20
replace [Table Name] with your table name
;with cte as
(
select DENSE_RANK() over(partition by date_time order by priority)*10 as newPriority,date_time,priority from [Table Name]
)
update [Table Name] set [priority]= newPriority from cte
where [Table Name].[priority]=cte.priority and [Table Name].Date_time=cte.Date_time

How to order the result based on the column values in sql server

I have a table with the following type:
Id Parent_id Code Name market
1 NULL 1ex name 1 3
2 1 2ex name 2 3
3 1 3ex name 3 3
4 Null 4ex name 4 1
5 null 5ex name 5 3
6 4 6ex name 6 3
I wanted to select code and name from the above table such that it is ordered in the following way:
based on the market where market id=3
Parent id
related child
others
ie. id 1 (Parent_id) should be displayed first followed by id 2 and 3 (Child id). The values in 'parent_id' are from the column 'id'.
I have built the following query so far and i am feeling little difficult to order the parent code and the related child codes.
select code,name from tbl_codes A
order by CASE WHEN(A.[Market] = 3) THEN 0 ELSE 1 END
Can someone please help me out.
Try this
SELECT code ,
name
FROM tbl_codes A
ORDER BY CASE WHEN ( A.[Market] = 3 ) THEN 0
ELSE 1
END ,
CASE WHEN ( ISNULL(parent_id,0) = 1 ) THEN 0
ELSE 1
END
A recursive CTE is the best way to construct a parent/child heirarchy as follows:
-- Set up test data
CREATE TABLE tbl_codes (id INT , Parent_id INT, Code VARCHAR(3), NAME VARCHAR(12), Market INT)
INSERT tbl_codes
SELECT 1, NULL, '1ex', 'name 1', 3 UNION ALL
SELECT 2, 1 , '2ex', 'name 2', 3 UNION ALL
SELECT 3, 1 , '3ex', 'name 3', 3 UNION ALL
SELECT 4, NULL , '4ex', 'name 4', 1 UNION ALL
SELECT 5, NULL , '5ex', 'name 5', 3 UNION ALL
SELECT 6, 4 , '6ex', 'name 6', 3
CREATE VIEW [dbo].[View_ParentChild]
AS
-- Use a recursive CTE to build a parent/child heirarchy
WITH
RecursiveCTE AS
(
SELECT
id,
name,
parent_id,
Code,
market,
sort = id
FROM
tbl_codes
WHERE
parent_id IS NULL
UNION ALL
SELECT
tbl_codes.id,
tbl_codes.name,
tbl_codes.parent_id,
tbl_codes.Code,
tbl_codes.market,
sort = tbl_codes.parent_id
FROM
tbl_codes
INNER JOIN RecursiveCTE
ON tbl_codes.parent_id = RecursiveCTE.id
WHERE
tbl_codes.parent_id IS NOT NULL
)
SELECT
Code,
NAME,
Market,
Sort
FROM
RecursiveCTE
GO
As per your request I have refactored the query as a VIEW.
To use the view:
SELECT
*
FROM
dbo.View_ParentChild AS vpc
ORDER BY
CASE WHEN ( Market = 3 ) THEN 0
ELSE 1
END,
sort
It gives the following result:
Code NAME Market Sort
---- ------ ------ ----
1ex name 1 3 1
2ex name 2 3 1
3ex name 3 3 1
6ex name 6 3 4
5ex name 5 3 5
4ex name 4 1 4
To learn more about recursive CTEs click here
And, as requested, is a new version of the view that does not use a recursive CTE
CREATE VIEW [dbo].[View_ParentChild_v2]
AS
SELECT
id,
Code,
market,
sort
FROM
(
SELECT
id,
name,
parent_id,
Code,
market,
sort = id
FROM
tbl_codes
WHERE
parent_id IS NULL
UNION ALL
SELECT
tbl_codes.id,
tbl_codes.name,
tbl_codes.parent_id,
tbl_codes.Code,
tbl_codes.market,
sort = tbl_codes.parent_id
FROM
tbl_codes
WHERE
tbl_codes.parent_id IS NOT NULL
) AS T
GO
Used as follows:
SELECT
*
FROM
View_ParentChild_v2
ORDER BY
CASE WHEN ( Market = 3 ) THEN 0
ELSE 1
END,
sort
nb: The first version, using a recursive CTE, could handle virtually unlimited levels of Parent/Child while version 2 only handles one level.
You can put condition in your columns. Try:
SELECT code ,
name ,
CASE WHEN ( A.[Market] = 3 ) THEN 0
ELSE 1
END AS marketOrder ,
CASE WHEN ( parent_id = 1 ) THEN 0
ELSE 1
END AS parentOrder
FROM tbl_codes A
ORDER BY parentOrder ,
marketOrder

SQL grouping interescting/overlapping rows

I have the following table in Postgres that has overlapping data in the two columns a_sno and b_sno.
create table data
( a_sno integer not null,
b_sno integer not null,
PRIMARY KEY (a_sno,b_sno)
);
insert into data (a_sno,b_sno) values
( 4, 5 )
, ( 5, 4 )
, ( 5, 6 )
, ( 6, 5 )
, ( 6, 7 )
, ( 7, 6 )
, ( 9, 10)
, ( 9, 13)
, (10, 9 )
, (13, 9 )
, (10, 13)
, (13, 10)
, (10, 14)
, (14, 10)
, (13, 14)
, (14, 13)
, (11, 15)
, (15, 11);
As you can see from the first 6 rows data values 4,5,6 and 7 in the two columns intersects/overlaps that need to partitioned to a group. Same goes for rows 7-16 and rows 17-18 which will be labeled as group 2 and 3 respectively.
The resulting output should look like this:
group | value
------+------
1 | 4
1 | 5
1 | 6
1 | 7
2 | 9
2 | 10
2 | 13
2 | 14
3 | 11
3 | 15
Assuming that all pairs exists in their mirrored combination as well (4,5) and (5,4). But the following solutions work without mirrored dupes just as well.
Simple case
All connections can be lined up in a single ascending sequence and complications like I added in the fiddle are not possible, we can use this solution without duplicates in the rCTE:
I start by getting minimum a_sno per group, with the minimum associated b_sno:
SELECT row_number() OVER (ORDER BY a_sno) AS grp
, a_sno, min(b_sno) AS b_sno
FROM data d
WHERE a_sno < b_sno
AND NOT EXISTS (
SELECT 1 FROM data
WHERE b_sno = d.a_sno
AND a_sno < b_sno
)
GROUP BY a_sno;
This only needs a single query level since a window function can be built on an aggregate:
Get the distinct sum of a joined table column
Result:
grp a_sno b_sno
1 4 5
2 9 10
3 11 15
I avoid branches and duplicated (multiplicated) rows - potentially much more expensive with long chains. I use ORDER BY b_sno LIMIT 1 in a correlated subquery to make this fly in a recursive CTE.
Create a unique index on a non-unique column
Key to performance is a matching index, which is already present provided by the PK constraint PRIMARY KEY (a_sno,b_sno): not the other way round (b_sno, a_sno):
Is a composite index also good for queries on the first field?
WITH RECURSIVE t AS (
SELECT row_number() OVER (ORDER BY d.a_sno) AS grp
, a_sno, min(b_sno) AS b_sno -- the smallest one
FROM data d
WHERE a_sno < b_sno
AND NOT EXISTS (
SELECT 1 FROM data
WHERE b_sno = d.a_sno
AND a_sno < b_sno
)
GROUP BY a_sno
)
, cte AS (
SELECT grp, b_sno AS sno FROM t
UNION ALL
SELECT c.grp
, (SELECT b_sno -- correlated subquery
FROM data
WHERE a_sno = c.sno
AND a_sno < b_sno
ORDER BY b_sno
LIMIT 1)
FROM cte c
WHERE c.sno IS NOT NULL
)
SELECT * FROM cte
WHERE sno IS NOT NULL -- eliminate row with NULL
UNION ALL -- no duplicates
SELECT grp, a_sno FROM t
ORDER BY grp, sno;
Less simple case
All nodes can be reached in ascending order with one or more branches from the root (smallest sno).
This time, get all greater sno and de-duplicate nodes that may be visited multiple times with UNION at the end:
WITH RECURSIVE t AS (
SELECT rank() OVER (ORDER BY d.a_sno) AS grp
, a_sno, b_sno -- get all rows for smallest a_sno
FROM data d
WHERE a_sno < b_sno
AND NOT EXISTS (
SELECT 1 FROM data
WHERE b_sno = d.a_sno
AND a_sno < b_sno
)
)
, cte AS (
SELECT grp, b_sno AS sno FROM t
UNION ALL
SELECT c.grp, d.b_sno
FROM cte c
JOIN data d ON d.a_sno = c.sno
AND d.a_sno < d.b_sno -- join to all connected rows
)
SELECT grp, sno FROM cte
UNION -- eliminate duplicates
SELECT grp, a_sno FROM t -- add first rows
ORDER BY grp, sno;
Unlike the first solution, we don't get a last row with NULL here (caused by the correlated subquery).
Both should perform very well - especially with long chains / many branches. Result as desired:
SQL Fiddle (with added rows to demonstrate difficulty).
Undirected graph
If there are local minima that cannot be reached from the root with ascending traversal, the above solutions won't work. Consider Farhęg's solution in this case.
I want to say another way, it may be useful, you can do it in 2 steps:
1. take the max(sno) per each group:
select q.sno,
row_number() over(order by q.sno) gn
from(
select distinct d.a_sno sno
from data d
where not exists (
select b_sno
from data
where b_sno=d.a_sno
and a_sno>d.a_sno
)
)q
result:
sno gn
7 1
14 2
15 3
2. use a recursive cte to find all related members in groups:
with recursive cte(sno,gn,path,cycle)as(
select q.sno,
row_number() over(order by q.sno) gn,
array[q.sno],false
from(
select distinct d.a_sno sno
from data d
where not exists (
select b_sno
from data
where b_sno=d.a_sno
and a_sno>d.a_sno
)
)q
union all
select d.a_sno,c.gn,
d.a_sno || c.path,
d.a_sno=any(c.path)
from data d
join cte c on d.b_sno=c.sno
where not cycle
)
select distinct gn,sno from cte
order by gn,sno
Result:
gn sno
1 4
1 5
1 6
1 7
2 9
2 10
2 13
2 14
3 11
3 15
here is the demo of what I did.
Here is a start that may give some ideas on an approach. The recursive query starts with a_sno of each record and then tries to follow the path of b_sno until it reaches the end or forms a cycle. The path is represented by an array of sno integers.
The unnest function will break the array into rows, so a sno value mapped to the path array such as:
4, {6, 5, 4}
will be transformed to a row for each value in the array:
4, 6
4, 5
4, 4
The array_agg then reverses the operation by aggregating the values back into a path, but getting rid of the duplicates and ordering.
Now each a_sno is associated with a path and the path forms the grouping. dense_rank can be used to map the grouping (cluster) to a numeric.
SELECT array_agg(DISTINCT map ORDER BY map) AS cluster
,sno
FROM ( WITH RECURSIVE x(sno, path, cycle) AS (
SELECT a_sno, ARRAY[a_sno], false FROM data
UNION ALL
SELECT b_sno, path || b_sno, b_sno = ANY(path)
FROM data, x
WHERE a_sno = x.sno
AND NOT cycle
)
SELECT sno, unnest(path) AS map FROM x ORDER BY 1
) y
GROUP BY sno
ORDER BY 1, 2
Output:
cluster | sno
--------------+-----
{4,5,6,7} | 4
{4,5,6,7} | 5
{4,5,6,7} | 6
{4,5,6,7} | 7
{9,10,13,14} | 9
{9,10,13,14} | 10
{9,10,13,14} | 13
{9,10,13,14} | 14
{11,15} | 11
{11,15} | 15
(10 rows)
Wrap it one more time for the ranking:
SELECT dense_rank() OVER(order by cluster) AS rank
,sno
FROM (
SELECT array_agg(DISTINCT map ORDER BY map) AS cluster
,sno
FROM ( WITH RECURSIVE x(sno, path, cycle) AS (
SELECT a_sno, ARRAY[a_sno], false FROM data
UNION ALL
SELECT b_sno, path || b_sno, b_sno = ANY(path)
FROM data, x
WHERE a_sno = x.sno
AND NOT cycle
)
SELECT sno, unnest(path) AS map FROM x ORDER BY 1
) y
GROUP BY sno
ORDER BY 1, 2
) z
Output:
rank | sno
------+-----
1 | 4
1 | 5
1 | 6
1 | 7
2 | 9
2 | 10
2 | 13
2 | 14
3 | 11
3 | 15
(10 rows)

For different groups, sql query to replace null value with known value from next known value

I have this SQL table which looks like this:
customer date number
--------- ---- ------
A 1 3
A 2 NULL
A 3 5
A 4 NULL
A 5 6
B 1 NULL
B 2 NULL
B 3 10
Per customer, I'm looking to add an extra column number_NEW which replaces the NULL in number (if this is null) with the next known chronologically known number (determined by date):
customer date number number_NEW
--------- ---- ------ ----------
A 1 3 3
A 2 NULL 5
A 3 5 5
A 4 NULL 6
A 5 6 6
B 1 NULL 10
B 2 NULL 10
B 3 10 10
How would I go about this in SQL?
Thanks a lot!
You can use APPLY:
SELECT
*,
Number_NEW = ISNULL(t.Number, x.Number)
FROM Test t
OUTER APPLY(
SELECT TOP 1 Number
FROM Test
WHERE
Customer = t.Customer
AND Date > t.Date
AND Number IS NOT NULL
ORDER BY Date
)x
ORDER BY t.Customer, t.Date
Your sample data is not upto the mark .
[date] column is not clear.So to be safe I have use row_number which I think is require.
Also I think your problem is already solved.I have written this script using sql 2012 with dynamic LEAD().
It not only giving correct output but also depict dynamic use of LEAD().
Declare #t table(customer varchar(20),[date] int, number int)
insert into #t values
('A', 1,3 )
,('A', 2, NULL)
,('A', 3, 5 )
,('A', 4, NULL )
,('A', 5, 6)
,('B', 1, NULL)
,('B', 2, NULL)
,('B', 3, 10)
;WITH CTE
AS (
SELECT *
,ROW_NUMBER() OVER (
PARTITION BY customer ORDER BY [DATE]
) RN
FROM #T
)
--SELECT * FROM CTE
SELECT *
,IIF(number IS NULL, LEAD(number, (
SELECT TOP 1 RN - A.RN
FROM CTE
WHERE customer = a.customer
AND RN > a.RN
AND number IS NOT NULL
ORDER BY RN
), number) OVER (
ORDER BY customer
,[date]
), number) number_NEW
FROM CTE A
alter table T add number_NEW int null;
update T /* substitute table name here -- I realize that SQL Server allows aliases */
set number_NEW =
case
when number is null
then (
select min(t2.number) /* do date and number always increase together? */
from T as t2
/* substitute full table name here as well */
where t2.customer = T.customer and t2.date > T."date"
)
else number
end
);
alter table T alter column number_NEW int not null;

duplicate entry in union

I have three tables:
1. Flat Discount
2. Promotion
3. weeklyorder
When i join these table and take union i got 2 row with same data but one different .. how to merge it to show only one row.
Query:
SELECT skuMaster.SKU,
(skuMaster.MinimumStock - COUNT(*)) as ReorderQuantity,
'LowInventory' as descp
FROM SKUMaster skuMaster
JOIN InventoryMaster inventoryMaster ON skuMaster.SKU = inventoryMaster.SKU
GROUP BY skuMaster.sku, skuMaster.MinimumStock, skuMaster.Name
HAVING COUNT(*) < skuMaster.MinimumStock
UNION
SELECT WeeklyOrderList.SKU,
WeeklyOrderList.Quantity as ReorderQuantity,
'NoPO' as descp
FROM WeeklyOrderList
WHERE WeeklyOrderList.POCGen = 'true'
result :
SKU ReorderQuantity descp
1 1 LowInventory
2 2 LowInventory
2 2 NoPO
6 5 LowInventory
here 2nd And 3rd are alomost same only description is different.
can we combine them and show only one row with descp as lowinventory and NOPO
SKU ReorderQuantity descp
1 1 LowInventory
2 2 LowInventory NoPo
6 5 LowInventory
same as above suppose we have table below
SKU ReorderQuantity
1 1
2 5
2 10
6 5
here output should be Max reorder quantity of same sku
Result:
SKU ReorderQuantity
1 1
2 10
6 5
;WITH CTE AS
(
SELECT skuMaster.SKU,
(skuMaster.MinimumStock - COUNT(*)) as ReorderQuantity,
'LowInventory' as descp
FROM SKUMaster skuMaster
JOIN InventoryMaster inventoryMaster ON skuMaster.SKU = inventoryMaster.SKU
GROUP BY skuMaster.sku, skuMaster.MinimumStock, skuMaster.Name
HAVING COUNT(*) < skuMaster.MinimumStock
UNION
SELECT WeeklyOrderList.SKU,
WeeklyOrderList.Quantity as ReorderQuantity,
'NoPO' as descp
FROM WeeklyOrderList
WHERE WeeklyOrderList.POCGen = 'true'
)
SELECT DISTINCT
a.SKU,
a.ReorderQuantity,
descp = STUFF((SELECT ', ' + b.descp
FROM CTE b
WHERE b.ReorderQuantity = a.ReorderQuantity
FOR XML PATH('')), 1, 2, '')
FROM CTE a
just an example for above output mentioned
DECLARE #t TABLE
(
SKU INT,
ReorderQuantity INT
)
INSERT INTO #t (SKU,ReorderQuantity)
VALUES (1,1), (2,5), (2,10), (6,5)
SELECT t.SKU,tt.Qty
FROM #t t
INNER JOIN (SELECT MAX(ReorderQuantity)as Qty, SKU
FROM #t
GROUP BY SKU) tt
ON tt.SKU = t.SKU
GROUP BY t.SKU,tt.Qty