Joining a table on itself for different criteria - sql

So I have a table, at it's base is the following structure:
WorkOrder
SubOrder
Status
StatusDate
Type
Category
(There are other things, but we're ignoring them for the sake of the question)
I'm filtering for a certain subset of type/category, and I know how to do that. But what I need to come out is the following
WorkOrder | SubOrder | Type - Category | Min(StatusDate) where Status = Open | Min(StatusDate) where Status = Complete | Calculated Date Difference between the two
I can get one at a time and that's no issue. Ror the time being, I've just been monkeying with it in Excel. I need to however get this into a SQL statement for a report I need to start generating. There may or may not be a StatusDate with the status Complete (if the SubOrder has not been marked complete elsewhere), and the dataset needs to reflect that as well. There could be multiple times that each SubOrder was opened and completed, so we just need the first (hence the Min())
Any ideas on how I should approach this task? (And no, changing the data structure is not allowed, OTS software, and we're not paying to change it)

I'm not sure that I understand why you would need to join the table to itself. It sounds like you just want
SELECT WorkOrder,
SubOrder,
Type,
Category,
min( case when Status = 'Open'
then StatusDate
else null
end) min_open_status_date,
min( case when Status = 'Complete'
then StatusDate
else null
end) min_complete_status_date,
min( case when Status = 'Complete'
then StatusDate
else null
end) -
min( case when Status = 'Open'
then StatusDate
else null
end)
FROM table_name
WHERE <<whatever conditions>>
GROUP BY WorkOrder,
SubOrder,
Type,
Category
If that is not what you are looking for, it would be very helpful to edit your question post the DDL to create the table, the DML to populate some rows, and tell us exactly what output you want for that sample data. Or create a fiddle on sqlfiddle.com. Otherwise, we have to guess at what you want. For example, you say that you want to calculate the difference between two dates but one of the two dates may not exist-- do you want to use some baseline date for the calculation if there is no closed date? This query will return NULL for the calculated difference which may or may not be what you want.

Related

In SQL, is there something like COALESCE() to qualify Column values which are NOT NULL?

I have an "Order"Table which houses "Status"Column. Values under Status = ('Completed','Sold','In-Process','Released','NotStarted').
Even though there is no sequence or hierarchy for that in the table, we can perceive the sequence as below.
1 NotStarted
2 Released
3 In-Process
4 Sold
5 Completed
So 'Completed' is the highest status and each order goes through these Statuses until they are Completed. if they are not completed yet, they should be in one of the other status.
When I filter on Completed, I miss out on the other records. When I include all Status, I get multiple records of same order such as 1 record for Released, 1 record for InProcess, etc (i.e, the various stages of the order)
select * from OrderTable
where Status = 'Completed'
I want to have the ability to do something like this --
COALESCE(Completed,Sold,In-Process,Released,NotStarted, NULL)
In other words, I want to get the highest record for that status and only 1 record for each order.
Is this possible in Sql?
One option is to use ROW_NUMBER() with a Case expression to establish your ordering.
SELECT *
FROM
(
SELECT OrderNumber,
Status,
ROW_NUMBER() OVER (PARTITION BY OrderNumber ORDER BY
CASE Status
WHEN 'NotStarted' THEN 1
WHEN 'Released' THEN 2
WHEN 'In-Process' THEN 3
WHEN 'Sold' THEN 4
WHEN 'Completed' THEN 5
END DESC) as Order_Status_Rank
FROM OrderTable
) dt
WHERE Order_Status_Rank = 1;
See it in action here
If you used the numbers shown in your list instead of the text, all you would have to do is select Top 1 and sort on the number. You can use another table to store the number/entry data. By the way, this would also prevent erroneous data entry.

Including columns from duplicate rows in select

I am working on creating a query which would return one row for each primary key. The question is that in the database there is another table I am trying to join with, and in this other table the primary key from the first table can appear multiple times but with a code which describes what type of information is stored in a column called text_info which stores text related to what the code represents.
For example:
PrimaryKey|Code |text_info
--------------------------------
5555 |1 |1/4/2017
5555 |2 |Approved
What I would am trying to get to is a select statement that would return something like this.
PrimaryKey|Date |Status
----------------------------------
5555 |1/4/2017 |Approved
I have been trying to join the two tables in various ways but my attempts have always returned multiple rows which I do not want for this query. Any help would be greatly appreciated for this.
I think a simple conditional aggregation would do the trick here. If you have a large and/or variable number of codes, you may have to go DYNAMIC.
Select PrimaryKey
,Date = max(case when code=1 then text_info else null end)
,Status = max(case when code=2 then text_info else null end)
From YourTable
Group By PrimaryKey

if-then-else construction in complex stored procedure

I am relatively new to sql queries and I was wondering how to create a complex stored procedure. My database runs on SQL server.
I have a table customer (id, name) and a table customer_events (id, customer_id, timestamp, action_type). I want add a calculated field customer_status to table customer which is
0: (if there is no event for this customer in customer_events) or (the most recent event is > 5 minutes ago)
1: if the most recent event is < 5 minutes ago and action_type=0
2: if the most recent event is < 5 minutes ago and action_type=1
Can I use if-then-else constructions or should I solve this challenge differently?
As you mentioned in comments, you actually want to add a field to a select query, and in a general sense what you want is a CASE statement. They work like this:
SELECT field1,
field2,
CASE
WHEN some_condition THEN some_result
WHEN another_condition THEN another_result
END AS field_alias
FROM table
Applied to your specific scenario, well it's not totally straightforward. You're certainly going to need to left join your status table, you also want to aggregate to find the most recent event, along with that event's action type. Once you have that information, the case statement is straightforward.
Always hard to write sql without access to your data, but something like:
SELECT c.id,
c.name,
CASE
WHEN e.id IS NULL OR DATEDIFF(minute,e.timestamp,getDate())>=5 THEN 0
WHEN DATEDIFF(minute,e.timestamp,getDate())<5 AND s.action_type=1 THEN 1
WHEN DATEDIFF(minute,e.timestamp,getDate())<5 AND s.action_type=0 THEN 2
END as customer_status
FROM clients c
LEFT JOIN (
SELECT id, client_id, action_type,
rank() OVER(partition by client_id order by timestamp desc) AS r
FROM customer_events
) e
ON c.id=e.client_id AND e.r=1
The core of this is the subquery in the middle, it's using a rank funtion to give a number to each status by client_id ordered by the timestamp descending. Therefore every record with a rank of 1 will be the most recent (for that client). Thereafter, you simply join it on to the client table, and use it to determine the right value for customer_status
Presuming you get the event info into "Most_Recent_Event_Mins_Ago". If none it will be NULL.
SELECT Id, Name,
CASE
WHEN Most_Recent_Event_Mins_Ago IS NULL THEN 0
WHEN Most_Recent_Event_Mins_Ago <5 AND Action_type = 0 THEN 1
WHEN Most_Recent_Event_Mins_Ago <5 AND Action_type = 1 THEN 0
..other scenarions
ELSE yourDefaultValueForStatus
END as Status
FROM customer
WHERE
...
...

Converting Rows to Columns in SQL SERVER 2008

In SQL Server 2008,
I have a table for tracking the status history of actions (STATUS_HISTORY) that has three columns ([ACTION_ID],[STATUS],[STATUS_DATE]).
Each ACTION_ID can have a variable number of statuses and status dates.
I need to convert these rows into columns that preferably look something like this:
[ACTION_ID], [STATUS_1], [STATUS_2], [STATUS_3], [DATE_1], [DATE_2], [DATE_3]
Where the total number of status columns and date columns is unknown, and - of course - DATE_1 correlates to STATUS_1, etc. And I'd like for the status to be in chronological order (STATUS_1 has the earliest date, etc.)
My reason for doing this is so I can put the 10 most recent Statuses on a report in an Access ADP, along with other information for each action. Using a subreport with each status in a new row would cause the report to be far too large.
Is there a way to do this using PIVOT? I don't want to use the date or the status as a column heading.
Is it possible at all?
I have no idea where to even begin. It's making my head hurt.
Let us suppose for brevity that you only want 3 most recent statuses for each action_id (like in your example).
Then this query using CTE should do the job:
WITH rownrs AS
(
SELECT
action_id
,status
,status_date
,ROW_NUMBER() OVER (PARTITION BY action_id ORDER BY status_date DESC) AS rownr
FROM
status_history
)
SELECT
s1.action_id AS action_id
,s1.status AS status_1
,s2.status AS status_2
,s3.status AS status_3
,s1.status_date AS date_1
,s2.status_date AS date_2
,s3.status_date AS date_3
FROM
(SELECT * FROM rownrs WHERE rownr=1) AS s1
LEFT JOIN
(SELECT * FROM rownrs WHERE rownr=2) AS s2
ON s1.action_id = s2.action_id
LEFT JOIN
(SELECT * FROM rownrs WHERE rownr=3) AS s3
ON s1.action_id = s3.action_id
NULL values will appear in the rows where the action_id has less then 3 status-es.
I haven't had to do it with two columns, but a PIVOT sounds like what you should try. I've done this in the past with dates in a result set where I needed the date in each row be turned into the columns at the top.
http://msdn.microsoft.com/en-us/library/ms177410.aspx
I sympathize with the headache from trying to design and visualize it, but the best thing to do is try getting it working with one of the columns and then go from there. It helps once you start playing with it.

Use SUM in update statement where criteria

I have been trying to search for this and am not sure I am using the correct terms. Or it might just be that it's not possible. But what I am trying to do is update all records in a table that make up a sum value that is less than a set value.
So here is an example:
ID type amount received_date processed_date
1 debit 10 7/1/2010 NULL
2 debit 10 7/2/2010 NULL
3 debit 10 7/3/2010 NULL
Now what I want to do is update all records that are equal to a sum of less than 22. So when I would do the sum id 1 and 2 would equal 20 which is less than 22. But it also needs to be only records that have a null for processed_date. I also want it to work so that it updates from oldest to newest.
Basically here is how I would write it in pseudo code:
UPDATE credits
SET date_processed = '8/1/2010'
WHERE SUM(amount) <= #total AND
credit_type = [debits]
But I know that this doesn't work. So I'm hoping some SQL master might have ideas.
I'm sure I could write this within a cursor but I'm wondering if there is a set based way to perform this.
EDIT: I updated the table and brief description below to better portray my circumstance.
Rows in a SQL table, represent an unordered list of items. Thus, we have to provide an order. In your example, you hint that it should process the rows ordered by Id.
Update TableName
Set processed_date = '2010-08-01'
Where [type] = 'debit'
And Exists (
Select 1
From TableName As C1
Where C1.Id <= TableName.Id
And C1.[type] = 'debit'
Having Sum(C1.amount) <= #total
)
As I mentioned in comments, it is not safe to depend on Id being the marker for sequence. It is possible to have gaps and for someone to insert "later" rows into those gaps using IDENTITY_INSERT. Instead, you should use a datetime column. If that column is received_date, then simply substitute Id for received_date in the above query.
You should use the HAVING clause for this type of situations.
According to w3schools, "The HAVING clause was added to SQL because the WHERE keyword could not be used with aggregate functions."
UPDATE credits
SET date_processed = '8/1/2010'
WHERE credit_type = [debits]
HAVING SUM(amount) <= #total
Here is a great tutorial found on w3schools
http://www.w3schools.com/SQL/sql_having.asp