SQl query to Break up rows to batches - sql

I have a table like this
OrderID Product Quantity
1 A 800
2 B 700
3 C 300
I need a select query in order to split the total quantity of 1800 to a number of batches (eg 3) like this
BatchNo Product Quantity
1 A 600
2 A 200
2 B 400
3 B 300
3 C 300
Is there any way to do this as a select query ?
I use SQL 2016

ceiling(cast(row_number()(order by (select 1)) as decimal (10, 2)) / 3) as BatchNo
This should do what you require. Just add it to your select statement. You can change the number you are dividing by splitting it up into more batches.

Imho the fastest way to do this is write for example CLR SqlProcedure that would read data row by row and produce output record every time "batch" has 300 or more using SqlContext.Pipe.SendResultsStart() / SqlContext.Pipe.SendResultsEnd() / SqlContext.Pipe.SendResultsRow() . It is easy to produce output data in single pass with c#, not so much in tsql
public partial class StoredProcedures
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void MakeBatches()
{
using (var connection = new SqlConnection("context connection=true"))
{
connection.Open();
var command = new SqlCommand(string.Format("SELECT [...]"), connection);
var reader = command.ExecuteReader();
using (reader)
{
var outputRecord = new SqlDataRecord(new SqlMetaData[]
{
new SqlMetaData("BatchNo", SqlDbType.Int),
new SqlMetaData("Product", SqlDbType.Char),
new SqlMetaData("Quantity", SqlDbType.Int)
}
);
SqlContext.Pipe.SendResultsStart(outputRecord);
while (reader.Read())
{
var orderId = reader.GetInt32(0);
var product = (char)reader.GetValue(1);
var quantity = reader.GetInt32(2);
if ([...]) //put some logic here
{
outputRecord.SetInt32(0, batchNo_value_here);
[...] //insert record here
SqlContext.Pipe.SendResultsRow(outputRecord);
}
}
SqlContext.Pipe.SendResultsEnd();
}
}
}
}

Related

SQLite with Android Studio, return all records selected by 2 arguments

