SQL Server IF 0, add value for following date - sql

I am writing this query in MSSQL Server Management Studio, and I have run into a mental road block. Let's say I have a few buildings and as they have come online I want to compare their production vs what they were forecasted, P_AMOUNT and F_AMOUNT. These are two separate databases combined into one with a query (The production db and forecast db that is).
So my problem here is. I want to select the peak production here along side the forecast each B_ID. Most of the buildings are like B_ID #2 and have a forecast amount, but occasionally there is one that does not. Like B_ID #1. How would I go about rolling the date one month forward for only the F_Date if the F_Amount = 0?
SELECT P1.B_ID, P1.P_DATE, P1.P_AMOUNT, F1.F_DATE, F1.F_AMOUNT
FROM DB1.Production P1
INNER JOIN DB2.Forecast F1
ON P1.DB_ID = F1.DB_ID

Use a nested select of the Forecast table so that you get the minimum forecast date after the production date that has a value, something like:
SELECT MIN(F1.F_DATE)
WHERE F1.F_DATE > P1.P_DATE
AND F1.F_AMOUNT > 0

There are a handful of ways to solve your problem. One way is to rank each row based on F_DATE and when the F_AMOUNT value is zero, to pull the next row. Since you did not specify a version of SQL Server, I used syntax that would work in SQL Server 2005 or later.
With RnkItems As
(
Select B_ID, P_DATE, P_AMOUNT, F_DATE, F_AMOUNT
, Row_Number() Over ( Order By F_DATE ) As Rnk
From SourceData
)
Select R.B_ID, R.P_DATE, R.P_AMOUNT
, R.F_DATE As [Original F_DATE]
, R.F_AMOUNT As [Original F_AMOUNT]
, Case R.F_AMOUNT
When 0 Then R1.F_DATE
Else R.F_DATE
End As F_DATE
, Case R.F_AMOUNT
When 0 Then R1.F_AMOUNT
Else R.F_AMOUNT
End As F_AMOUNT
From RnkItems As R
Left Join RnkItems As R1
On R1.Rnk = R.Rnk + 1
SQL Fiddle Version
If you are using SQL Server 2012, you can use the new Lead function:
Select B_ID, P_DATE, P_AMOUNT
, F_DATE As [Original F_DATE]
, F_AMOUNT As [Original F_AMOUNT]
, Case F_AMOUNT
When 0 Then Lead ( F_DATE, 1 ) Over ( Order By F_DATE )
Else F_DATE
End As F_DATE
, Case F_AMOUNT
When 0 Then Lead ( F_AMOUNT, 1 ) Over ( Order By F_DATE )
Else F_AMOUNT
End As F_AMOUNT
From SourceData
SQL Fiddle Version
The above two solutions rely on the stated requirement that you always go exactly one month ahead. If, however, you want to go the first month with a non-zero value (i.e., it could be multiple jumps), that's different:
Select S.B_ID, S.P_DATE, S.P_AMOUNT
, S.F_DATE As [Original F_DATE]
, S.F_AMOUNT As [Original F_AMOUNT]
, Case S.F_AMOUNT
When 0 Then (
Select Min( S2.F_DATE )
From SourceData As S2
Where S2.F_DATE >= S.F_DATE
And S2.F_AMOUNT <> 0
)
Else S.F_DATE
End As F_DATE
, Case S.F_AMOUNT
When 0 Then (
Select S1.F_AMOUNT
From SourceData As S1
Where S1.F_DATE = (
Select Min( S2.F_DATE )
From SourceData As S2
Where S2.F_DATE > S.F_DATE
And S2.F_AMOUNT <> 0
)
)
Else S.F_AMOUNT
End As F_AMOUNT
From SourceData As S
SQL Fiddle Version

Related

How to select data without using group?

