Generalize messy sql tables to a dynamic approach - database

I'm/We currently working on a refactor of our database. We got the following tables currently(minimized)
storage1 {
id: int;
weight: float;
maxWeight: float;
minWeight: float
isEmpty: bit/boolean;
}
storage2 {
id: int;
isFull: bit/boolean;
temperature: float;
isEmpty: bit/boolean;
emptyTo: int;
}
with more state values.
Our approach would be a more dynamic one like:
storage {
id: int;
description: varchar;
location: int;
}
with the following table
StorageStates {
StorageId(Storage_FK): int;
Description: varchar;
value: sql_variant;
}
The goal is to create a dynamic storage table, so we dont have to create new columns for every new parameter and our table design is equal for every storage. We would match our storageLocation with the column Location. For every request we would join Storage on StorageState to access the states of that storage.
What do you think about this approach? Do we miss something? What about performance?

Related

Multiple Parameters issue in SQL Server Stored Procedure and Nest JS API

Stored Procedure:
ALTER Procedure [dbo].[API_Insert_Break]
#EmployeeId int,
#BreakTypeTitle nvarchar(200)=null,
#UpdationIP varchar(20)=null,
#Reason nvarchar(200)=null,
#BreakInIP nvarchar(50)=null,
#BreakOutIP nvarchar(50)=null
as begin
if (#BreakOutIP is null)
begin
INSERT INTO AttendanceBreaks(AttendanceId,
BreakTypeId,
StartAt,
BreakInIPAddress,
Reason,
CreationDate,
CreatedBy,
UpdationIP)
VALUES ((select top 1 Id from Attendances where EmployeeId=#EmployeeId order by Id desc),
(select BreakTypes.Id from BreakTypes where BreakTypes.Title=#BreakTypeTitle),
(SELECT convert(time(0),getutcDate())),
#BreakInIP,
#Reason,
getutcdate(),
#EmployeeId,
#UpdationIP);
end
else
begin
Update AttendanceBreaks set EndAt=(SELECT convert(time(0),getutcDate())),
DurationInMinutes=DATEDIFF(minute,StartAt,(SELECT convert(time(0),getutcDate()))),
BreakOutIPAddress=#BreakOutIP,
UpdationDate=getutcdate(),
UpdationBy=#EmployeeId,
UpdationIP=#UpdationIP
where Id=(select MAX(Id) from AttendanceBreaks where AttendanceId=(select MAX(Id) from Attendances where EmployeeId=#EmployeeId));
end
end
NestJS service code:
async breakStart(employeeId:number,breakTypeTitle:string,reason:string): Promise<any> {
var data = await this.sequelize.query(
'Exec API_Insert_Break :EmployeeId, :BreakTypeTitle, :UpdationIP, :Reason, :BreakInIP',
{
replacements: {
EmployeeId: employeeId,
BreakTypeTitle:breakTypeTitle,
UpdationIP:ip.address(),
Reason:reason,
BreakInIP:ip.address()},
type: sequelize.QueryTypes.INSERT
});
}
async breakEnd(employeeId:number): Promise<any> {
var data = await this.sequelize.query(
'Exec API_Insert_Break :EmployeeId, :BreakTypeTitle, :UpdationIP, :Reason, :BreakInIP, :BreakOutIP',
{
replacements: {
EmployeeId: employeeId,
UpdationIP:ip.address(),
BreakOutIP:ip.address()
},
type: sequelize.QueryTypes.INSERT
});
}
The problem is that for the else statement to run, BreakOutIP must not be null. But since BreakOutIP is in the last (declared as a parameter), some of the parameters in between are skipped and it jumps to the last parameter. So if I pass a value to BreakOutIP, that value is not passed to it and as a result, it still runs the first if statement (and not the else statement) which requires BreakOutIP to be null. Because I have heard that all the parameters passed in the stored procedure must be in the correct sequence and no value should be skipped or it will not run properly. So can someone please tell how to overcome this problem? I mean how to properly pass parameters so that all input values are read without any confliction?
I found the problem. Problem is that the parameters sequence of Nest JS code and Stored Procedure must be the same. No parameter must be skipped in the Nest JS code. So I passed null values to the remaining parameters and it worked fine.
async breakEnd(employeeId:number): Promise<any> {
var data = await this.sequelize.query(
'Exec API_Insert_Break :EmployeeId, :BreakTypeTitle, :UpdationIP, :Reason, :BreakInIP, :BreakOutIP',
{
replacements: {
EmployeeId: employeeId,
------> BreakTypeTitle:null,
UpdationIP:ip.address(),
------> Reason:null,
------> BreakInIP:null,
BreakOutIP:ip.address()
},
type: sequelize.QueryTypes.INSERT
});
}

ClassCastException when using slick and Sql Server stored procedure

I have a problem with slick. It looks like after using it scala is not a statically typed language anymore:)
I used slick to call a SqlServer stored procedure that should return some values (and it does when I call the procedure directly in my SQL client). Unfortunately as you can see below the result is that I have a val with type Option[ProcessUserRoleDto], with ProcessUserRoleDto being just an ordinary case class, that in fact hold value of Some(1) and the '1' is of type java.lang.Integer. How is this possible? Do you have any ideas how to resolve this problem?
case class ProcessUserRoleDto(
processUserRoleId: Int,
processUserId: Int,
processRoleId: Int,
dataCollectionId: Int,
recognizingInstanceId: Int,
processRoleName: String,
dataCollectionName: String,
recognizingInstanceName: String,
isActive: Boolean)
def assign(dto: ProcessUserRoleAssignDto): Future[Option[ProcessUserRoleDto]] = {
val db = implicitly[SQLServerDriver.backend.DatabaseDef]
val sql = sql"exec EVO.spProcessUserRoleAssignToRecognizingInstance ${dto.ProcessUser_ID}, ${dto.ProcessRole_ID}, ${dto.RecognizingInstance_ID}"
implicit val registerProcessUserResult = GetResult { r =>
ProcessUserRoleDto(r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<)
}
val resultFut: Future[Option[ProcessUserRoleDto]] = db
.run(sql.as[ProcessUserRoleDto])
.map(_.headOption)
val singleResult: Option[ProcessUserRoleDto] = Await.result(resultFut, 10 seconds)
println(s"singleResult = $singleResult")
// in runtime result was: Some(1)
// WTF ?!?
println(s"class inside Some() = ${singleResult.get.getClass.getCanonicalName}")
// in runtime result is: java.lang.Integer
println(s"Option[Class] = ${singleResult.map(_.getClass.getCanonicalName)}")
// here exception is thrown: java.lang.ClassCastException: java.lang.Integer cannot be cast to ProcessUserRoleDto
}
The stored procedure was executing an insert statement and then a select statement. I turns out that it confused the JDBC driver because the insert was returning the number of modified records and that adding "SET NOCOUNT ON" to the stored procedure stoped that (https://stackoverflow.com/a/4809815/1185138).
Still, slick quietly messed up types instead of reaturning a nice error message about that..

Receiving one out of few Result Sets in stored procedure

I have a stored procedure that works fine but it has inside it three "select"s.
The selects are not from an inner temporary table.
This is mainly the format of the procedure:
ALTER PROCEDURE [dbo].[STProce]
#param1 int,
#param2 int,
#param3 int,
#param4 int,
#param5 int
AS
select #param1 as p1, #param2 as p2, #param3 as p3
.
.
.
select #param4 as p4
.
.
.
select #param5 as p5
I'm executing the procedure from another procedure and need to catch it there.
I created a table and inserts into it the "exec" from the procedure, like that:
CREATE TABLE #stalledp
(
RowNumber INT,
fldid INT,
fldLastUpdated datetime,
fldCreationDate datetime,
fldName nvarchar(255),
fldPending nvarchar(255)
)
INSERT INTO #stalledp (RowNumber,fldid,fldLastUpdated,fldCreationDate,fldName,fldPending)
EXEC spDebuggerViews_GetStuckWorkflowInstances #workflowSpaceId='00000000-0000-0000-0000-000000000000',#pageNum=1,#pageSize=100000,#orderByColumn=N'fldid',#sortOrder=1,#workflowInstanceId=0,#stuckInstanceType=1,#createdDateFrom='1900-01-01 00:00:00',#createdDateTo='9999-01-01 23:59:59',#updatedDateFrom='1900-01-01 00:00:00',#updatedDateTo='9999-01-01 23:59:59'
Afterwards I receive this error:
Column name or number of supplied values does not match table definition.
The order and name of columns of the table is exactly like the procedure returns.
Is there a possibility to catch only one of the tables that the procedure returns and avoid the other? I cannot change the procedure at all.
I tried declaring a table the same fields as the first select of the procedure and I get an error says that
Thank you in advance!
If all of the result sets returned are of the same structure, then you can dump them to a temp table as you are trying to do. However, that only gets you so far because if the data in the fields cannot be used to determine which result set a particular row came from, then you just have all of the result sets with no way to filter out the ones you don't want.
The only way to interact with multiple result sets individually, regardless of them having the same or differing structures, is through app code (i.e. a client connection). And if you want to do this within the context of another query, then you need to use SQLCLR.
The C# code below shows a SQLCLR stored procedure that will execute a T-SQL stored procedure that returns 4 result sets. It skips the first 2 result sets and only returns the 3rd result set. This allows the SQLCLR stored procedure to be used in an INSERT...EXEC as desired.
The code for the T-SQL stored proc that is called by the following code is shown below the C# code block. The T-SQL test proc executes sp_who2 and only return a subset of the fields being returned by that proc, showing that you don't need to return the exact same result set that you are reading; it can be manipulated in transit.
C# SQLCLR proc:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public class TheProc
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void Get3rdResultSetFromGetStuckWorkflowInstances()
{
int _ResultSetsToSkip = 2; // we want the 3rd result set
SqlConnection _Connection = null;
SqlCommand _Command = null;
SqlDataReader _Reader = null;
try
{
_Connection = new SqlConnection("Context Connection = true;");
_Command = _Connection.CreateCommand();
_Command.CommandType = CommandType.StoredProcedure;
_Command.CommandText = "tempdb.dbo.MultiResultSetTest";
// (optional) add parameters (but don't use AddWithValue!)
// The SqlDataRecord will be used to define the result set structure
// and act as a container for each row to be returned
SqlDataRecord _ResultSet = new SqlDataRecord(
new SqlMetaData[]
{
new SqlMetaData("SPID", SqlDbType.Char, 5),
new SqlMetaData("Status", SqlDbType.NVarChar, 30),
new SqlMetaData("Login", SqlDbType.NVarChar, 128),
new SqlMetaData("HostName", SqlDbType.NVarChar, 128),
new SqlMetaData("BlkBy", SqlDbType.VarChar, 5),
new SqlMetaData("DBName", SqlDbType.NVarChar, 128)
});
SqlContext.Pipe.SendResultsStart(_ResultSet); // initialize result set
_Connection.Open();
_Reader = _Command.ExecuteReader();
// Skip a predefined number of result sets
for (int _Index = 0;
_Index < _ResultSetsToSkip && _Reader.NextResult();
_Index++) ;
// Container used to move 1 full row from the result set being read
// to the one being sent, sized to the number of fields being read
Object[] _TempRow = new Object[_Reader.FieldCount];
while (_Reader.Read())
{
_Reader.GetValues(_TempRow); // read all columns
_ResultSet.SetValues(_TempRow); // set all columns
SqlContext.Pipe.SendResultsRow(_ResultSet); // send row
}
}
catch
{
throw;
}
finally
{
if(SqlContext.Pipe.IsSendingResults)
{
SqlContext.Pipe.SendResultsEnd(); // close out result set being sent
}
if(_Reader != null && !_Reader.IsClosed)
{
_Reader.Dispose();
}
_Command.Dispose();
if (_Connection != null && _Connection.State != ConnectionState.Closed)
{
_Connection.Dispose();
}
}
return;
}
}
T-SQL test proc:
USE [tempdb]
SET ANSI_NULLS ON;
IF (OBJECT_ID('dbo.MultiResultSetTest') IS NOT NULL)
BEGIN
DROP PROCEDURE dbo.MultiResultSetTest;
END;
GO
CREATE PROCEDURE dbo.MultiResultSetTest
AS
SET NOCOUNT ON;
SELECT 1 AS [ResultSet], 'asa' AS [test];
SELECT 2 AS [ResultSet], NEWID() AS [SomeGUID], GETDATE() AS [RightNow];
EXEC sp_who2;
SELECT 4 AS [ResultSet], CONVERT(MONEY, 131.12) AS [CashYo];
GO
EXEC tempdb.dbo.MultiResultSetTest;
To do:
Adjust _ResultSetsToSkip as appropriate. If you only want the first result set, simply remove both _ResultSetsToSkip and the for loop.
Define _ResultSet as appropriate
Set _Command.CommandText to be "spDebuggerViews_GetStuckWorkflowInstances"
Create the necessary parameters via SqlParameter (i.e. #workflowSpaceId='00000000-0000-0000-0000-000000000000',#pageNum=1,#pageSize=100000,#orderByColumn=N'fldid',#sortOrder=1,#workflowInstanceId=0,#stuckInstanceType=1,#createdDateFrom='1900-01-01 00:00:00',#createdDateTo='9999-01-01 23:59:59',#updatedDateFrom='1900-01-01 00:00:00',#updatedDateTo='9999-01-01 23:59:59')
If needed, add input parameters to the SQLCLR proc so that they can be used to set the values of certain SqlParameters
Then use as follows:
INSERT INTO #stalledp
(RowNumber,fldid,fldLastUpdated,fldCreationDate,fldName,fldPending)
EXEC Get3rdResultSetFromGetStuckWorkflowInstances;
There is a way to get the first record set but the others, I'm afraid, you're out of luck.
EXEC sp_addlinkedserver #server = 'LOCALSERVER', #srvproduct = '',
#provider = 'SQLOLEDB', #datasrc = ##servername
SELECT * FROM OPENQUERY(LOCALSERVER, 'EXEC testproc2')
EDIT: If you only need to check the other result set for columns to be not null you could predefine the expected results sets like so:
EXEC testproc2 WITH RESULT SETS (
(a VARCHAR(MAX) NOT NULL, b VARCHAR(MAX) NOT NULL),
(a VARCHAR(MAX) NOT NULL)
);
If the query within the stored procedure returns null values a exception is raised at that point in procedure. This will only work on sql server 2012 and upwards though.

Passing Array to Oracle Function

I am passing an array to a PL/SQL package function. I am doing this to use this array in a query inside the function which has IN clause.
My declaration of package looks like :
create or replace
PACKAGE selected_pkg IS
TYPE NUM_ARRAY IS TABLE OF NUMBER;
FUNCTION get_selected_kml(
in_layer IN NUMBER,
in_id IN NUMBER,
in_feature_ids IN selected_pkg.NUM_ARRAY,
in_lx IN NUMBER,
in_ly IN NUMBER,
in_ux IN NUMBER,
in_uy IN NUMBER
)
RETURN CLOB;
END selected_pkg;
In my PL/SQL function I am firing a query like following
select a.id, a.geom from Table_FIELD a where a.id in (select * from table (in_feature_ids)) and sdo_filter(A.GEOM,mdsys.sdo_geometry(2003,4326,NULL,mdsys.sdo_elem_info_array(1,1003,3), mdsys.sdo_ordinate_array(0,57,2.8,59)),'querytype= window') ='TRUE'
The same query runs fine if I run it from anonymous block like
CREATE TYPE num_arr1 IS TABLE OF NUMBER;
declare
myarray num_arr1 := num_arr1(23466,13396,14596);
BEGIN
FOR i IN (select a.id, a.geom from Table_FIELD a where a.id in (select * from table (myarray)) and sdo_filter(A.GEOM,mdsys.sdo_geometry(2003,4326,NULL,mdsys.sdo_elem_info_array(1,1003,3), mdsys.sdo_ordinate_array(0,57,2.8,59)),'querytype= window') ='TRUE'
loop
dbms_output.put_line(i.id);
end loop;
end;
If I try to run it by calling function as below
--Running function from passing array for IDs
declare
result CLOB;
myarray selected_pkg.num_array := selected_pkg.num_array(23466,13396,14596);
begin
result:=SELECTED_PKG.get_selected_kml(3, 19, myarray, 0.0,57.0,2.8,59);
end;
I am getting error
ORA-00904: "IN_FEATURE_IDS": invalid identifier
Could someone please help me understand the cause of it?
Thanks,
Alan
You cannot query a type declared in plsql in a sql query, as the sql engine doesn't recognise it.
Your first example works because you have declared the type numarr1 in the database, whereas the type selected_pkg.num_array is declared in a package.
Good summary here
I can't quite recreate the error you're getting; the anonymous block doesn't refer to in_feature_ids, and the package ought to only report that if it doesn't recognise it on compilation rather than at runtime - unless you're using dynamic SQL. Without being able to see the function body I'm not sure how that's happening.
But you can't use a PL/SQL-defined type in an SQL statement. At some point the table(in_feature_ids) will error; I'm getting an ORA-21700 when I tried it, which is a new one for me, I'd expect ORA-22905. Whatever the error, you have to use a type defined at schema level, not within the package, so this will work (skipping the spatial stuff for brevity):
CREATE TYPE num_array IS TABLE OF NUMBER;
/
CREATE OR REPLACE PACKAGE selected_pkg IS
FUNCTION get_selected_kml(
in_layer IN NUMBER,
in_id IN NUMBER,
in_feature_ids IN NUM_ARRAY,
in_lx IN NUMBER,
in_ly IN NUMBER,
in_ux IN NUMBER,
in_uy IN NUMBER
) RETURN CLOB;
END selected_pkg;
/
CREATE OR REPLACE PACKAGE BODY selected_pkg IS
FUNCTION get_selected_kml(
in_layer IN NUMBER,
in_id IN NUMBER,
in_feature_ids IN NUM_ARRAY,
in_lx IN NUMBER,
in_ly IN NUMBER,
in_ux IN NUMBER,
in_uy IN NUMBER
) RETURN CLOB IS
BEGIN
FOR i IN (select * from table(in_feature_ids)) LOOP
DBMS_OUTPUT.PUT_LINE(i.column_value);
END LOOP;
RETURN null;
END get_selected_kml;
END selected_pkg;
/
... and calling that also using the schema-level type:
set serveroutput on
declare
result CLOB;
myarray num_array := num_array(23466,13396,14596);
begin
result:=SELECTED_PKG.get_selected_kml(3, 19, myarray, 0.0,57.0,2.8,59);
end;
/
23466
13396
14596
PL/SQL procedure successfully completed.
Also note that you have to use exactly the same type, not just one that looks the same, as discussed in a recent question. You wouldn't be able to call your function with a variable of num_arr1 type, for example; they look the same on the surface but to Oracle they are different and incompatible.

stored procedure returns varchar in LINQ

I would like to know if in SQL is it possible to return a varchar value from a stored procedure, most of the examples I have seen the return value is an int
Example within a proc
declare #ErrorMessage varchar(255) if #TestFlag = 0 set #ErrorMessage = 'Test'
return #ErrorMessage
calling on asp.net
Updated:
Error:
Conversion failed when converting the varchar value 'Development' to data type int.
using (DataContextDataContext dc = conn.GetContext())
{
string serverName = ""
var result = dc.spGetServerName(ref serverName);
return result.ToString();
}
No, you can't return anything but an int using the RETURN statement.
You'd need to use OUTPUT parameters instead. They are more flexible.
e.g.
CREATE PROCEDURE dbo.ExampleSproc
#OutParam VARCHAR(10) OUTPUT
AS
SET #OutParam = 'Test'
...
GO
Update:
For an example on how to retrieve the output param, see this article.
No. Please see: http://msdn.microsoft.com/en-us/library/ms174998.aspx

Resources