SQL Server Switch Based on grouping of previous values - sql

I have a table of order information in the following format:
╔══════════════╦══════╦════════════════╦═══════════╦═════════╦══════════╗
║ Order Number ║ Line ║ Item ║ Warehouse ║ Carrier ║ Quantity ║
╠══════════════╬══════╬════════════════╬═══════════╬═════════╬══════════╣
║ 255 ║ 1 ║ STUFFED-ANIMAL ║ WH1 ║ UPS ║ 3 ║
║ 256 ║ 1 ║ BLOCKS ║ WH2 ║ FEDEX ║ 1 ║
║ 257 ║ 1 ║ DOLL ║ WH1 ║ UPS ║ 1 ║
║ 257 ║ 2 ║ DRESS ║ WH1 ║ UPS ║ 3 ║
║ 257 ║ 3 ║ SHOES ║ WH2 ║ UPS ║ 1 ║
║ 258 ║ 1 ║ CHAIR ║ WH3 ║ FEDEX ║ 1 ║
║ 258 ║ 2 ║ CHAIR ║ WH3 ║ UPS ║ 2 ║
╚══════════════╩══════╩════════════════╩═══════════╩═════════╩══════════╝
I am trying to query it in such a way that I partition it into groups based on a unique combination of columns.
In my example, I would like the following result:
╔════════════════╦══════╦════════════════╦═══════════╦═════════╦══════════╗
║ Package-Number ║ Line ║ Item ║ Warehouse ║ Carrier ║ Quantity ║
╠════════════════╬══════╬════════════════╬═══════════╬═════════╬══════════╣
║ 255 ║ 1 ║ STUFFED-ANIMAL ║ WH1 ║ UPS ║ 3 ║
║ 256 ║ 1 ║ BLOCKS ║ WH2 ║ FEDEX ║ 1 ║
║ 257-1 ║ 1 ║ DOLL ║ WH1 ║ UPS ║ 1 ║
║ 257-1 ║ 2 ║ DRESS ║ WH1 ║ UPS ║ 3 ║
║ 257-2 ║ 3 ║ SHOES ║ WH2 ║ UPS ║ 1 ║
║ 258-1 ║ 1 ║ CHAIR ║ WH3 ║ FEDEX ║ 1 ║
║ 258-2 ║ 2 ║ CHAIR ║ WH3 ║ UPS ║ 2 ║
╚════════════════╩══════╩════════════════╩═══════════╩═════════╩══════════╝
To break it down I would like to do the following:
If the order number, warehouse, and carrier are the same that is one 'partition.' If there is only one partition then we just leave the order number as the package number, otherwise we break it down into packages. These packages are numbered by the same set of values being the same, but now have a number denoting which package it is.
I was looking into using row_number() over (partition by... that I found after searching for similar issues but I don't think it is exactly what I'm looking for.
Could someone point me in the right direction?

This is tricky. Having count(distinct) as a window function would help. But there is a convenient trick using the sums of dense_rank()s.
So, I think this does what you want:
select (case when seqnum_asc + seqnum_desc - 1 > 1 -- more than 1 distinct value
then concat(ordernumber, '-', seqnum_asc)
else concat(ordernumber, '') -- just to convert the value to a string
end) as packagenumber,
t.*
FROM (select t.*,
dense_rank() over (partition by ordernumber order by warehouse, carrier) as seqnum_asc,
dense_rank() over (partition by ordernumber order by warehouse desc, carrier desc) as seqnum_desc
from mytable t
) t;
Here is a db<>fiddle.
Note: This does not take into account the ordering by the line number -- because your question doesn't mention that at all. If you only want adjacent rows with the same value to be included in each group, then ask a new question with appropriate sample data and desired results.

Here is an option using Dense_Rank() instead of Row_Number()
Example
Select [Package-Number] = concat([Order Number]
,left(nullif(count(*) over (partition by [Order Number] ),1),0)
+dense_rank() over (partition by [Order Number],warehouse,carrier order by Line)*-1 )
,Line
,Item
,Warehouse
,Carrier
,Quantity
From YourTable
Returns

Related

Is there any query to only return 1 row for each product

I want to make a query on my data and only want to return 1 row for each product name where date is bigger.
Data:
╔═══════════╦═════════════╦═══════╦═══════╗
║ ProductID ║ ProductName ║ Total ║ date ║
╠═══════════╬═════════════╬═══════╬═══════╣
║ 1001 ║ abc ║ 12 ║ 2 ║
║ 1002 ║ abc ║ 23 ║ 4 ║
║ 2002 ║ xyz ║ 8 ║ 5 ║
║ 3004 ║ ytp ║ 15 ║ 1 ║
║ 4001 ║ aze ║ 19 ║ 1 ║
╚═══════════╩═════════════╩═══════╩═══════╝
I mean I want to see this result:
╔═══════════╦═════════════╦═══════╦═══════╗
║ ProductID ║ ProductName ║ Total ║ date ║
╠═══════════╬═════════════╬═══════╬═══════╣
║ 1002 ║ abc ║ 23 ║ 4 ║
║ 2002 ║ xyz ║ 8 ║ 5 ║
║ 3004 ║ ytp ║ 15 ║ 1 ║
║ 4001 ║ aze ║ 19 ║ 1 ║
╚═══════════╩═════════════╩═══════╩═══════╝
Try this :
SELECT DISTINCT ON (ProductName)
ProductID, ProductName, Total, date
FROM your_table
ORDER BY ProductName, date DESC
SELECT *FROM
(
SELECT X.ProductID,X.ProductName,X.Total,X.DATE,
ROW_NUMBER()OVER(PARTITION BY X.PRODUCTNAME ORDER BY X.DATE DESC)XCOL
FROM YOUR_TABLE AS X
)A WHERE A.XCOL=1
Try this :
SELECT a.ProductID, a.ProductName, a.Total, a.date
FROM Product a
INNER JOIN (
SELECT ProductName, MAX(date) date
FROM Product
GROUP BY ProductName
) b ON a.ProductName = b.ProductName AND a.date = b.date
order by a.ProductID
http://sqlfiddle.com/#!17/7ffd5/1

How to filter SQL dataset based off value in one column?

I have a table with this structure:
╔══════╦════╦═════════╗
║ Comp ║ ID ║ Desc ║
╠══════╬════╬═════════╣
║ 1 ║ 1 ║ Comp1-1 ║
║ 1 ║ 2 ║ Comp1-2 ║
║ 3 ║ 2 ║ Comp3-2 ║
║ 1 ║ 3 ║ Comp1-3 ║
║ 1 ║ 4 ║ Comp1-4 ║
║ 3 ║ 5 ║ Comp3-5 ║
╚══════╩════╩═════════╝
The dataset I'm creating should have a unique ID.
If an ID exists in Comp1, use that Desc.
If it does not exist in Comp1, use Comp3.
End result should look like this instead:
╔══════╦════╦═════════╗
║ Comp ║ ID ║ Desc ║
╠══════╬════╬═════════╣
║ 1 ║ 1 ║ Comp1-1 ║
║ 1 ║ 2 ║ Comp1-2 ║
║ 1 ║ 3 ║ Comp1-3 ║
║ 1 ║ 4 ║ Comp1-4 ║
║ 3 ║ 5 ║ Comp3-5 ║
╚══════╩════╩═════════╝
I've tried using NOT EXISTS and joining with a subquery but I'm not sure what to Join on.
Using not exists, it looks like:
select t.*
from t
where t.descr like 'Comp1-%' or
not exists (select 1
from t t2
where t2.id = t.id and t2.descr like 'Comp1-%'
);

Sorting and Number results based in order, using multiple columns "order by" criteria

all
I've been trying to do, with data following the structure:
╔══════════════╦═══════════╦══╗
║ Alphabetical ║ Numerical ║ ║
╠══════════════╬═══════════╬══╣
║ A ║ 15 ║ ║
║ A ║ 30 ║ ║
║ E ║ 100 ║ ║
║ C ║ 45 ║ ║
║ F ║ 25 ║ ║
║ C ║ 65 ║ ║
║ B ║ 25 ║ ║
║ F ║ 35 ║ ║
║ C ║ 100 ║ ║
║ A ║ 10 ║ ║
║ C ║ 20 ║ ║
║ B ║ 5 ║ ║
║ E ║ 10 ║ ║
║ F ║ 85 ║ ║
║ D ║ 30 ║ ║
║ F ║ 1 ║ ║
╚══════════════╩═══════════╩══╝
To get the following:
╔══════════════╦══════╦═════════╗
║ Alphabetical ║ Rank ║ Numeric ║
╠══════════════╬══════╬═════════╣
║ A ║ 1 ║ 30 ║
║ A ║ 2 ║ 15 ║
║ A ║ 3 ║ 10 ║
║ B ║ 1 ║ 25 ║
║ B ║ 2 ║ 5 ║
║ C ║ 1 ║ 100 ║
║ C ║ 2 ║ 65 ║
║ C ║ 3 ║ 45 ║
║ C ║ 4 ║ 20 ║
║ D ║ 1 ║ 30 ║
║ E ║ 1 ║ 100 ║
║ E ║ 2 ║ 10 ║
║ F ║ 1 ║ 85 ║
║ F ║ 2 ║ 35 ║
║ F ║ 3 ║ 25 ║
║ F ║ 4 ║ 1 ║
╚══════════════╩══════╩═════════╝
Basically, to order the alphabetical field in ascending order, the numerical field in descending order and get the order or rank by using the order used for the numerical field, grouped by the alphabetical field.
I have only achieved it if I limit it to one specific value in the Alphabetical column, by using something like:
select ordered_src.*, ROWNUM Rank from (select src.* from Source src where alphabetical = 'A' order by Numeric desc) ordered_src;
But I have no idea how to get the result shown above. Any idea? Also, is there any alternative that will work also in mysql/mssql/etc?
Thanks!
Use row_number():
select s.*,
row_number() over (partition by alphabetical order by numerical desc) as rank
from source s
order by alphabetical, rank;

H2, how to update with nested selects?

I have two tables, EVENT and EVENT_REV
EVENT:
╔══════════╦════════════════════╗
║ EVENT_ID ║ SENT_INTO_WF_BY_ID ║
╠══════════╬════════════════════╣
║ 1 ║ null ║
║ 2 ║ null ║
║ 3 ║ null ║
║ 4 ║ null ║
║ 5 ║ null ║
╚══════════╩════════════════════╝
and EVENT_REV:
╔══════════════╦══════════╦═════════╦════════╦════════════╦══════════╗
║ EVENT_REV_ID ║ EVENT_ID ║ USER_ID ║ STATUS ║ VALID_FROM ║ VALID_TO ║
╠══════════════╬══════════╬═════════╬════════╬════════════╬══════════╣
║ 1 ║ 1 ║ 54 ║ 0 ║ 1000 ║ 1001 ║
║ 2 ║ 1 ║ 55 ║ 100 ║ 2000 ║ 2001 ║
║ 3 ║ 1 ║ 56 ║ 200 ║ 3000 ║ 3001 ║
║ 4 ║ 2 ║ 57 ║ 0 ║ 4000 ║ 4001 ║
║ 5 ║ 3 ║ 58 ║ 0 ║ 5000 ║ 5001 ║
║ 6 ║ 3 ║ 59 ║ 100 ║ 6000 ║ null ║
║ 7 ║ 4 ║ 60 ║ 0 ║ 7000 ║ null ║
║ 8 ║ 5 ║ 61 ║ 500 ║ 8000 ║ 8001 ║
║ 9 ║ 5 ║ 62 ║ 600 ║ 9000 ║ 9001 ║
╚══════════════╩══════════╩═════════╩════════╩════════════╩══════════╝
I want to update the EVENT table and set the SENT_INTO_WF_BY_ID
The rule for this is:
event_ids should match (EVENT.EVENT_ID = EVENT_REV.EVENT_ID)
take the row where STATUS is not equal to the STATUS with the lowest VALID_FROM. Which should be the row with the second lowest VALID_FROM
From that row, take the USER_ID
For example:
For the EVENT_ID = 1 it should select the 2nd row from EVENT_REV and put the USER_ID 55 into the SENT_INTO_WF_BY_ID
Because inner joins are not allowed for H2, my query looks like this:
UPDATE event ltm
SET ltm.sent_into_wf_by_id =
(SELECT top 1 ltmRev.user_id
FROM event_rev ltmRev
WHERE ltmRev.event_id = ltm.event_id
AND ltmRev.status !=
(SELECT top 1 EVENT_REV.status
FROM EVENT_REV
ORDER BY valid_from ASC nulls LAST)
ORDER BY ltmRev.valid_to ASC nulls LAST)
The result should look like:
╔══════════╦════════════════════╗
║ EVENT_ID ║ SENT_INTO_WF_BY_ID ║
╠══════════╬════════════════════╣
║ 1 ║ 55 ║
║ 2 ║ null ║
║ 3 ║ 59 ║
║ 4 ║ null ║
║ 5 ║ 62 ║
╚══════════╩════════════════════╝
but it's actually:
╔══════════╦════════════════════╗
║ EVENT_ID ║ SENT_INTO_WF_BY_ID ║
╠══════════╬════════════════════╣
║ 1 ║ 55 ║
║ 2 ║ null ║
║ 3 ║ 59 ║
║ 4 ║ null ║
║ 5 ║ 61 <-- wrong ║
╚══════════╩════════════════════╝
Could solve it with the following query:
UPDATE ltm_op_risk_event ltm
SET ltm.sent_into_wf_by_id =
(SELECT ltmRev.adm_user_id
FROM ltm_op_risk_event_rev ltmRev
WHERE ltmRev.ltm_op_risk_event_id = ltm.ltm_op_risk_event_id
AND ltmRev.status !=
(SELECT ltmRev2.status
FROM LTM_OP_RISK_EVENT_REV ltmRev2
WHERE valid_from IS NOT NULL
AND ltmRev.ltm_op_risk_event_id = ltmRev2.ltm_op_risk_event_id
ORDER BY valid_from ASC LIMIT 1)
ORDER BY ltmRev.valid_to ASC LIMIT 1)
WHERE ltm.sent_into_wf_by_id IS NULL;
The missing part was the AND ltmRev.ltm_op_risk_event_id = ltmRev2.ltm_op_risk_event_id in the innermost select. I first tested this connection with the wrong connections...

SQL: Calculating Number of Days Between Dates of One Column In Different Rows

With my data I have individuals taking an assessment multiple times at different dates. It looks something like this:
╔════════╦═══════════╦═══════════╦═══════╗
║ Person ║ ID Number ║ Date ║ Score ║
║ John ║ 134 ║ 7/11/2013 ║ 18 ║
║ John ║ 134 ║ 8/23/2013 ║ 16 ║
║ John ║ 134 ║ 9/30/2013 ║ 16 ║
║ Kate ║ 887 ║ 2/28/2013 ║ 21 ║
║ Kate ║ 887 ║ 3/16/2013 ║ 19 ║
║ Bill ║ 990 ║ 4/18/2013 ║ 15 ║
║ Ken ║ 265 ║ 2/12/2013 ║ 23 ║
║ Ken ║ 265 ║ 4/25/2013 ║ 20 ║
║ Ken ║ 265 ║ 6/20/2013 ║ 19 ║
║ Ken ║ 265 ║ 7/15/2013 ║ 19 ║
╚════════╩═══════════╩═══════════╩═══════╝
I'd like it to have another column at the end that calculates the number of days since the first assessment for that person. I'd also settle for the number of days since the previous assessment for that person if that's easier.
Ideally it would look like this:
╔════════╦═══════════╦═══════════╦═══════╦══════════════════╗
║ Person ║ ID Number ║ Date ║ Score ║ Days Since First ║
║ John ║ 134 ║ 7/11/2013 ║ 18 ║ 0 ║
║ John ║ 134 ║ 8/23/2013 ║ 16 ║ 43 ║
║ John ║ 134 ║ 9/30/2013 ║ 16 ║ 81 ║
║ Kate ║ 887 ║ 2/28/2013 ║ 21 ║ 0 ║
║ Kate ║ 887 ║ 3/16/2013 ║ 19 ║ 16 ║
║ Bill ║ 990 ║ 4/18/2013 ║ 15 ║ 0 ║
║ Ken ║ 265 ║ 2/12/2013 ║ 23 ║ 0 ║
║ Ken ║ 265 ║ 4/25/2013 ║ 20 ║ 72 ║
║ Ken ║ 265 ║ 6/20/2013 ║ 19 ║ 128 ║
║ Ken ║ 265 ║ 7/15/2013 ║ 19 ║ 153 ║
╚════════╩═══════════╩═══════════╩═══════╩══════════════════╝
select *
, datediff(day, min(Date) over (partition by [ID Number]), Date)
from YourTable
Live example at SQL Fiddle.
I like Andomar's answer, but if you wanted to find both days between and total days since first you could do this:
SELECT a.*
,ISNULL(DATEDIFF(day,b.Date,a.Date),0)'Since Previous'
,datediff(day, min(a.Date) over (partition by a.[ID Number]), a.Date)'Since First'
FROM (select *,ROW_NUMBER() OVER(PARTITION BY [ID Number] ORDER BY DATE)RowRank
from YourTable
)a
LEFT JOIN (select *,ROW_NUMBER() OVER(PARTITION BY [ID Number] ORDER BY DATE)RowRank
from YourTable
)b
ON a.[ID Number] = b.[ID Number]
AND a.RowRank = b.RowRank + 1
Demo: SQL Fiddle
You can use option with APPLY operator
1.difference between the current row date and the previous date
SELECT t1.*,
DATEDIFF(dd, ISNULL(o.[Date], t1.[Date]), t1.[Date]) AS [Days Since First]
FROM YourTable t1 OUTER APPLY (
SELECT TOP 1 [Date]
FROM YourTable t2
WHERE t1.[ID Number] = t2.[ID Number]
AND t1.[Date] > t2.[Date]
ORDER BY t2.[Date] DESC
) o
See example on SQLFiddle
2.number of days since the first assessment
SELECT t1.*,
DATEDIFF(dd, ISNULL(o.[Date], t1.[Date]), t1.[Date]) AS [Days Since First]
FROM YourTable t1 OUTER APPLY (
SELECT MIN(t2.[Date]) AS [Date]
FROM YourTable t2
WHERE t1.[ID Number] = t2.[ID Number]
) o
See example on SQLFiddle