SQL: How to query the distinct values for all columns - sql

For example,
The table has 3 rows and 3 columns:
Name Age Gender
Peter 25 M
John 29 M
Alex 25 M
And I want to query the table and get
Name Age Gender
Peter 25 M
John 29
Alex
The method I have tried:
SELECT DISTINCT Name,Age,Gender FROM table
The output is still
Name Age Gender
Peter 25 M
John 29 M
Alex 25 M
How to achieve the table that there is no redundant entries for every field? Thanks.
Thanks for the help from all of you, especially the help from donPablo.
Here's my VBA code to achieve that. Since I am totally new to VBA, the code might not be very clean and efficient. But at least it works.
Option Compare Database
Sub ReadDistinctValue()
Dim d As Database
Dim rs As Recordset
Dim FN As Field, Age As Field, Sex As Field
Set d = CurrentDb()
Set rs = d.OpenRecordset("Table1")
Set FN = rs.Fields("FN")
Set Age = rs.Fields("Age")
Set Sex = rs.Fields("Sex")
d.Execute "CREATE TABLE Table4 (FN Text,Age Text,Sex Text)"
While Not rs.EOF
If CheckFN(FN) = False Then
Call WriteFN(FN)
End If
If CheckAge(Age) = False Then
Call WriteAge(Age)
End If
If CheckSex(Sex) = False Then
Call WriteSex(Sex)
End If
rs.MoveNext
Wend
rs.Close
End Sub
Function CheckFN(FN As Field) As Boolean
Dim d As Database
Dim rs_new As Recordset
Dim FN_new As Field
Set d = CurrentDb()
Set rs_new = d.OpenRecordset("Table4")
Set FN_new = rs_new.Fields("FN")
CheckFN = False
Do While Not rs_new.EOF
If FN_new = FN Then
CheckFN = True
Exit Do
End If
rs_new.MoveNext
Loop
rs_new.Close
End Function
Function WriteFN(FN As Field)
Dim d As Database
Dim rs_new As Recordset
Dim FN_new As Field
Set d = CurrentDb()
Set rs_new = d.OpenRecordset("Table4")
Set FN_new = rs_new.Fields("FN")
If Not rs_new.EOF Then
rs_new.MoveFirst
End If
Do While True
If rs_new.EOF Then
rs_new.AddNew
FN_new = FN
rs_new.Update
Exit Do
End If
If IsNull(FN_new.Value) Then
rs_new.Edit
FN_new = FN
rs_new.Update
Exit Do
End If
rs_new.MoveNext
Loop
rs_new.Close
End Function
Function CheckAge(Age As Field) As Boolean
Dim d As Database
Dim rs_new As Recordset
Dim Age_new As Field
Set d = CurrentDb()
Set rs_new = d.OpenRecordset("Table4")
Set Age_new = rs_new.Fields("Age")
CheckAge = False
Do While Not rs_new.EOF
If Age_new = Age Then
CheckAge = True
Exit Do
End If
rs_new.MoveNext
Loop
rs_new.Close
End Function
Function WriteAge(Age As Field)
Dim d As Database
Dim rs_new As Recordset
Dim Age_new As Field
Set d = CurrentDb()
Set rs_new = d.OpenRecordset("Table4")
Set Age_new = rs_new.Fields("Age")
If Not rs_new.EOF Then
rs_new.MoveFirst
End If
Do While True
If rs_new.EOF Then
rs_new.AddNew
Age_new = Age
rs_new.Update
Exit Do
End If
If IsNull(Age_new.Value) Then
rs_new.Edit
Age_new = Age
rs_new.Update
Exit Do
End If
rs_new.MoveNext
Loop
rs_new.Close
End Function
Function CheckSex(Sex As Field) As Boolean
Dim d As Database
Dim rs_new As Recordset
Dim Sex_new As Field
Set d = CurrentDb()
Set rs_new = d.OpenRecordset("Table4")
Set Sex_new = rs_new.Fields("Sex")
CheckSex = False
Do While Not rs_new.EOF
If Sex_new = Sex Then
CheckSex = True
Exit Do
End If
rs_new.MoveNext
Loop
rs_new.Close
End Function
Function WriteSex(Sex As Field)
Dim d As Database
Dim rs_new As Recordset
Dim Sex_new As Field
Set d = CurrentDb()
Set rs_new = d.OpenRecordset("Table4")
Set Sex_new = rs_new.Fields("Sex")
If Not rs_new.EOF Then
rs_new.MoveFirst
End If
Do While True
If rs_new.EOF Then
rs_new.AddNew
Sex_new = Sex
rs_new.Update
Exit Do
End If
If IsNull(Sex_new.Value) Then
rs_new.Edit
Sex_new = Sex
rs_new.Update
Exit Do
End If
rs_new.MoveNext
Loop
rs_new.Close
End Function

