Get the latest row based on condition - sql

I have a table material -- HERE IS THE FIDDLE
+--------+-----+-------------------+----------------+-----------+
| ID | REV | name | Description | curr |
+--------+-----+-------------------+----------------+-----------+
| 211-32 | 001 | Screw 1.0 | Used in MAT 1 | READY |
| 211-32 | 002 | Screw 2 plus | can be Used-32 | WITHDRAWN |
| 212-41 | 001 | Bolt H1 | Light solid | READY |
| 212-41 | 002 | BOLT H2+Form | Heavy solid | READY |
| 101-24 | 001 | HexHead 1-A | NOR-1 | READY |
| 101-24 | 002 | HexHead Spl | NOR-22 | READY |
| 423-98 | 001 | Nut Repair spare | NORM1 | READY |
| 423-98 | 002 | Nut Repair Part-C | NORM2 | WITHDRAWN |
| 423-98 | 003 | Nut SP-C | NORM2+NORM1 | NULL |
| 654-01 | 001 | Bar | Specific only | WITHDRAWN |
| 654-01 | 002 | Bar rod-S | Designed+Spe | WITHDRAWN |
| 654-01 | 003 | Bar OPG | Hard spec | NULL |
+--------+-----+-------------------+----------------+-----------+
Here each ID can have multiple revisions. I want to take latest revisions (i.e highest of 001,002,003 etc.,). But If the latest revision has curr as either NULL(string) or WITHDRAWN then I have take the previous revision and its corresponding value. If even that's curr is NULL or WITHDRAWN I have to again go to previous revision. If all the revision has the same issue then we can ignore it. so the expected output is
+--------+-----+------------------+---------------+-------+
| ID | REV | name | Description | curr |
+--------+-----+------------------+---------------+-------+
| 211-32 | 001 | Screw 1.0 | Used in MAT 1 | READY |
| 212-41 | 002 | BOLT H2+Form | Heavy solid | READY |
| 101-24 | 002 | HexHead Spl | NOR-22 | READY |
| 423-98 | 001 | Nut Repair spare | NORM1 | READY |
+--------+-----+------------------+---------------+-------+
I have tried below code, but i'm not sure how to take previous revision.
with cte as (
select *,dense_rank() over (partition by id order by rev desc) as DR ,
lead(curr) over (partition by id order by rev desc) LEAD_CURR
from material )
select * from cte where DR = 1 and curr='READY'
union all
select * from cte where LEAD_CURR='READY' and DR=2
union all
select * from cte where LEAD_CURR='READY' and DR=3

This sounds like filtering and then calculating the row number:
select m.*
from (select m.*,
row_number() over (partition by id order by rev desc) as seqnum
from material m
where curr is not null and curr not in ( 'WITHDRAWN', 'NULL' )
) m
where seqnum = 1;
You can also do this using a correlated subquery:
select m.*
from material m
where m.rev = (select max(m2.rev)
from material m2
where m2.id = m.id and
curr is not null and curr not in ( 'WITHDRAWN', 'NULL' )
);
Here is a db<>fiddle.
Note: It is quite non-traditional to store the string 'NULL' in a column, because that can easily be confused with the SQL "constant" NULL.
Also, your question specifically mentions 'WITHDRAWN' and NULL, but it does not say what other values are allowed. Obviously, the logic in the queries above might be equivalent to curr = 'READY' and you can use that. The above logic follows your description of the problem.

Related

Loop over one table, subselect another table and update values of first table with SQL/VBA

