How to return Value based on Subquery in SQL Server? - sql

I Have a table with ITEM , OPCODE ,WorkCenter, LOT , COIL ,Strt_Time(DateTime) .I want to return Previous WorkCenter and NextWorkCenter along with ITEM , OPCODE ,WorkCenter, LOT , COIL ,Strt_Time .
A coil of particlular LOT will be routed from Workcenter to Workcenter with time stamp(Strt_time)
Please kindly advise how to achieve this in SQL Server 2008 .
Thanks in advance.

In SQL Server 2012 you should use LEAD and LAG Functions.
Since you are using SQL Server 2008, here is a workaround:
WITH WorkCenter_details AS
(
SELECT
ROW_NUMBER() OVER ( ORDER BY Strt_Time) rn,
(ROW_NUMBER() OVER ( ORDER BY Strt_Time)) / 2 rndiv2,
(ROW_NUMBER() OVER ( ORDER BY Strt_Time) + 1) / 2 rnplus1div2,
ITEM, OPCODE, WorkCenter, LOT, COIL, Strt_Time
FROM
MyTable
)
SELECT
rn, rndiv2, rnplus1div2,
ITEM, OPCODE, WorkCenter, LOT, COIL, Strt_Time,
CASE
WHEN rn % 2 = 1
THEN MAX(CASE WHEN rn % 2 = 0 THEN WorkCenter END) OVER (PARTITION BY rndiv2)
ELSE MAX(CASE WHEN rn % 2 = 1 THEN WorkCenter END) OVER (PARTITION BY rnplus1div2)
END AS WorkCenter_Previous,
CASE
WHEN rn % 2 = 1
THEN MAX(CASE WHEN rn % 2 = 0 THEN WorkCenter END) OVER (PARTITION BY rnplus1div2)
ELSE MAX(CASE WHEN rn % 2 = 1 THEN WorkCenter END) OVER (PARTITION BY rndiv2)
END AS WorkCenter_Next
FROM
WorkCenter_details
ORDER BY
ITEM, Strt_Time
This works only if workcenter is numeric

Related

Current and previous days date diff in days with some condition