By naming the three columns, you are retrieving distinct combinations of that set of values.
If you want lists of distinct values, name each individually in a select.
SELECT DISTINCT Name FROM table
SELECT DISTINCT Age FROM table
SELECT DISTINCT Gender FROM table
If you are trying to get them to display as you have in your example, that will have to be accomplished by some GUI functionality. SQL database engines are not good at display trickery, just handling data.

I have expanded the table values a little, just to see what would happen --
FN Age Sex
Alice 28 F
Ben 19 M
Charles 33 M
Doug 23 M
Elaine 21 F
Frank 25 M
Gwen 28 F
Helen 33 F
Alice 17 F
Ben 21 F
Then I developed a single query for FN, and later generalized to all three fields --
The clue is to sequence # each FN/AGE/SEX and then join on that seq#--
SELECT
AB.fn,
CD.age,
EF.sex
FROM
((SELECT A.fn, Count(B.fn) AS CNTfn
FROM
(SELECT DISTINCT fn FROM table1) AS A,
(SELECT DISTINCT fn FROM table1) AS B
WHERE B.fn <= A.fn
GROUP BY A.fn) AS AB
LEFT JOIN
(SELECT C.age, Count(D.age) AS CNTage
FROM
(SELECT DISTINCT age FROM table1) AS C,
(SELECT DISTINCT age FROM table1) AS D
WHERE D.age <= C.age
GROUP BY C.age) AS CD
ON AB.cntfn = CD.cntage)
LEFT JOIN
(SELECT E.sex, Count(F.sex) AS CNTsex
FROM
(SELECT DISTINCT sex FROM table1) AS E,
(SELECT DISTINCT sex FROM table1) AS F
WHERE F.sex <= E.sex
GROUP BY E.sex) AS EF
ON AB.cntfn = EF.CNTsex;
This gives the results desired --
FN AGE SEX
Alice 17 F
Ben 19 M
Charles 21
Doug 23
Elaine 25
Frank 28
Gwen 33
Helen
I changed the Sex in my sample table, and added to the following as the first sequencing of the un-Distinct whole table and changed the ON... to XZ.cntall ...
(SELECT X.FN & X.AGE & X.SEX, Count(*) AS CNTall
FROM
(SELECT DISTINCT FN, AGE, SEX FROM table1) AS X,
(SELECT DISTINCT FN, AGE, SEX FROM table1) AS Z
WHERE Z.FN & Z.AGE & Z.SEX <= X.FN & X.AGE & X.SEX
GROUP BY X.FN, X.AGE, X.SEX) as XZ
and now get these results
fn age sex
Alice 17 M
Ben 19 N
Charles 21 O
Doug 23 P
Elaine 25 Q
Frank 28 R
Gwen 33 W
Helen X
Y
Z

There is probably an SQL solution for this. I am constantly amazed at what can be done. However, my answer is that this is a perfect application for VBA.

Related

VBA code - Group by matching columns using SQL