My base data based on dealer code only but in one condition we need to select other field as well to matching the condition in other temp table how can i retrieve data only based on dealercode ith matching the condition on chassis no.
Below is the sample data:
This is how we have selected the data for the requirement:
---------------lastyrRenewalpolicy------------------
IF OBJECT_ID('TEMPDB..#LASTYRETEN') IS NOT NULL DROP TABLE #LASTYRETEN
select DEALERMASTERCODE , count(*) RENEWALEXPRPOLICY,SUM(NETOD_YEAR_PREM_PART_A) AS 'ACHIEVED-ODPREMIUM_RENEWAL' into #LASTYRETEN
from [dbo].[T_RE_POLICY_TRANSACTION]
where cast (InsPolicyCreatedDate as date) between #FirstDayC and #LastDayC
AND PolicyStatus= 'Renewal' AND (ltrim(rtrim(ISCANCELLEDSTATUS)) = 0 ) group by DEALERMASTERCODE
-----------------lastrollower------------------------
IF OBJECT_ID('TEMPDB..#LASTYROLWR') IS NOT NULL DROP TABLE #LASTYROLWR
select DEALERMASTERCODE , count(*) ROLLOWEEXPRPOLICY ,SUM(NETOD_YEAR_PREM_PART_A) AS 'ACHIEVED-ODPREMIUM_ROLLOVER'
into #LASTYROLWR from [dbo].[T_RE_POLICY_TRANSACTION] where cast (InsPolicyCreatedDate as date) between #FirstDayC and #LastDayC
AND PolicyStatus= 'ROLLOVER' AND (ltrim(rtrim(ISCANCELLEDSTATUS)) = 0 ) group by DEALERMASTERCODE
And continue with above flow Below is the other select statement which creating issue at the end due to grouping
:
-------------OTHERYRBASE(EXPIRYRENEWAL)--------------
IF OBJECT_ID('TEMPDB..#OTHERYRBASEEXPIRY') IS NOT NULL DROP TABLE #OTHERYRBASEEXPIRY
select DEALERMASTERCODE ,ChassisNo , count(*) RENEWALPOLICYEXPIRY
into #OTHERYRBASEEXPIRY
from [dbo].[T_RE_POLICY_TRANSACTION] where cast (PolicyExpiryDate as date) between '2020-08-01' and '2020-08-31'
and BASIC_PREM_TOTAL <> 0 AND PolicyStatus in ('Renewal','rollover') and BusinessType='jcb'
AND (ltrim(rtrim(ISCANCELLEDSTATUS)) = 0 ) group by DEALERMASTERCODE,ChassisNo
-------------OTHERYRBASE(EXPIRYRENEWAL)--------------
IF OBJECT_ID('TEMPDB..#OTHERYRCON') IS NOT NULL DROP TABLE #OTHERYRCON
select OTE.DEALERMASTERCODE ,OTE.ChassisNo , count(*) OTHERYRCON into #OTHERYRCON
from [dbo].[T_RE_POLICY_TRANSACTION] OTE INNER JOIN #OTHERYRBASEEXPIRY EXP
ON OTE.ChassisNo=EXP.ChassisNo
where cast(CREATED_DATE as date) between '2020-06-01' and '2020-12-31' and BusinessType='jcb'
and OTE.BASIC_PREM_TOTAL <> 0 AND OTE.PolicyStatus = 'Renewal'
AND (ltrim(rtrim(ISCANCELLEDSTATUS)) = 0 ) group by OTE.DEALERMASTERCODE,OTE.ChassisNo
Thanks a lot in advance for helping and giving a solution very quickly ///
After taking a look at this code it seems possible there was an omitted JOIN condition in the last SELECT statement. In the code provided the JOIN condition is only on ChassisNo. The GROUP BY in the prior queries which populates the temporary table also included the DEALERMASTERCODE column. I'm thinking DEALERMASTERCODE should be added to the JOIN condition. Something like this
select OTE.DEALERMASTERCODE ,OTE.ChassisNo , count(*) OTHERYRCON
into #OTHERYRCON
from [dbo].[T_RE_POLICY_TRANSACTION] OTE
INNER JOIN #OTHERYRBASEEXPIRY EXP ON OTE.DEALERMASTERCODE=EXP.DEALERMASTERCODE
and OTE.ChassisNo=EXP.ChassisNo
where cast(CREATED_DATE as date) between '2020-06-01' and '2020-12-31'
and BusinessType='jcb'
and OTE.BASIC_PREM_TOTAL <> 0
AND OTE.PolicyStatus = 'Renewal'
AND (ltrim(rtrim(ISCANCELLEDSTATUS)) = 0 )
group by OTE.DEALERMASTERCODE,OTE.ChassisNo;

How make Excel arbitrary pivoting in MS SQL 2017 (cross join + loops)