I have a source table that has a few different prices for each product (depending on the order quantity). Those prices are listed vertically, so each product could have more than one row to display its prices.
Example:
ID | Quantity | Price
--------------------------
001 | 5 | 100
001 | 15 | 90
001 | 50 | 80
002 | 10 | 20
002 | 20 | 15
002 | 30 | 10
002 | 40 | 5
The other table I have is the result table in which there is only one row for each product, but there are five columns that each could contain the quantity and price for each row of the source table.
Example:
ID | Quantity_1 | Price_1 | Quantity_2 | Price_2 | Quantity_3 | Price_3 | Quantity_4 | Price_4 | Quantity_5 | Price_5
---------------------------------------------------------------------------------------------------------------------------
001 | | | | | | | | | |
002 | | | | | | | | | |
Result:
ID | Quantity_1 | Price_1 | Quantity_2 | Price_2 | Quantity_3 | Price_3 | Quantity_4 | Price_4 | Quantity_5 | Price_5
---------------------------------------------------------------------------------------------------------------------------
001 | 5 | 100 | 15 | 90 | 50 | 80 | | | |
002 | 10 | 20 | 20 | 15 | 30 | 10 | 40 | 5 | |
Here is my Python/SQL solution for this (I'm fully aware that this could not work in any way, but this was the only way for me to show you my interpretation of a solution to this problem):
For Each result_ID In result_table.ID:
Subselect = (SELECT * FROM source_table WHERE source_table.ID = result_ID ORDER BY source_table.Quantity) # the Subselect should only contain rows where the IDs are the same
For n in Range(0, len(Subselect)): # n (index) should start from 0 to last row - 1
price_column_name = 'Price_' & (n + 1)
quantity_column_name = 'Quantity_' & (n + 1)
(UPDATE result_table
SET result_table.price_column_name = Subselect[n].Price, # this should be the price of the n-th row in Subselect
result_table.quantity_column_name = Subselect[n].Quantity # this should be the quantity of the n-th row in Subselect
WHERE result_table.ID = Subselect[n].ID)
I honestly have no idea how to do this with only SQL or VBA (those are the only languages I'd be able to use -> MS-Access).
This is a pain in MS Access. If you can enumerate the values, you can pivot them.
If we assume that price is unique (or quantity or both), then you can generate such a column:
select id,
max(iif(seqnum = 1, quantity, null)) as quantity_1,
max(iif(seqnum = 1, price, null)) as price_1,
. . .
from (select st.*,
(select count(*)
from source_table st2
where st2.id = st.id and st2.price >= st.price
) as seqnum
from source_table st
) st
group by id;
I should note that another solution would use data frames in Python. If you want to take that route, ask another question and tag it with the appropriate Python tags. This question is clearly a SQL question.

Calculate Final outcome based on Results/ID

For a Table T1
+----------+-----------+-----------------+
| PersonID | Date | Employment |
+----------+-----------+-----------------+
| 1 | 2/28/2017 | Stayed the same |
| 1 | 4/21/2017 | Stayed the same |
| 1 | 5/18/2017 | Stayed the same |
| 2 | 3/7/2017 | Improved |
| 2 | 4/1/2017 | Stayed the same |
| 2 | 6/1/2017 | Stayed the same |
| 3 | 3/28/2016 | Improved |
| 3 | 5/4/2016 | Improved |
| 3 | 4/19/2017 | Worsened |
| 4 | 5/19/2016 | Worsened |
| 4 | 2/16/2017 | Improved |
+----------+-----------+-----------------+
I'm trying to calculate a Final Result field partitioning on Employment/PersonID fields, based on the latest result/person relative to prior results. What I mean by that is explained in the logic behind Final Result:
For every Person,
If all results/person are Stayed the same, then only should final
result for that person be "Stayed the same"
If Worsened/Improved
are in the result set for a person, the final result should be the
latest Worsened/Improved result for that person, irrespective of "Stayed the same" after a W/I result.
Eg:
Person 1 Final result -> Stayed the same, as per (1)
Person 2 Final result -> Improved, as per (2)
Person 3 Final result -> Worsened, as per (2)
Person 4 Final result -> Improved, as per (2)
Desired Result:
+----------+-----------------+
| PersonID | Final Result |
+----------+-----------------+
| 1 | Stayed the same |
| 2 | Improved |
| 3 | Worsened |
| 4 | Improved |
+----------+-----------------+
I know this might involve Window functions or Sub-queries but I'm struggling to code this.
Hmmm. This is a prioritization query. That sounds like row_number() is called for:
select t1.personid, t1.employment
from (select t1.*,
row_number() over (partition by personid
order by (case when employment <> 'Stayed the same' then 1 else 2 end),
date desc
) as seqnum
from t1
) t1
where seqnum = 1;

SQL Query to Work out Every Product Combination

I require a SQL query to work out every product combination.
I have three product categories (game, accessory, upgrade) and products assigned to each of these three categories:
+----+------------+-----------+------------+
| id | category | product | prod_code |
+----+------------+-----------+------------+
| 1 | game | GTA | 100 |
| 2 | game | GTA1 | 200 |
| 3 | game | GTA2 | 300 |
| 4 | accessory | Play Pad | 400 |
| 5 | accessory | Xbox Pad | 500 |
| 6 | upgrade | Memory | 600 |
| 6 | upgrade | drive | 700 |
+----+------------+-----------+------------+
I want to take one product from each of the categories and work out every single combination:
+----+--------------+
| id | combinations |
+----+--------------+
| 1 | 100,400,600 |
| 2 | 100,500,600 |
| 3 | 100,400,700 |
| 4 | 100,500,700 |
| ? | etc |
+----+--------------+
How would I go about doing this?
Thanks in advance, Stuart
Use a CROSS JOIN:
SELECT CONCAT(t1.[prod_code], ',',
t2.[prod_code], ',',
t3.[prod_code])
FROM (
SELECT [prod_code]
FROM mytable
WHERE category = 'game') AS t1
CROSS JOIN (
SELECT [prod_code]
FROM mytable
WHERE category = 'accessory') AS t2
CROSS JOIN (
SELECT [prod_code]
FROM mytable
WHERE category = 'upgrade') AS t3
ORDER BY t1.[prod_code], t2.[prod_code], t3.[prod_code]
CROSS JOIN of derived tables, one for each category, produces the following cartesian product: 'game' products x 'accessory' products x 'upgrade' products
Demo here

How to select number of days from history table?

EDITED: history table may contain rows that have no effect in the time calculation (such as comments, or sending reminders...)
I have 2 tables with the following structure
PS: for the sake of simplicity, I avoided going into too much details (in reality, I have another table for STATUS codes, and another for users, etc.)
Tickets Table
--------------------------------------------------
| ID | Ticket_Details | Issued_By | Status |
--------------------------------------------------
| 001 | 'PC not working' | 'John' | On Hold |
| 002 | 'Printer broken' | 'Mike' | Rejected |
| 003 | 'Network down' | 'Alex' | Submitted |
| .. | ... | .. | ... |
--------------------------------------------------
History Table
------------------------------------------------------------------------
| ID | Ticket_ID | Ticket_Status | History_Details | Insert_Date |
------------------------------------------------------------------------
| 01 | 001 | new | submitted | 23-Feb-2015 |
| 02 | 001 | submitted | assigned to [Frank] | 25-Feb-2015 |
| 03 | 001 | submitted | commented 'needs ti.'| 25-Feb-2015 |
| 04 | 001 | assigned | put on hold by[Frank]| 26-Feb-2015 |
| 05 | 001 | on hold | reminder sent | 01-Mar-2015 |
| 06 | 002 | new | submitted | 23-Feb-2015 |
| 07 | 002 | submitted | rejected by [Sam] | 24-Feb-2015 |
| 08 | 003 | new | submitted | 25-Feb-2015 |
------------------------------------------------------------------------
Each row of the history table contains the following information:
1- the id (auto inc) primary key
2- a foreign key to ticket id
3- the status of the ticket before it was modified
4- remarks of what action was done to the ticket
5- the date [and time] of the action
Notice, that some rows describe a change in the status, while some rows, there was no change. Only a comment or a reminder was sent, while the ticket stayed at its status.
now what I want to achieve is a table that shows the current status of the ticket with the number of days it had been assigned to this status. So for the sample data above, the output should be: (considering today's date is 1-Mar-2015)
desired output
--------------------------------------------------
| ID | Ticket_Details | Status | Since |
--------------------------------------------------
| 001 | 'PC not working' | On Hold | 3 days |
| 002 | 'Printer broken' | Rejeced | 5 days |
| 003 | 'Network down' | submitted | 4 days |
| .. | ... | .. | |
--------------------------------------------------
Basically, I am using the information stored in insert_date in history table to determine how long the ticket has been in that status.
Here is what I tried:
select
t.id,
t.ticket_details,
t.status,
datediff(day, h.insert_date, getdate() ) "since"
from
tickets t
left join history h on t.id = h.ticket_id
and h.id = ( select max(id) from history h2
where h2.ticket_id = h.ticket_id
AND h2.ticket_status = t.status )
I am telling SQL to look for the last time in history where this ticket has had this status..
but I got in-accurate data since some of the "since" values came out nulls.
What mistake am I doing?
I think this is a good use of cross apply:
select t.*, datediff(day, minid, getdate()) as days_since
from tickets t outer apply
(select min(insert_date) as minid
from history h
where h.ticket_id = t.ticket_id and h.ticket_status = t.status
) h;
Note: this returns the earliest date that a ticket was at the current status. This assumes that tickets never return to a previous status, which is consistent with the information in your question.
Maybe try
select
t.id,
t.ticket_details,
t.status,
datediff(day, h.insert_date, getdate() ) "since"
from
tickets t
left join (SELECT MAX(insert_date) as insert_date,ticket_id
FROM history group by ticket_id) as h
on t.id=h.ticket_id

SQL Server: how do I get data from a history table?

Can you please help me build an SQL query to retrieve data from a history table?
I'm a newbie with only a one-week coding experience. I've been trying simple SELECT statements so far but have hit a stumbling block.
My football club's database has three tables. The first one links balls to players:
BallDetail
| BallID | PlayerID | TeamID |
|-------------------|--------|
| 1 | 11 | 21 |
| 2 | 12 | 22 |
The second one lists things that happen to the balls:
BallEventHistory
| BallID | Event | EventDate |
|--------|------ |------------|
| 1 | Pass | 2012-01-01 |
| 1 | Shoot | 2012-02-01 |
| 1 | Miss | 2012-03-01 |
| 2 | Pass | 2012-01-01 |
| 2 | Shoot | 2012-02-01 |
And the third one is a history change table. After a ball changes hands, history is recorded:
HistoryChanges
| BallID | ColumnName | ValueOld | ValueNew |
|--------|------------|----------|----------|
| 2 | PlayerID | 11 | 12 |
| 2 | TeamID | 21 | 22 |
I'm trying to obtain a table that would list all passes and shoots Player 11 had done to all balls before the balls went to other players. Like this:
| PlayerID | BallID | Event | Month |
|----------|--------|-------|-------|
| 11 | 1 | Pass | Jan |
| 11 | 1 | Shoot | Feb |
| 11 | 2 | Pass | Jan |
I begin so:
SELECT PlayerID, BallID, Event, DateName(month, EventDate)
FROM BallDetail bd INNER JOIN BallEventHistory beh ON bd.BallID = beh.BallID
WHERE PlayerID = 11 AND Event IN (Pass, Shoot) ...
But how to make sure that Ball 2 also gets included despite being with another player now?
Select PlayerID,BallID,Event,datename(month,EventDate) as Month,Count(*) as cnt from
(
Select
Coalesce(
(Select ValueNew from #HistoryChanges where ChangeDate=(Select max(ChangeDate) from #HistoryChanges h2 where h2.BallID=h.BallID and ColumnName='PlayerID' and ChangeDate<=EventDate) and BallID=h.BallID and ColumnName='PlayerID')
,(Select PlayerID from #BallDetail where BallID=h.BallID)
) as PlayerID,
h.BallID,h.Event,EventDate
from #BallEventHistory h
) a
Group by PlayerID, BallID, Event,datename(month,EventDate)
SELECT d.PlayerID, d.BallID, h.Event, DATENAME(mm, h.EventDate) AS Month
FROM BallDetail d JOIN BallEventHistory h ON d.BallID = h.BallID
WHERE h.Event IN ('Pass', 'Shoot') AND d.PlayerID = 11
OR EXISTS (SELECT 1
FROM dbo.HistoryChanges c
WHERE c.ValueOld = 11 AND c.ValueNew = d.PlayerID AND c.ColumnName = 'PlayerID' and c.ChangeDate = h.EventDate)