Input:
G F V S P M
10 1 1 1 1 a
10 1 1 1 1 b
10 1 2 1 1 c
10 2 1 1 1 c
11 1 1 1 1 d
11 1 1 2 1 d
11 1 1 2 1 e
Output should be:
G F V S P M
10 1 1 1 1 a, b
10 1 2 1 1 c
10 2 1 1 1 c
11 1 1 1 1 d, e
11 1 1 2 1 d, e
Public Function Test()
Dim sqlCON As New ADODB.Connection
Dim sqlREC As New ADODB.Recordset
Dim sqlSTR As String
Dim sqlSTR2 As String
Dim newWB As Workbook
Set newWB = ActiveWorkbook
With sqlCON
.Provider = "Microsoft.ACE.OLEDB.12.0;"
.ConnectionString = "Data Source='" & newWB.FullName & "';Extended Properties=""Excel 12.0 Xml;HDR=Yes;IMEX=1"";"
.Open
End With
sqlSTR = "SELECT G, F, V, S, P, M " & _
"FROM [Sheet1$];"
Set sqlREC = sqlCON.Execute(sqlSTR)
If sqlREC.BOF = False And sqlREC.EOF = False Then
getAD = sqlREC.GetRows
Else
getAD = Empty
End If
Set sqlREC = Nothing
sqlCON.Close
In the SELECT section of the code, I have already tried FOR XML PATH, STRING_AGG and GROUP_CONCAT, but had no success, it seems these functions are not supported in VBA.
The "ID" of the row is a combination of all the columns, where the leading column is V, for each V (combined with the other columns) we have a combination of M.
Does someone have a clue on how I can get the desired output?

Cross table VB.NET & SQL Server & Linq

I have a table like this:
MAName feldtext
------------------
karl fieldtext1
karl fieldtext2
karl fieldtext1
karl fieldtext3
karl fieldtext4
karl fieldtext2
karl fieldtext5
karl fieldtext3
karl fieldtext3
susi fieldtext1
susi fieldtext4
john fieldtext2
john fieldtext5
john fieldtext5
and I need:
MAName fieldtext1 fieldtext2 fieldtext3 fieldtext4 fieldtext5 FehlerJeMA
karl 2 2 3 1 1 9
susi 1 0 0 1 0 2
john 0 1 0 0 2 3
The columns fieldtext can go from fieldtext1 to fieldtextn, it's dynamic, depending on query.
I was looking here for solutions and found, so my approach:
Dim dt2 As New DataTable
Dim nn As Integer = 0
Dim Zeile As DataRow
dt2.Columns.Add("MAName")
' fieldtext distinct
Dim query2 = (From dr In (From d In newTable2.AsEnumerable Select New With {.feldtext1 = d("feldtext")}) Select dr.feldtext1 Distinct)
For Each Feldtext In query2
dt2.Columns.Add(Feldtext)
Next
column = New DataColumn()
column.DataType = System.Type.GetType("System.Int32")
column.ColumnName = "FehlerJeMA"
dt2.Columns.Add(column)
' MAName distinct
Dim query3 = (From dr In (From d In newTable2.AsEnumerable Select New With {.MAName2 = d("MAName")}) Select dr.MAName2.ToString.ToLower Distinct)
For Each Mitarbeiter In query3
Zeile = dt2.NewRow()
Zeile(0) = Mitarbeiter.ToString.ToLower
MA2 = Mitarbeiter.ToString.ToLower
nn = 1
For Each colName2 In query2
Fehler2 = colName2
Dim AnzahlFehler As String = (From row In newTable2.Rows Select row Where row("MAName").ToString.ToLower = MA2 And row("feldtext") = Fehler2).Count
If AnzahlFehler = 0 Then
AnzahlFehler = ""
End If
Zeile(nn) = AnzahlFehler
nn += 1
If AnzahlFehler <> "" Then
FehlerJeMA += CInt(AnzahlFehler)
End If
Next
Zeile(nn) = FehlerJeMA
dt2.Rows.Add(Zeile)
Next
This works, but is very slow...
It could be the case that in my table has more than 10.000 rows...
So my question is: what is fastest approach to get the result?
Is it some kind of cross table with linq? Other approaches?
In C# you will be able to use the code, try to translate it for your problem:
var pivotData = data.GroupBy(x => new {x.MAName, x.feldtext}, (key, group) => new { MAName = key.Column1, feldtext = key.Column2, count = group.Count() });

Writing a routine to create sequential records