let's assume that i have a table with columns such as:
ID SSID BSSID RSSI
1 abcd hs:hd:sd -60
2 abcd hs:hd:po -68
There are about 5000 records with the same SSID, slighltly different BSSID and the LEVEL values. My device is scanning the nearest environment for WiFi networks, therefore I know their MAC address and level of RSSI. I pick 3 with the highest value od RSSI.
First thing I would like to know if it is possible to search through the database to get all the records with the LEVEL value equal or close to 60, for instance 59,58,61.
Secondly, is there a way to query the database to return all the records with the same MAC addresses and RSSI values as from the 3 best scan result? If so, how would that query look like?
EDIT: Thanks for all the answers. What I'm trying to do now is to compare 3 scans with records stored in database with getRequiredData function. I would like to pass 2 parameters to this function, mac address and level and find records with same value for both parameters. The rawQuery seems to be fine, code is compiling but the app is crashing with the first scan. I cant find the cause of it, is it because my logic of getting these parameters is wrong or does it have something to do with query?
public Cursor getRequiredData(String mac, int level){
SQLiteDatabase db = this.getWritableDatabase();
Cursor res = db.rawQuery("SELECT BSSID, RSSI FROM TABLE_NAME WHERE BSSID =? AND RSSI=?", new String[] {mac, level});
return res;
}
scan part:
class WifiReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
sb = new StringBuilder();
Comparator<ScanResult> comparator = new Comparator<ScanResult>() {
#Override
public int compare(ScanResult o1, ScanResult o2) {
return (o1.level>o2.level ? -1 : (o1.level==o2.level ? 0 : 1));
}
};
lista = wifiManager.getScanResults();
Collections.sort(lista, comparator);
for (int i = 0; i < lista.size(); i++) {
scanResult = wifiManager.getScanResults().get(i);
sb.append(new Integer(i + 1).toString() + ". " + (lista.get(i)).SSID + " " + (lista.get(i)).BSSID + " " + (lista.get(i)).level + "\n");
boolean isInserted = myDb.insertData(lista.get(i).SSID.toString(), lista.get(i).BSSID.toString(), lista.get(i).level);
if (isInserted = true)
Toast.makeText(MainActivity.this, "Data inserted", Toast.LENGTH_LONG).show();
else
Toast.makeText(MainActivity.this, "Data not inserted", Toast.LENGTH_LONG).show();
}
for (int i=0; i<4; i++)
{
scanResult = wifiManager.getScanResults().get(i);
match = myDb.getRequiredData(lista.get(i).BSSID.toString(), lista.get(i).level);
}
Log.i("match values: ", DatabaseUtils.dumpCursorToString(match));
txt.setText(sb);
wifiManager.startScan();
}
}
Here is what match contains:
2018-12-10 16:36:26.334 13347-13347/com.example.maciek.wifiscann I/match values:: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor#e1a86d1
0 {
BSSID=f4:c5:ed:5c:s6:20
RSSI=-69
}
1 {
BSSID=f4:c5:ed:5c:s6:20
RSSI=-69
}
2 {
BSSID=f4:c5:ed:5c:s6:20
RSSI=-69
}
3 {
BSSID=f4:c5:ed:5c:s6:20
RSSI=-69
}
4 {
BSSID=f4:c5:ed:5c:s6:20
RSSI=-69
}
5 {
BSSID=f4:c5:ed:5c:s6:20
RSSI=-69
}
<<<<<
To get the 3 rows with the closest values to 60 in column LEVEL:
SELECT * FROM tablename ORDER BY ABS(LEVEL - 60), LEVEL LIMIT 3
For the 2nd part of your question, you should provide sample data of the table. Edit:
From the sample data that you posted I don't see a column RSSI, but if it exists in the table then the SELECT statement is ok.
Change the 2nd parameter of rawQuery() to:
new String[] {mac, String.valueOf(level)}
because level is int.
In onReceive() you use myDb. I don't know how you initialize it.
If the app crashes you must copy the log, the part that identifies the problem and post it.
First thing I would like to know if it is possible to search through
the database to get all the records with the LEVEL value equal or
close to 60, for instance 59,58,61.
SELECT * FROM your_table WHERE level BETWEEN 59 AND 61;
where your_table is the respective table name.
Note if levels are negative (as per example data) then BETWEEN requires the lowest value first so it would be BETWEEN -61 AND -59.
Secondly, is there a way to query the database to return all the
records with the same MAC addresses and RSSI values as from the 3 best
scan result? If so, how would that query look like?
SELECT * FROM your_table WHERE your_mac_address_column = 'the_mac_address_value' AND RSSI = 'the_rssi_value' ORDER BY LEVEL DESC LIMIT 3
Note the above assumes that the MAC address is stored in a column (if NOT then cannot be done unless the mac address can be correlated to a column).
Assumes best LEVEL is lowest so -1 is better than -60 (if not then use ASC instead of DESC)
Again your_table, your_mac_address_column, the_mac_address_value and the_rssi_value would be replaced accordingly with actual values (note that strings should be in single quotes).

Use names of named ranges in formula in Epplus