I have the first three fields of the following table. I want to compute the number of consecutive days an amount was higher than 0 ("days" field).
key
date
amount
days
1
2023-01-23
0
0
1
2023-01-22
10
2
1
2023-01-21
20
1
1
2023-01-20
0
0
1
2023-01-19
0
0
1
2023-01-18
0
0
1
2023-01-17
3
1
1
2023-01-16
0
0
I have tried with some windows function using this link. Did not add and reset to 1 if the previous amount is 0.
My code:
case when f.amount > 0
then SUM ( DATE_PART('day',
date::text::timestamp - previou_bus_date::text::timestamp )
) OVER (partition by f.key
ORDER BY f.date
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW )
else 0
end as days
Another option, you could use the difference between two row_numbers approach as the following:
select key, date, amount,
sum(case when amount > 0 then 1 else 0 end) over
(partition by key, grp, case when amount > 0 then 1 else 0 end order by date) days
from
(
select *,
row_number() over (partition by key order by date) -
row_number() over (partition by key, case when amount > 0 then 1 else 0 end order by date) grp
from table_name
) T
order by date desc
See demo
This problem falls into the gaps-and-islands kind of problem, as long as you need to compute consecutive values of non-null amounts.
You can reliably solve this problem in 3 steps:
flagging when there's a change of partition, by using 1 when current amount > 0 and previous amount = 0
compute a running sum (with SUM) on flags generated at step 1, to create your partitioning, which to observe the number of consecutive values on
compute a ranking (with ROW_NUMBER) to rank your non-null consecutive amounts in each partition generated at step 2
WITH cte AS (
SELECT *,
CASE WHEN amount > 0
AND LAG(amount) OVER(PARTITION BY key_ ORDER BY date_) = 0
THEN 1
END AS change_part
FROM tab
), cte2 AS (
SELECT *,
SUM(change_part) OVER(PARTITION BY key_ ORDER BY date_) AS parts
FROM cte
)
SELECT key_, date_, amount,
CASE WHEN amount > 0
THEN ROW_NUMBER() OVER(PARTITION BY key_, parts ORDER BY date_)
ELSE 0
END AS days
FROM cte2
ORDER BY date_ DESC
Check the demo here.
Note: This is not the most performant solution, although I'm leaving it for reference to the next part (missing consecutive dates). #Ahmed's answer is more likely to work better in this case.
If your data should ever have holes in dates (some missing records, making the consecutiveness of amounts no-more valid), you should add a further condition in Step 1, where you create the flag for changing partition.
The partition should change:
either if when current amount > 0 and previous amount = 0
or if current date is greater than previous date + 1 day (consecutive dates are not consecutive in time)
WITH cte AS (
SELECT *,
CASE WHEN (amount > 0
AND LAG(amount) OVER(PARTITION BY key_ ORDER BY date_) = 0)
OR date_ > LAG(date_) OVER(PARTITION BY key_ ORDER BY date_)
+ INTERVAL '1 day'
THEN 1
END AS change_part
FROM tab
), cte2 AS (
...
Check the demo here.

Working out a rolling average where you don't always divide by the total count

I am trying to work out a rolling average for batsmen in cricket. Anyone who knows the sport will know that the average is worked out as runs scored / innings, unless a batsmen is not out. If a batsmen plays 2 innings, and is 'not out' in 1 of those, their average would be worked out as
runs scored innings 1 + runs scored innings 2 / 1
If they were out in both innings, the calculation would be
runs scored innings 1 + runs scored innings 2 / 2
This is easy enough to work for an overall average, however I would like to calculate this as a running average. I have done this before using a loop and calculating the average for each row individually, but can anyone suggest a way to do this using any built in functions?
Current code example:
with cte as (
select
Innings_Player,
Innings_Runs_Scored,
Innings_Date,
CASE WHEN Innings_Runs_Scored = "DNB" THEN null WHEN Innings_Runs_Scored LIKE "%*%" THEN REPLACE(Innings_Runs_Scored,"*","") ELSE Innings_Runs_Scored END AS RunsNum,
CASE WHEN Innings_Runs_Scored LIKE "%*%" THEN 1 ELSE 0 END AS NotOutFlag,
ROW_NUMBER() OVER (PARTITION BY Innings_Player ORDER BY Innings_Date) as RN
from TABLE
where Innings_Player = "JE Root"
AND Innings_Runs_Scored IS NOT NULL
ORDER BY Innings_Date
)
,cte2 as
(
select
*,
SUM(CAST(RunsNum AS INT64)) OVER (PARTITION BY Innings_Player ORDER BY RN) AS RunningTotal,
AVG(CAST(RunsNum AS INT64)) OVER (PARTITION BY Innings_Player ORDER BY RN) AS RunningAvg,
from cte
where runsNum IS NOT NULL AND runsNum <> "TDNB"
)
select * from cte2
Resulting dataset:
So, the average is not correct. For the rolling average, the calcuation for row three should be innings_run_scored for the first three rows, divided by 2 rather than 3, as you can see from the NotOutFlag, that the 3 innings in the list was a not out.
Similarly, row 4 should be divided by 3, row 5 by 4, and then as row 6 was a not out as well, row 6 should be divided by 4, row 7 by 5 etc etc. I think the equation would be
Innings_Run_Scored / Innings - Not Out Count
AVG is basically a SUM / COUNT Since you want to alter the COUNT portion I would suggest forgoing the use of the AVG function. You can count using a SUM with CASE to count only the cases where NotOutFlag is 0
so the line
AVG(CAST(RunsNum AS INT64)) OVER (PARTITION BY Innings_Player ORDER BY RN) AS RunningAvg,
would become
SUM(CAST(RunsNum AS INT64)) OVER (PARTITION BY Innings_Player ORDER BY RN)
/ SUM(CASE WHEN NotOutFlag = 0 THEN 1 ELSE 0 END) OVER (PARTITION BY Innings_Player ORDER BY RN)
AS RunningAvg,
Of course you will need to add some more logic to avoid division by 0.
CASE WHEN SUM(CASE WHEN NotOutFlag = 0 THEN 1 ELSE 0 END) OVER (PARTITION BY Innings_Player ORDER BY RN) = 0
THEN 0
ELSE
SUM(CAST(RunsNum AS INT64)) OVER (PARTITION BY Innings_Player ORDER BY RN)
/
SUM(CASE WHEN NotOutFlag = 0 THEN 1 ELSE 0 END) OVER (PARTITION BY Innings_Player ORDER BY RN)
END AS RunningAvg,

How to get multiple columns in Crosstab

I would like a cross table from the following table.
The cross table should look like this
A pivot table does not seem to solve the problem, because only one column can be used at a time. But in our case we are dealing with 4 different columns. (payment, month, year and free of charge)
I solved the problem by splitting these 4 columns into four different pivot tables, using temporary tables and finally reassembling the obtained data. But this is very complicated, long and confusing, in short not very nice...
The years and months should be shown in ascending form, exactly as you can see in the cross table above.
I have been looking for a solution for quite a while but I can't find the same problem anywhere.
If someone would give me a short, elegant solution I would be very grateful.
Under http://www.sqlfiddle.com/#!18/7216f/2 you can see the problem definition.
Thank you!
You can rank records by date in a subquery with row_number(), and then pivot with conditional aggregation:
select
ClientId,
max(case when rn = 1 then Payment end) Payment1,
max(case when rn = 2 then Payment end) Payment2,
max(case when rn = 3 then Payment end) Payment3,
max(case when rn = 1 then [Month] end) Month1,
max(case when rn = 2 then [Month] end) Month2,
max(case when rn = 3 then [Month] end) Month3,
max(case when rn = 1 then [Year] end) Year1,
max(case when rn = 2 then [Year] end) Year2,
max(case when rn = 3 then [Year] end) Year3,
max(case when rn = 1 then FreeOfCharge end) FreeOfCharge1,
max(case when rn = 2 then FreeOfCharge end) FreeOfCharge2,
max(case when rn = 3 then FreeOfCharge end) FreeOfCharge3
from (
select
t.*,
row_number() over(partition by ClientId order by [Year], [Month]) rn
from mytable t
) t
group by ClientId
You can join the table with itself a few times, as in:
with p as (
select
*, row_number() over(partition by clientid order by year, month) as n
from Payment
)
select
p1.clientid,
p1.payment, p2.payment, p3.payment,
p1.month, p2.month, p3.month,
p1.year, p2.year, p3.year,
p1.freeofcharge, p2.freeofcharge, p3.freeofcharge
from p p1
left join p p2 on p2.clientid = p1.clientid and p2.n = 2
left join p p3 on p3.clientid = p1.clientid and p3.n = 3
where p1.n = 1
See Fiddle.

sql purchase items

I need a stored procedure for solve this problem. I have table with name Items With Values
id qty
1 5
2 10
3 15
If a parameter value = 10, and the table will be
id qty
1 0
2 5
3 15
If your dbms supports window functions (ms sql server 2012+ is used below):
declare #prm int = 10;
select id, qty
, case
when qty + tot <= #prm then 0
when tot > #prm then qty
else (qty + tot) - #prm
end currQty
from (
select id, qty, coalesce (sum(qty) over(order by id rows between unbounded preceding and 1 preceding), 0) tot
from
-- your table here
(values
(1,5 )
,(2,10)
,(3,15)
) tbl (id,qty)
) t;
Basically, you want to use a cumulative sum for this purpose. The rest is just arithmetic. I like to phrase this as:
select t.*,
(case when running_qty <= #parameter
then 0
when running_qty - qty <= #parameter
then running_qty - #parameter
else qty
end) as new_qty
from (select t.*,
sum(qty) over (order by id) as running_qty
from t
) t;
Here is a db<>fiddle.

SQL Server 2008 Combine two rows into one

I have written pretty straightforward queries so far, so I am now looking a help to write a SQL statement so that it will combine two separate period end rows from a table into one row. The rows are basically can be matched by their PId, Region, Market, Code, Source. For example-
if 1st row is:
Id Region Market CODE Source Period_End Amt Pct
100 CAN CABLE V1 SA 20120930 100.00 0.2
and 2nd row is:
Id Region Market CODE Source Period_End Amt Pct
100 CAN CABLE V1 SA 20121231 200.00 0.5
Then the SQL should return this result:
Id Region Market CODE Source Period_End_1 Amt_1 Pct_1 Period_End_2 Amt_2 Pct_2
100 CAN CABLE V1 SA 20120930 100.00 0.2 20121231 200.00 0.5
Your help is really appreciated.
Ana.
Thanks for your responses. This is what I started with but I am not sure if I am on right direction or not. I also noticed as I would add more and more information to the row based on Period End then the below query would be too long with redundant "case condition" in each select.
select
A.id , A.region, A.market, A.code, A.source ,
case when period_end = #day_id1 then period_end else '' end as Period_End_1,
case when period_end = #day_id2 then period_end else '' end as Period_End_2,
case when period_end = #day_id1 then Amt else 0.0 end as Amt_1,
case when period_end = #day_id2 then Amt else 0.0 end as Amt_2,
case when period_end = #day_id1 then Pct else 0.0 end as Pct_1,
case when period_end = #day_id2 then pct else 0.0 end as Pct_2,
from
products A with (nolock)
where
A.product_id in (select product_id from #products) -- temp table holding multiple Ids
If I'm understanding your question correctly, you're trying to pivot multiple rows into multiple columns.
Assuming it's always 2 rows you're trying to combine, using the period_end field to order the first from the second, then something like this should work using max with case to pivot your results:
WITH CTE AS (
SELECT *,
Row_Number() Over (Partition By Id, Region, Market, Code, Source
Order By Period_End) rn
FROM YourTable
)
SELECT Id,
Region,
Market,
Code,
Source,
max(case when rn = 1 then Period_End end) Period_End_1,
max(case when rn = 1 then Amt end) Amt_1,
max(case when rn = 1 then Pct end) Pct_1,
max(case when rn = 2 then Period_End end) Period_End_2,
max(case when rn = 2 then Amt end) Amt_2,
max(case when rn = 2 then Pct end) Pct_2
FROM CTE
GROUP BY Id, Region, Market, Code, Source
If you have more potential period_end dates, then you might need to use dynamic sql to achieve your results.
SELECT t1.Id
,t1.Region
,t1.Market
,t1.CODE
,t1.Source
,t1.Period_End AS Period_End_1
,t1.Amt AS Amt_1
,t1.Pct AS Pct_1
,t2.Period_End AS Period_End_2
,t2.Amt AS Amt_2
,t2.Pct AS Pct_2
FROM Table_Name t1
INNER JOIN TABLE_Name t2 ON t1.ID = t2.ID
WHERE t1.ID = 100 AND t1.Period_End <> t2.Period_End