Would you help me please, to solve the task below in SQL (MS SQL Server 2017). It is simple in Excel, but seems very complicated in SQL.
There is a table with clients and their activities split by days:
client 1may 2may 3may 4may 5may other days
client1 0 0 0 0 0 ...
client2 0 0 0 0 0 ...
client3 0 0 0 0 0 ...
client4 1 1 1 1 1 ...
client5 1 1 1 0 0 ...
It is necessary to create the same table (the same quantity of rows and columns), but turn the values into new one according to the rule:
Current day value =
A) If all everyday values during a week before the day, including the current one = 1, then 1
B) If all everyday values during a week before the day, including the current one = 0, then 0
C) If the values are different, then we leave the status of the previous day (if the status of the previous day is not known, for example, the Client is new, then 0)
In Excel, I do this using the formula: = IF (AND (AF2 = AE2; AE2 = AD2; AD2 = AC2; AC2 = AB2; AB2 = AA2; AA2 = Z2); current_day_value; IF (previous_day_value = ""; 0; previous_day_value )).
The example with excel file is attached.
Thank you very much.
First thing, it's NEVER a good idea to have dates as columns.
So step #1 transpose your columns to rows. In other world to build a table with three columns
```
client date Value
client1 May1 0
client1 May2 0
client1 May3 0
.... ... ..
client4 May1 1
client4 May2 1
client4 May3 1
.... ... ..
```
step #2 perform all the calculations you need by using the date field.
Basically you put always the status of the previous day, in any case (except null).
So, i would do something like this (oracle syntax, working in sql server too), supposing the first columns is 1may
Insert into newTable (client, 1may,2may,....) select (client, 0, coalesce(1may,0), coalesce (2may,0), .... from oldTable;
Anyway me too i believe is not a good practice to put the days as columns of a relational table.
You're going to struggle with this because most brands of SQL don't allow "arbitrary pivoting", that is, you need to specify the columns you want to be displayed on a pivot - Whereas Excel will just do this for you. SQL can do this but it required dynamic SQL which can get pretty complicated and annoying pretty fast.
I would suggest you use sql just to construct the data, and then excel or SSRS (As you're in TSQL) to actually do the visualization.
Anyway. I think this does what you want:
WITH Data AS (
SELECT * FROM (VALUES
('Client 1',CONVERT(DATE, '2020-05-04'),1)
, ('Client 1',CONVERT(DATE, '2020-05-05'),1)
, ('Client 1',CONVERT(DATE, '2020-05-06'),1)
, ('Client 1',CONVERT(DATE, '2020-05-07'),0)
, ('Client 1',CONVERT(DATE, '2020-05-08'),0)
, ('Client 1',CONVERT(DATE, '2020-05-09'),0)
, ('Client 1',CONVERT(DATE, '2020-05-10'),1)
, ('Client 1',CONVERT(DATE, '2020-05-11'),1)
, ('Client 1',CONVERT(DATE, '2020-05-12'),1)
, ('Client 2',CONVERT(DATE, '2020-05-04'),1)
, ('Client 2',CONVERT(DATE, '2020-05-05'),0)
, ('Client 2',CONVERT(DATE, '2020-05-06'),0)
, ('Client 2',CONVERT(DATE, '2020-05-07'),1)
, ('Client 2',CONVERT(DATE, '2020-05-08'),0)
, ('Client 2',CONVERT(DATE, '2020-05-09'),1)
, ('Client 2',CONVERT(DATE, '2020-05-10'),0)
, ('Client 2',CONVERT(DATE, '2020-05-11'),1)
) x (Client, RowDate, Value)
)
SELECT
Client
, RowDate
, Value
, CASE
WHEN OnesBefore = DaysInWeek THEN 1
WHEN ZerosBefore = DaysInWeek THEN 0
ELSE PreviousDayValue
END As FinalCalculation
FROM (
-- This set uses windowing to calculate the intermediate values
SELECT
*
-- The count of the days present in the data, as part of the week may be missing we can't assume 7
-- We only count up to this day, so its in line with the other parts of the calculation
, COUNT(RowDate) OVER (PARTITION BY Client, WeekCommencing ORDER BY RowDate) AS DaysInWeek
-- Count up the 1's for this client and week, in date order, up to (and including) this date
, COUNT(IIF(Value = 1, 1, NULL)) OVER (PARTITION BY Client, WeekCommencing ORDER BY RowDate) AS OnesBefore
-- Count up the 0's for this client and week, in date order, up to (and including) this date
, COUNT(IIF(Value = 0, 1, NULL)) OVER (PARTITION BY Client, WeekCommencing ORDER BY RowDate) AS ZerosBefore
-- get the previous days value, or 0 if there isnt one
, COALESCE(LAG(Value) OVER (PARTITION BY Client, WeekCommencing ORDER BY RowDate), 0) AS PreviousDayValue
FROM (
-- This set adds a few simple values in that we can leverage later
SELECT
*
, DATEADD(DAY, -DATEPART(DW, RowDate) + 1, RowDate) As WeekCommencing
FROM Data
) AS DataWithExtras
) AS DataWithCalculations
As you haven't specified your table layout, I don't know what table and field names to use in my example. Hopefully if this is correct you can figure out how to click it in place with what you have - If not, leave a comment
I will note as well, I've made this purposely verbose. If you don't know what the "OVER" clause is, you'll need to do some reading: https://www.sqlshack.com/use-window-functions-sql-server/. The gist is they do aggregations without actually crunching the rows together.
Edit: Adjusted the calculation to be able to account for an arbitrary number of days in the week
Thank you so much to everyone, especially to David and Massimo, which prompted me to restructure the data.
--we join clients and dates each with each and label clients with 'active' or 'inactive'
with a as (
select client, dates
from (select distinct client from dbo.clients) a
cross join (select dates from dates) b
)
, b as (
select date
,1 end active
,client
from clients a
join dbo.dates b on a.id = b.id
)
select client
,a.dates
,isnull(b.active, 0) active
into #tmp2
from a
left join b on a.client= b.client and a.dates = b.dates
--declare variables - for date start and for loop
declare #min_date date = (select min(dates) from #tmp2);
declare #n int = 1
declare #row int = (select count(distinct dates) from #tmp2) --number of the loop iterations
--delete data from the final results
delete from final_results
--fill the table with final results
--run the loop (each iteration = analyse of each 1-week range)
while #n<=#row
begin
with a as (
--run the loop
select client
,max(dates) dates
,sum (case when active = 1 then 1 else null end) sum_active
,sum (case when active = 0 then 1 else null end) sum_inactive
from #tmp2
where dates between dateadd(day, -7 + #n, #min_date) and dateadd(day, -1 + #n, #min_date)
group by client
)
INSERT INTO [dbo].[final_results]
(client
,[dates]
,[final_result])
select client
,dates
,case when sum_active = 7 then 1 --rule A
when sum_inactive = 7 then 0 -- rule B
else
(case when isnull(sum_active, 0) + isnull(sum_inactive, 0) < 7 then 0
else
(select final_result
from final_results b
where b.dates = dateadd(day, -1, a.dates)
and a.client= b.client) end
) end
from a
set #n=#n+1
end
if object_id(N'tempdb..#tmp2', 'U') is not null drop table #tmp2

COUNT from DISTINCT values in multiple columns

If this has been asked before, I apologize, I wasn't able to find a question/solution like it before breaking down and posting. I have the below query (using Oracle SQL) that works fine in a sense, but not fully what I'm looking for.
SELECT
order_date,
p_category,
CASE
WHEN ( issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END AS issue_group,
srt AS srt_level,
COUNT(*) AS total_orders
FROM
database.t_con
WHERE
order_date IN (
'&Enter_Date_YYYYMM'
)
GROUP BY
p_category,
CASE
WHEN ( issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END,
srt,
order_date
ORDER BY
p_category,
issue_group,
srt_level,
order_date
Current Return (12 rows):
Needed Return (8 rows without the tan rows being shown):
Here is the logic of total_order column that I'm expecting:
count of order_date where (srt_level = 80 + 100 + Late) ... 'Late' counts needed to be added to the total, just not be displayed
I'm eventually adding a filled_orders column that will go before the total_orders column, but I'm just not there yet.
Sorry I wasn't as descriptive earlier. Thanks again!
You don't appear to need a subquery; if you want the count for each combination of values then group by those, and aggregate at that level; something like:
SELECT
t1.order_date,
t1.p_category,
CASE
WHEN ( t1.issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END AS issue_group,
t1.srt AS srt_level,
COUNT(*) AS total_orders
FROM
database.t_con t1
WHERE
t1.order_date = TO_DATE ( '&Enter_Date_YYYYMM', 'YYYYMM' )
GROUP BY
t1.p_category,
CASE
WHEN ( t1.issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END,
t1.srt,
t1.order_date
ORDER BY
p_category,
issue_group,
srt_level,
order_date;
You shouldn't be relying on implicit conversion and NLS settings for your date argument (assuming order_date is actually a date column, not a string), so I've used an explicit TO_DATE() call, using the format suggested by your substitution variable name and prompt.
However, that will give you the first day of the supplied month, since a day number isn't being supplied. It's more likely that you either want to prompt for a full date, or (possibly) just the year/month but want to include all days in that month - which IN() will not do, if that was your intention. It also implies that stored dates all have their time portions set to midnight, as that is all it will match on. If those values have non-midnight times then you need a range to pick those up too.
I got it working to the extent of what my question was. Just needed to nest each column where counts/calculations were happening.
SELECT
order_date,
p_category,
issue_group,
srt_level,
order_count,
SUM(order_count) OVER(
PARTITION BY order_date, issue_group, p_category
) AS total_orders
FROM
(
SELECT
order_date,
p_category,
CASE
WHEN ( issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END AS issue_group,
srt AS srt_level,
COUNT(*) AS order_count
FROM
database.t_con
WHERE
order_date IN (
'&Enter_Date_YYYYMM'
)
GROUP BY
p_category,
CASE
WHEN ( issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END,
srt,
order_date
)
ORDER BY
order_date,
p_category,
issue_group

SQL pivot columns null (code/results included)

I have been playing with the SQL Fiddle, and am very close to being done - I just want some help on getting the column data returned ( I think the column varchar title with two words (example: High Value) is throwing it off.
This is query I'm struggling with:
SELECT Velocity
, [Very High]
, High
, [Medium]
, [Low]
, [Very Low]
, [Total]
FROM (
SELECT CASE
WHEN GROUPING(velocity) = 0 THEN CAST(velocity AS CHAR(7))
ELSE 'Total'
END AS velocity
, CASE
WHEN GROUPING(volume) = 0 THEN CAST(volume AS CHAR(7))
ELSE 'Total'
END AS volume
, SUM(Sales) AS Sales
FROM test1
GROUP BY velocity, volume
WITH CUBE
) AS s
PIVOT(SUM(Sales) FOR volume IN ([Very High], [High], [Medium], [Low], [Very Low], [Total])) AS p;
I'm expecting to see this result:
Adding a SQL Fiddle
I've found mistake in your SQL query. You need to change CAST(volume AS CHAR(7)) to CAST(volume AS CHAR(12)) Otherwise you have in your inner temp table values like "Very Hi" and "Very Lo".

MIN MAX query with a twist

I need to get the MIN and MAX dates for volume but I need to group it based on volume and not all the volume of same amount....
Basically, I have daily volume and dates for those daily volume. I need to be able to get the MIN Date as "to" and MAX date as "from" for a set of volume.
Note that the volume can traverse dates and then break and then have a new set of dates for the same volume.
Hopefully the screenshots below do a better job explaining than I can. I know how to do this via code.. but was wondering if the same was possible with SQL.
Please note that the SQL will be called from within an application and I can't insert into a temp table to get my end result data set...
Here is the raw data that I am querying using select *:
Here is what I ultimately want:
The query that I am running gives me the MIN and MAX for all the occurrences of the volume 1100. I want it split based on the break between dates as shown in the End result screenshot....
Here is my SQL:
SELECT daily_volume, MIN(volume_date) AS min_date, MAX(volume_date) AS max_date, ins_num
FROM daily_volume
WHERE ins_num = 3854439
GROUP BY daily_volume, ins_num
The following was written for sql server, but it should work for other databases (sqlfiddle):
with DatesMinMax as
(
select
volume_date,
daily_volume,
isnull
(
(
select top 1 d2.volume_date
from daily_volume d2
where d.volume_date > d2.volume_date and d.daily_volume <> d2.daily_volume
order by d2.volume_date desc
)
, '1753-01-01'
) as min_date,
isnull
(
(
select top 1 d2.volume_date
from daily_volume d2
where d.volume_date < d2.volume_date and d.daily_volume <> d2.daily_volume
order by d2.volume_date
)
, '9999-12-31'
) as max_date
from daily_volume d
),
DatesFromTo as
(
select d1.daily_volume as qty,
(select min(d2.volume_date)
from DatesMinMax d2
where d2.volume_date > d1.min_date and d2.volume_date < d1.max_date
) as [from],
(select max(d2.volume_date)
from DatesMinMax d2
where d2.volume_date > d1.min_date and d2.volume_date < d1.max_date
) as [to]
from DatesMinMax d1
)
select distinct
qty,
[from],
[to]
from DatesFromTo
order by [from]
DatesMinMax is used to get the first date that had a different volume than the current volume
DatesFromTo is used to get the date range for each row