I would like to write a routine which will allow me to take dated events (records) in a table which span accross a set time frame and in the cases where no event took place for a specific day, an event will be created duplicating the most recent prior record where an event DID take place.
For example: If on September 4 Field 1 = X, Field 2 = Y and Field 3 = Z and then nothing took place until September 8 where Field 1 = Y, Field 2 = Z and Field 3 = X, the routine would create records in the table to account for the 3 days where nothing took place and ultimately return a table looking like:
Sept 4: X - Y - Z
Sept 5: X - Y - Z
Sept 6: X - Y - Z
Sept 7: X - Y - Z
Sept 8: Y - Z - X
Unfortunately, my level of programming knowledge although good, does not allow me to logically conclude a solution in this case. My gut feeling tells me that a loop could be the correct solution here but I still an not sure exactly how. I just need a bit of guidance to get me started.
Here you go.
Sub FillBlanks()
Dim rsEvents As Recordset
Dim EventDate As Date
Dim Fld1 As String
Dim Fld2 As String
Dim Fld3 As String
Dim SQL As String
Set rsEvents = CurrentDb.OpenRecordset("SELECT * FROM tblevents ORDER BY EventDate")
'Save the current date & info
EventDate = rsEvents("EventDate")
Fld1 = rsEvents("Field1")
Fld2 = rsEvents("Field2")
Fld3 = rsEvents("Field3")
rsEvents.MoveNext
On Error Resume Next
Do
' Loop through each blank date
Do While EventDate < rsEvents("EventDate") - 1 'for all dates up to, but not including the next date
EventDate = EventDate + 1 'advance date by 1 day
rsEvents.AddNew
rsEvents("EventDate") = EventDate
rsEvents("Field1") = Fld1
rsEvents("Field2") = Fld2
rsEvents("Field3") = Fld3
rsEvents.Update
Loop
' get new current date & info
EventDate = rsEvents("EventDate")
Fld1 = rsEvents("Field1")
Fld2 = rsEvents("Field2")
Fld3 = rsEvents("Field3")
rsEvents.MoveNext
' new records are placed on the end of the recordset,
' so if we hit on older date, we know it's a recent insert and quit
Loop Until rsEvents.EOF Or EventDate > rsEvents("EventDate")
End Sub
With no details about your specifics (table schema, available language options etc), iI guess that you just need the algorithm to pick up. So here's a quick algorithm with no safeguards.
properdata = "select * from data where eventHasTakenPlace=true";
wrongdata = "select * from data where eventHasTakenPlace=false";
for each wrongRecord in wrongdata {
exampleRecord = select a.value1, a.value2,...,a.date from properdata as a
inner join
(select id,max(date)
from properdata
group by id
having date<wrongRecord.date
) as b
on a.id=b.id
minDate = exampleRecord.date;
maxDate = wrongRecord.date -1day; --use proper date difference function as per your language of choice.
for i=minDate to maxDate step 1day{
dynamicsql="INSERT INTO TABLE X(Value1,Value2....,date) VALUES (exampleRecord.Value1, exampleRecord.Value2,...i);
exec dynamicsql;
}
}
Private Sub Command109_Click()
On Error GoTo errhandler
Dim rsEvents As Recordset
Dim EventDate As Date
Dim ProjID As String
Dim Fld1 As String
Dim Fld2 As String
Dim Fld3 As String
Dim Fld4 As String
Dim Fld5 As String
Dim Fld6 As String
Dim Fld7 As String
Dim Fld8 As String
Dim Fld9 As String
Dim Fld10 As String
Dim Fld11 As String
Dim Fld12 As String
Dim Fld13 As String
Dim Fld14 As String
Dim Fld15 As String
Dim Fld16 As String
Dim Fld17 As String
Dim Fld18 As String
Dim Fld19 As String
Dim Fld20 As String
Dim Fld21 As String
Dim st_sql As String
Dim Sql As String
Me.Refresh
Set rsEvents = CurrentDb.OpenRecordset("SELECT * FROM tblProjectMasterListHistory02 ORDER BY LastUpdateDate")
'Save the current date and info
EventDate = rsEvents("LastUpdateDate")
ProjID = rsEvents("ID Project")
Fld1 = rsEvents("OverallPrincipleStatus1")
Fld2 = rsEvents("OverallPrincipleStatus2")
Fld3 = rsEvents("OverallObjectiveStatus")
Fld4 = rsEvents("OverallObjectiveStatus2")
Fld5 = rsEvents("OverallDependencyStatus1")
Fld6 = rsEvents("OverallDependencyStatus2")
Fld7 = rsEvents("OverallAssumptionsStatus1")
Fld8 = rsEvents("OverallAssumptionsStatus2")
Fld9 = rsEvents("OverallConstraintsStatus1")
Fld10 = rsEvents("OverallConstraintsStatus2")
Fld11 = rsEvents("ObjectivesScope")
Fld12 = rsEvents("ObjectivesResources")
Fld13 = rsEvents("ObjectivesProjectPlan")
Fld14 = rsEvents("ObjectivesEffort")
Fld15 = rsEvents("ObjectivesBenefits")
Fld16 = rsEvents("ObjectivesResourceMobilisation")
Fld17 = rsEvents("ObjectivesMetrics")
Fld18 = rsEvents("OverallRiskStatus1")
Fld19 = rsEvents("OverallRiskStatus2")
Fld20 = rsEvents("GovernanceStatus1")
Fld21 = rsEvents("GovernanceStatus2")
rsEvents.MoveNext
Do
' Loop through each blank date
Do While EventDate < rsEvents("LastUpdateDate") - 1 'for all dates up to, but not including the next date
EventDate = EventDate + 1 'advance date by 1 day
rsEvents.AddNew
rsEvents("LastUpdateDate") = EventDate
rsEvents("ID Project") = ProjID
rsEvents("OverallPrincipleStatus1") = Fld1
rsEvents("OverallPrincipleStatus2") = Fld2
rsEvents("OverallObjectiveStatus") = Fld3
rsEvents("OverallObjectiveStatus2") = Fld4
rsEvents("OverallDependencyStatus1") = Fld5
rsEvents("OverallDependencyStatus2") = Fld6
rsEvents("OverallAssumptionsStatus1") = Fld7
rsEvents("OverallAssumptionsStatus2") = Fld8
rsEvents("OverallConstraintsStatus1") = Fld9
rsEvents("OverallConstraintsStatus2") = Fld10
rsEvents("ObjectivesScope") = Fld11
rsEvents("ObjectivesResources") = Fld12
rsEvents("ObjectivesProjectPlan") = Fld13
rsEvents("ObjectivesEffort") = Fld14
rsEvents("ObjectivesBenefits") = Fld15
rsEvents("ObjectivesResourceMobilisation") = Fld16
rsEvents("ObjectivesMetrics") = Fld17
rsEvents("OverallRiskStatus1") = Fld18
rsEvents("OverallRiskStatus2") = Fld19
rsEvents("GovernanceStatus1") = Fld20
rsEvents("GovernanceStatus2") = Fld21
rsEvents.Update
Loop
' get new current date and info
EventDate = rsEvents("LastUpdateDate")
ProjID = rsEvents("ID Project")
Fld1 = rsEvents("OverallPrincipleStatus1")
Fld2 = rsEvents("OverallPrincipleStatus2")
Fld3 = rsEvents("OverallObjectiveStatus")
Fld4 = rsEvents("OverallObjectiveStatus2")
Fld5 = rsEvents("OverallDependencyStatus1")
Fld6 = rsEvents("OverallDependencyStatus2")
Fld7 = rsEvents("OverallAssumptionsStatus1")
Fld8 = rsEvents("OverallAssumptionsStatus2")
Fld9 = rsEvents("OverallConstraintsStatus1")
Fld10 = rsEvents("OverallConstraintsStatus2")
Fld11 = rsEvents("ObjectivesScope")
Fld12 = rsEvents("ObjectivesResources")
Fld13 = rsEvents("ObjectivesProjectPlan")
Fld14 = rsEvents("ObjectivesEffort")
Fld15 = rsEvents("ObjectivesBenefits")
Fld16 = rsEvents("ObjectivesResourceMobilisation")
Fld17 = rsEvents("ObjectivesMetrics")
Fld18 = rsEvents("OverallRiskStatus1")
Fld19 = rsEvents("OverallRiskStatus2")
Fld20 = rsEvents("GovernanceStatus1")
Fld21 = rsEvents("GovernanceStatus2")
rsEvents.MoveNext
'new records are placed on the end of the recordset
'so if we hit an older date, we know it's a recent insert and quit
Loop Until rsEvents.EOF Or EventDate > rsEvents("LastUpdateDate")
errhandler:
End Sub