I'm using Epplus in a .NET Core application and I'd like to use names of named ranges in formulas. The issue is that when I reference a named range it takes the value of the first cell in range and don't iterate through rows:
var data = new List<object[]>()
{
new object[] {"Hours", "Price", "Total"},
new object[] {0.5, 10, 0},
new object[] {2, 100, 0},
new object[] {3, 20, 0}
};
using (var package = new ExcelPackage())
{
var sheet = package.Workbook.Worksheets.Add("Variables");
sheet.Cells["A1"].LoadFromArrays(data);
sheet.Names.Add("Hours", sheet.Cells["A2:A4"]);
sheet.Names.Add("Price", sheet.Cells["B2:B4"]);
sheet.Names.Add("Total", sheet.Cells["C2:C4"]);
sheet.Names["Total"].Formula = "Hours * Price";
package.Workbook.Calculate();
for (int i = 1; i <= sheet.Dimension.End.Row; i++)
{
Console.WriteLine($"{sheet.Cells[i, 1].Value}\t{sheet.Cells[i, 2].Value}\t{sheet.Cells[i, 3].Value}");
}
}
This code writes the following to the console:
Hours Price Total
0,5 10 5
2 100 5
3 20 5
But actually I'd like to get this:
Hours Price Total
0,5 10 5
2 100 200
3 20 60
Is it possible to somehow do that using names as arguments instead of cell addresses, like "Hours * Price"?
EPPlus Calculate() doesn't seem to support named ranges in formulas. But if your console output is only for test, the formulas are calculated correctly if you save the file and open it. I.e. if you add using (var package = new ExcelPackage(new FileInfo(someFileName))) and package.Save(); inside the using statement.
The console output would be correct if your formula uses the cell addresses (but guessing that's not good enough for you):
for (int col = 2; col < 5; col++)
sheet.Cells[$"C{col}"].Formula = $"A{col} * B{col}";

System.IndexOutOfRangeException when trying to save results from a stored proecedure

I have a SQL Server stored procedures with this output:
unit_id on_date notes type_code type_order status (No column name) (No column name)
3 2016-12-08 00:00:00.000 AVL -1 D NULL 16
3 2016-12-08 00:00:00.000 RSU 1 D 3 2
3 2016-12-08 00:00:00.000 TOW 2 D 6 5
.......etc
What I am trying to do it get these rows a columns to I can display them in a grid (spreadsheet like) view, and use them as variables in a bar graph.
I've tried the code (in my controller)
var model = new List<ResultsModel>();
SqlCommand command3 = new SqlCommand("dbo.pr_name");
command3.Parameters.Add(new SqlParameter { ParameterName = "#from", SqlDbType = System.Data.SqlDbType.DateTime, Value = NowDate });
command3.Parameters.Add(new SqlParameter { ParameterName = "#to", SqlDbType = System.Data.SqlDbType.DateTime, Value = "2017-09-21 00:00:00" });
command3.Parameters.Add(new SqlParameter { ParameterName = "#method", SqlDbType = System.Data.SqlDbType.Int, Value = 3 });
command3.CommandType = System.Data.CommandType.StoredProcedure;
using (var SPOutput3 = command.ExecuteReader())
{
model.Add(new ResultsModel()
{
unit_id = (Int32)SPOutput3["unit_id"],
on_date = (DateTimeOffset)SPOutput3["on_date"],
notes = SPOutput3["notes"].ToString(),
type_code = (string)SPOutput3["type_code"]
// other properties
});
return View(model);
}
and in my view
#*#foreach (var item in Model)
{
<tr>
<td>#item.on_date</td>
<td>#item.type_code</td>
</tr>
}*#
The code breaks at the line:
unit_id = (Int32)SPOutput3["unit_id"],
with an error System.IndexOutOfRangeException.
If I comment out that line, the error moves onto the next one etc.
The advise was after is: is the error telling me that there is no columns called unit_id in the output received? even thought the output from the SSMS shows it?
.. and what I can do to fix this?
Also....if the column has no name, how can I assign it ..like unit_id, on_date etc ?
Thanks
Well, first off, you need to call Read in a while loop:
using (var SPOutput3 = command.ExecuteReader())
{
while (SPOutput3.Read())
{
...
}
}
Then, inside the while loop, you're dealing with an individual row. So you can do:
while (SPOutput3.Read())
{
var unit_id = SPOutput3["unit_id"] as int?;
}
You want to use as rather than a direct cast here so you can stave off potential issues if bad data is returned or the type isn't what you think it is. If you need a non-nullable value, then you can simply use the null coalesce operator to provide a default:
SPOutput3["unit_id"] as int? ?? 0;

Insert SQL statement is not working after checking against quantities from tables in database

I created a sale table which Insert function does not work properly. It shows the error message
Must declare the scalar variable "#iProductID"
at the statement
using (var sdRead = cmdOrder.ExecuteReader())
I am really stuck here. I also want to know how I can achieve for inserting SaleID with auto-increment without with any input field at the form. Every time I insert a new record, SaleID should be auto-generated and saved in the database.
My code below work like this. I checked available stocks from my Product table. If quantity order is greater than quantity from Product table, show error message.
Otherwise, proceed to inserting order information into Sale table. Any help is appreciated.
private void btnOrder_Click(object sender, EventArgs e)
{
int iQuantityDB;
int iCustomerID = Convert.ToInt32(txtCustomerID.Text);
int iProductID = Convert.ToInt32(txtProductID.Text);
decimal dPrice = Convert.ToDecimal(txtPrice.Text);
int iQuantity = Convert.ToInt32(txtQuantity.Text);
decimal dSubtotal = Convert.ToDecimal(txtSubTotal.Text);
decimal dGST = Convert.ToDecimal(txtGST.Text);
decimal dTotal = Convert.ToDecimal(txtTotal.Text);
string strConnectionString = #"Data Source = KK\SQLEXPRESS; Integrated Security = SSPI; Initial Catalog = JeanDB; MultipleActiveResultSets=True;";
using (var sqlconn = new SqlConnection(strConnectionString))
{
sqlconn.Open();
string querySelectQuantity = #"Select Quantity from dbo.JeanProduct WHERE ProductID = #iProductID";
using (var cmdOrder = new SqlCommand(querySelectQuantity, sqlconn))
{
using (var sdRead = cmdOrder.ExecuteReader())
{
sdRead.Read();
iQuantityDB = Convert.ToInt32(sdRead["Quantity"]);
}
}
if (iQuantityDB > iQuantity)
{
string InsertQuery = #"INSERT INTO Sale(CustomerID, ProductID, Price, Quantity, Subtotal, GST, Total)VALUES(#iCustomerID, #iProductID, #dPrice, #iQuantity, #dSubtotal, #dGST, #Total)";
using (var InsertCMD = new SqlCommand(InsertQuery, sqlconn))
{
InsertCMD.Connection = sqlconn;
InsertCMD.Parameters.AddWithValue("#iCustomerID", iCustomerID);
InsertCMD.Parameters.AddWithValue("#iProdcutID", iProductID);
InsertCMD.Parameters.AddWithValue("#dPrice", dPrice);
InsertCMD.Parameters.AddWithValue("#iQuantity", iQuantity);
InsertCMD.Parameters.AddWithValue("#dSubtotal", dSubtotal);
InsertCMD.Parameters.AddWithValue("#dGST", dGST);
InsertCMD.Parameters.AddWithValue("#dTotal", dTotal);
InsertCMD.ExecuteNonQuery();
LoadDataonTable();
}
}
else
{
MessageBox.Show("no more stock");
}
sqlconn.Close();
}
}
At the line using (var sdRead = cmdOrder.ExecuteReader()) your SQL SELECT query is using a parameter - WHERE ProductID = #iProductID - but this hasn't been set. Hence the error message Must declare the scalar variable "#iProductID"
Just add cmdOrder.Parameters.AddWithValue("#iProductID", iProductID) between defining the SQL and executing it, and that should clear that problem.
Moving on to the next one - you're using AddWithValue("#dTotal" but it's #Total in the SQL :)

IDataReader Issue in .Net 1.1 and .Net 4.0 for Sybase DB

I have a sybase DB which fetches results of a query properly as below...
select
S.ipoInternalID,
clientAccount,
clientPrice,
clientAccountType,
interestOnLoan =
CASE WHEN useHIBOR = 1 then
ROUND(financingAmount * (fixedRate + spreadRate) *
I.noOfDaysForInterest/365/100,2)
ELSE
ROUND(financingAmount * (I.fundingRate+ spreadRate) *
I.noOfDaysForInterest/365/100,2) END,
useHIBORSTR =
CASE WHEN useHIBOR = 1 then
"LOCK-IN RATE + SPREAD"
ELSE
"COST OF FUNDING + SPREAD" END,
from subscription S, iPO I, allocation A
where
S.ipoInternalID = #ipoInternalID and
I.ipoInternalID = #ipoInternalID and
A.ipoInternalID = #ipoInternalID and
S.ccassID *= A.ccassID
order by S.ccassID
Notice the way interestOnLoan field is calculated above.
Now when I run this query in SQL Advantage tool, it runs fine and gives me calculated values for interestOnLoan. When I run this query using .Net 1.1 API that loads this query via OleDB it runs fine...
myCommand.CommandText = myQuery;
myAdapter.SelectCommand = myCommand;
int i = myAdapter.Fill(resultSet);
My resultset fills ok.
But when I execute the above code in .net 4.0, the resultset errors out as
"Value was either too large or too small for a Decimal."
The value it has issues with is the interestOnLoan because I also executed the command via IDataReader as below...
using (var dr = myCommand.ExecuteReader())
{
resultSet.Tables.Add(ConvertDataReaderToTableManually(dr));
}
private static DataTable ConvertDataReaderToTableManually(IDataReader dr) {
var dt = new DataTable();
var dtSchema = dr.GetSchemaTable();
var listCols = new List<DataColumn>();
if (dtSchema != null) {
foreach (DataRow drow in dtSchema.Rows) {
var columnName = Convert.ToString(drow["ColumnName"]);
var t = (Type) (drow["DataType"]);
var column = new DataColumn(columnName, t);
column.Unique = (bool) drow["IsUnique"];
column.AllowDBNull = (bool) drow["AllowDBNull"];
column.AutoIncrement = (bool) drow["IsAutoIncrement"];
listCols.Add(column);
dt.Columns.Add(column);
}
}
// Read rows from DataReader and populate the DataTable
int j = 0;
while (dr.Read()) {
j++;
var dataRow = dt.NewRow();
for (int i = 0; i < listCols.Count; i++) {
try {
dataRow[((DataColumn)listCols[i])] = dr[i];
} catch (Exception ex1) { }
}
dt.Rows.Add(dataRow);
}
return dt;
}
Here it errors out at the dataRow[((DataColumn)listCols[i])] = dr[i] where it has issues reading from dr[i];
When observed the ith column is nothing but interestOnLoan.
So somehow .Net 4.0 is not able to read this value. It can read other decimal values correctly such as clientPrice.
Why could this be happening....
Also I wanted to ask is there any way I can load the values in the DataReader as Double (instead of Decimal) by default?
I didnt get the reason why .NET 4.0 had issues ith the above query but when I changed the query as below it worked in both (.Net 1.1 and 4.0)
select
S.ipoInternalID,
clientAccount,
clientPrice,
clientAccountType,
interestOnLoan = ROUND(
(CASE WHEN useHIBOR = 1 THEN
((financingAmount*(fixedRate + spreadRate) * .noOfDaysForInterest)/365.0)
ELSE
((financingAmount*(I.fundingRate+spreadRate)*I.noOfDaysForInterest)/365.0)
END) / 100.0, 2),
useHIBORSTR =
CASE WHEN useHIBOR = 1 then
"LOCK-IN RATE + SPREAD"
ELSE
"COST OF FUNDING + SPREAD" END,
from subscription S, iPO I , allocation A
where
S.ipoInternalID = #ipoInternalID and
I.ipoInternalID = #ipoInternalID and
A.ipoInternalID = #ipoInternalID and
S.ccassID *= A.ccassID
order by S.ccassID