grouping in LINQ query

Given a table of thousands of rows of data as shown in the sample below
Id Date SymbolId NumOccs HighProjection LowProjection ProjectionTypeId
1 2014-04-09 28 45 1.0765 1.0519 1
2 2014-04-10 5 44 60.23 58.03 1
3 2014-04-11 29 77 1.026 1.0153 1
and a Class defined as
Public Class ProjectionPerformance
Public symbolId As Integer
Public Name as String
Public Date as String
Public ActualRange as Decimal
End Class
I am trying to return the following for each symbolId;
The symbolId (from this table)
The symbol Name (from the symbols table)
The Actual Range (High Projection - Low Projection)
Can this be done in one query since i am essentially in need of a Dictionary(Of Integer, List(Of ProjectionPerformance)) where the integer is the symbolId and the List is generated from the query?
Updated:
So as to be a little clearer, Here is what I'm doing so far but contains two LINQ iterations
Public Shared Function GetRangeProjectionPerformance(Optional daysToRetrieve As Integer = 100) As Dictionary(Of Integer, List(Of ProjectionPerformance))
Dim todaysDate As Date = DateTime.Now.Date
Dim lookbackDate As Date = todaysDate.AddDays(daysToRetrieve * -1)
Dim temp As New Dictionary(Of Integer, List(Of ProjectionPerformance))
Using ctx As New ProjectionsEntities()
Dim query = (From d In ctx.projections
Where d.SymbolId <= 42 AndAlso d.Date >= lookbackDate
Join t In ctx.symbols On d.SymbolId Equals t.Id
Let actualRange = d.HighProjection - d.LowProjection
Select New With {
d.Date,
d.SymbolId,
t.Name,
actualRange}).GroupBy(Function(o) o.SymbolId).ToDictionary(Function(p) p.Key)
For Each itm In query
Dim rpp As New ProjectionPerformance
Dim rppList As New List(Of ProjectionPerformance)
If itm.Value.Count > 0 Then
For x As Integer = 0 To itm.Value.Count - 1
Dim bb As Integer = Convert.ToInt32(itm.Value(x).SymbolId)
With rpp
.SymbolId = bb
.ProjectionDate = itm.Value(x).Date.ToString()
.Name = itm.Value(x).Name
.ProjectedRange = itm.Value(x).actualRange
End With
rppList.Add(rpp)
Next
End If
temp.Add(itm.Key, rppList)
Next
End Using
Return temp
End Function
I'm going to answer in C#, but I think you'll get the gist of it anyway. Basically, you can group by SymbolId, build your object graph and then use ToDictionary using the Key to create dictionary.
var result = (From d In _projectionEntities.projections
Where d.SymbolId <= 42
group d by d.SymbolId into g
select new {
SymbolId = g.Key,
ProjectionPerformances =
g.Select(gg=>new ProjectionPerformance{
SymbolId = gg.SymbolId,
Name = gg.Symbol.Name,
rpDate = gg.Date.ToString(),
ActualRange = gg.HighProjection - gg.LowProjection
})
.ToDictionary(g=>g.SymbolId);
Try
Dim Result = (From d In _ProjectionEntities.projections
Join t In _projectionEntities.symbols On d.SymbolId Equals t.Id
Where d.SymbolId <= 42
Select New With {.SymbolID = d.SymbolID
.Date = d.Date
.Name = t.Name
.ActualRange = d.HighProjection - d.LowProjection})

Run function for multiple data sets and output results to different cells

I have been trying forever to try and figure this out. I have a set of data in a certain sheet in my Excel file. I have written code so that it outputs some of that information to another sheet. I don't know how to get the function to loop through all the different data sets and output them into the "Output" sheet in my excel file on different rows.
This is what I have so far. Can someone please help?
How do I get the function to run through about 6 data sets that include 5 cells in the column until there are 2 blank cells?
How do I output those different results to another sheet? I already have them outputting the first data set and it works fine. I just need to know how to do the other ones.
Thank you!
Sub EstBatch()
'variables
Dim N As String
Dim D As Date
Dim P As Integer
Dim H As Single
Dim NS As Integer
Dim NL As Integer
Dim BP As Currency
Dim OH As Single
Dim OC As Currency
Dim TP As Currency
Dim PPBR As Currency
Dim EHP As Single
Dim batches As Range
'inputs
N = Sheets("Batch Input").Range("A1").Value
D = Sheets("Batch Input").Range("B1").Value
P = Sheets("Batch Input").Range("A2").Value
H = Sheets("Batch Input").Range("A3").Value
PPBR = Sheets("User Form").Range("C22").Value
EHP = Sheets("User Form").Range("C23").Value
Range("A1").Select
'Processes
BP = P * PPBR
OH = H - 5
If P > 120 Or P < 20 Then
MsgBox ("Cannot Accommodate Group")
ElseIf P >= 20 And P <= 25 Then
NS = 1
NL = 0
ElseIf P >= 26 And P <= 50 Then
NS = 2
NL = 0
ElseIf P >= 51 And P <= 60 Then
NS = 0
NL = 1
ElseIf P >= 61 And P <= 85 Then
NS = 1
NL = 1
ElseIf P >= 86 And P <= 120 Then
NS = 0
NL = 2
End If
If OH > 4 Then
OH = 4
OC = BP * OH * EHP
ElseIf 0 < OH <= 4 Then
OC = BP * OH * EHP
ElseIf OH <= 0 Then
OC = 0
End If
TP = BP + OC
'outputs
Sheets("Batch Output").Range("A2").Value = N
Sheets("Batch Output").Range("B2").Value = D
Sheets("Batch Output").Range("C2").Value = P
Sheets("Batch Output").Range("D2").Value = H
Sheets("Batch Output").Range("E2").Value = PPBR
Sheets("Batch Output").Range("F2").Value = EHP
Sheets("Batch Output").Range("G2").Value = NS
Sheets("Batch Output").Range("H2").Value = NL
Sheets("Batch Output").Range("I2").Value = BP
Sheets("Batch Output").Range("J2").Value = OH
Sheets("Batch Output").Range("K2").Value = OC
Sheets("Batch Output").Range("L2").Value = TP
End Sub
Welcome to StackOverflow. Great first question.
I think what you're reaching for is how to use loops in solving a problem like this.
One easy way to do loops is with a counter, as in the examples I've given below. If appropriate, you can also use a range of cells to loop through data, as described in this answer: https://stackoverflow.com/a/19394207/2665195.
Starting with your second question: if you want a separate sheet for each output you can use Sheets.Add and paste into that new sheet. To do this you will want to use a variable naming convention like Sheets("Batch Output" & X).Range. In this way you can Dim X as Integer and loop through the process incrementing the X integer with each loop. Here's some sample code you can adapt for your purpose:
Sub ExampleAddSheets()
Dim intX As Integer
intX = 1
Dim wsBatchOutput As Worksheet
For intX = 1 To 6
Set wsBatchOutput = Worksheets.Add 'adds a worksheet and tags it to a variable
wsBatchOutput.Name = "BatchOutput" & intX 'names the worksheet
wsBatchOutput.Range("A1").Value = "Data here. Example " & intX
Next intX
Set wsBatchOutput = Nothing
End Sub
I don't know what your data source looks like, but hopefully it is set up in a way that you can turn the inputs aquisition into a loop. For example, if the data came into the system in rows (which your example does not seem to do) you could just increment the row number, something like this:
Sub ExampleSetInputs()
'variables
Dim N As String
Dim D As Date
Dim P As Integer
Dim H As Single
Dim PPBR As Currency
Dim EHP As Single
Dim intRow As Integer
intRow = 2
'inputs
For intRow = 2 To 7
N = Sheets("Batch Input").Range("A" & intRow).Value
D = Sheets("Batch Input").Range("B" & intRow).Value
P = Sheets("Batch Input").Range("C" & intRow).Value
H = Sheets("Batch Input").Range("D" & intRow).Value
Next intRow
End Sub
I hope this helps with your challenge.