I have some SP that takes year and month:
Create PROCEDURE Report(
#targetYear int,
#targetMonth int
)
And querying it with:
select sum(col) where year(dateTime) = #targetYear and month(dateTime) = #targetMonth
Then I have the same thing for year only
Create PROCEDURE Report(
#targetYear int
)
and querying it like:
select sum(col) where year(dateTime) = #targetYear
Of course, the logic is more complicated than sum(col)
My question is, how can I write this SP so the logic is not repeated across the two SP, even if it means passing 0 for target month when I mean the whole year?
I like Joel's answer except his won't work if you pass in a zero as the month. You will want #targetMonth=0 for example:
SELECT sum(col)
WHERE year(dateTime) = #TargetYear AND
(#targetMonth = 0 OR month(dateTime) = #targetMonth)
SELECT sum(col)
FROM [yourtable]
WHERE year(dateTime) = #TargetYear
AND (#targetMonth < 0 OR month(dateTime) = #targetMonth)
The stored proc can have an optional parameter:
Create PROCEDURE Report( #targetYear int, #targetMonth int = null )
It can be called either with the parameter or not:
exec Report 2009
exec Report 2009, 2
Then in your logic check for a null:
SELECT sum(col)
FROM [yourtable]
WHERE (year(dateTime) = #TargetYear and #targetMonth = null)
OR (year(dateTime) = #TargetYear AND #targetMonth = month(dateTime))
or
SELECT sum(col)
FROM [yourtable]
WHERE year(dateTime) = #TargetYear
AND (#targetMonth = null OR year(dateTime) = #TargetYear)
Related
I want to create a master view combining all the tables passed as input to Snowflake Stored procedure. Please help on how the code can be framed for this.
create or replace procedure TEST_PROC("SRC_DB" VARCHAR(30),
"SRC_SCHEMA" VARCHAR(30), "TGT_DB" VARCHAR(30), "TGT_SCHEMA"
VARCHAR(30))
RETURNS varchar
LANGUAGE JAVASCRIPT
EXECUTE AS OWNER
as
$$
var result = '';
var tab = 'TABLE1,TABLE2'
var get_tables = `
with cte as(select value from table(SPLIT_TO_TABLE
(('${tab}'),','))
) select value from cte;`
var tables_name_master=snowflake.execute ({sqlText: get_tables});
var lcols_agg = '';
while(tables_name_master.next()){
var table_value = tables_name_master.getColumnValue(1);
var column_list = `
WITH cte2 as (select COLUMN_NAME , listagg(TABLE_NAME, ', ')
within group (order by COLUMN_NAME) A
from ${SRC_DB}.information_schema.COLUMNS
where TABLE_SCHEMA= '${SRC_SCHEMA}' and TABLE_NAME in (select
value from table(SPLIT_TO_TABLE (('${tab}'),',')))
group by COLUMN_NAME order by COLUMN_NAME
),
cte3 as (select 'x' x, COLUMN_NAME,iff(contains(A,'${table_value}'),COLUMN_NAME,CONCAT('NULL AS \"',COLUMN_NAME,'\"')) valuess from cte2 order by COLUMN_NAME
)select listagg(valuess,',') final FROM cte3 GROUP BY x
`;
var rs = snowflake.execute({ sqlText: column_list });
while(rs.next()){
lcols_agg += "SELECT " + rs.getColumnValue(1) + " FROM "+ SRC_DB+"."+SRC_SCHEMA+"."+tables_name_master.getColumnValue(1) + "\n" +"UNION " +"\n"
}
}
var count1 = 0 ;
count1 = lcols_agg.length
result = lcols_agg.substring(0,(count1-7));
const create_union_view = `
create or replace view abcd AS ${result}
;`
var view_create = snowflake.execute({ sqlText: create_union_view });
view_create.next()
return result
$$
;
call SP_TEST('ABC','DEF','PQR','STU');
THis generates my final view statement as
CREATE OR REPLACE VIEW ABCD AS
COL1,COL2,COL3
UNION
NULL AS "COL2",NULL AS "COL3",COL1
Now due to mismatch of order od columns in union the view is throwing error while we do select * from abcd, any way we can have the columns of both tables in same order or any other work around?
You need to ORDER BY the function listagg after the cte3 expression:
instead of
select listagg(valuess,',') final FROM cte3 GROUP BY x
change it to:
select listagg(valuess,',') within group (order by valuess) final FROM cte3 GROUP BY x
See if that helps to resolve the order issue.
Hi I am working on ASP.NET MVC project. This is my code :
string[] dt1;
for (int ic = 0; ic < dt.Count() - 1; ic++)
{
dt1 = new string[3];
dt1 = dt[ic].Split('|');
var date = Convert.ToDateTime(dt1[0].Substring(0, dt1[0].IndexOf("GMT")).Trim()).ToShortDateString();
var time = Convert.ToDateTime(dt1[0].Substring(0, dt1[0].IndexOf("GMT")).Trim()).ToShortTimeString();
//Calling Stroed Procedure
var schedulesp = orderdata.uspApp_InsertScheduler(Convert.ToInt32(outputresults), newid, Name, Convert.ToDateTime(date),time);
orderdata.SaveChanges();
}
Here I am looping through dates and times. I need to insert new records every time when i loop through date and time, that is 5 times I need to insert records.
But when I run above code, it is always saving only last results that is 5th result.
So how can i save all results into my database??
Update: This is the store procedure code :
ALTER PROCEDURE [dbo].[uspApp_InsertScheduler]
#ID int = 0,
#UserID int = 0,
#ReportName nvarchar(50) = 0,
#ScheduleStartDate date,
#SchedulestartTime nvarchar(10) = 0
AS
BEGIN
IF EXISTS (SELECT * FROM App_ReportsScheduler where id = #ID )
BEGIN
UPDATE App_ReportsScheduler
SET
reportName = #ReportName, schedule_StartDate = #ScheduleStartDate, schedule_starttime = #SchedulestartTime
where id = #ID
END
ELSE
BEGIN
INSERT INTO App_ReportsScheduler (ID, UserID, reportName,schedule_StartDate,schedule_starttime)
values ( #ID, #UserID,#ReportName,#ScheduleStartDate,#SchedulestartTime)
END
END
GO
Just put orderdata.SaveChanges(); out side of your loop.
I have an int[] and pass it as parameter to a procedure, when the method is called, no error is reported but nothing is updated in the database. There may be something wrong in my Add() since the Stored Procedure is written by another programmer. I cannot modify this stored procedure so everything must be solved in the Add().
FYI, the Add() takes an array of userIDs and a groupID as parameters, format them and run the stored procedure. The stored procedure inserts userIDs and groupID into the DB
For example:
if userIDs=[1,2,3] and groupID=4,
then I want the following data to be inserted into the DB
userID groupID
1 4
2 4
3 4
The stored procedure
USE [xyz]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spSaveSomething]
#groupID INT,
#userIDs TEXT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #handle INT
EXEC sp_xml_preparedocument #handle OUTPUT, #userIDs
DELETE FROM tbl1 WHERE i_GroupID = #groupID AND i_NetworkUserID IN (SELECT [ID] FROM OPENXML (#handle, 'ROOT/VAL', 1) WITH ([ID] INT))
INSERT INTO tbl1 (i_NetworkUserID, i_GroupID)
SELECT [ID], #groupID FROM OPENXML (#handle, 'ROOT/VAL', 1) WITH ([ID] INT)
EXEC sp_xml_removedocument #handle
END
The Add()
public void AddUsers(int[] UserIDs, int GroupID)
{
List<int> testList = new List<int>();
foreach (int id in UserIDs)
{
testList.Add(id);
}
XmlSerializer xs = new XmlSerializer(typeof(List<int>));
MemoryStream ms = new MemoryStream();
xs.Serialize(ms, testList);
string resultXML = UTF8Encoding.UTF8.GetString(ms.ToArray());
SqlParameter param1 = new SqlParameter("#userIDs", resultXML);
SqlParameter param2 = new SqlParameter("#groupID", GroupID);
context.Database.ExecuteSqlCommand("spSaveSomething #groupID, #userIDs",
param2, param1);
}
Convert the int[] into a string with Comma seprated and then in Stored Prodedure, us in ie
WHERE i_NetworkUserID in #UserIds
for me, your approach a little bit complicated.
I suggest to rewrite your procedure like
alter procedure [dbo].[spSaveSomething]
(
#groupID int,
#userIDs nvarchar(max)
)
as
begin
set nocount on
declare #tmp_Users table (ID int primary key)
insert into #tmp_Users
select T.C.value('.', 'int')
from #userIDs.nodes('List/ID') as T(C)
delete from tbl1
where
i_GroupID = #group_ID and
i_NetworkUserID in (select ID from #tmp_Users)
insert into tbl1 (i_NetworkUserID, i_GroupID)
select ID, #groupID
from #tmp_Users
-- actually it's strange - you deleting users and then inserting
-- may be you wanted to do this:
-- delete from tbl1
-- where
-- i_GroupID = #group_ID and
-- i_NetworkUserID not in (select ID from #tmp_Users)
-- insert into tbl1 (i_NetworkUserID, i_GroupID)
-- select U.ID, #groupID
-- from #tmp_Users as U
-- where
-- not exists (
-- select T.*
-- from tbl1 as T
-- where T.i_GroupID = #group_ID and T.i_NetworkUserID = U.ID
-- )
end
and your xml generation like
var userIDs = new List<int>() { 1, 2, 3 };
var userXML = new XElement("List", a.Select(x => new XElement("ID", x))).ToString();
// so userXML = "<List><ID>1</ID><ID>2</ID><ID>3</ID></List>"
hope that helps
I'm trying to create a stored procedure with default parameters, in my query I would do it like this:
DECLARE #mydate DATETIME
DECLARE #MT DATETIME
DECLARE #MY DATETIME
SELECT #mydate = GETDATE()
SELECT #MT = DATEPART(MONTH,#mydate)
SELECT #MY = DATEPART(YEAR,#mydate)
SELECT * FROM ....
In my stored proc I've tried it as follows but when I execute the stored proc it prompts me for parameter values:
USE ..
GO
..
ALTER PROCEDURE ...
(#mydate DATETIME
,#MT DATETIME
,#MY DATETIME)
AS
BEGIN ...
SELECT #mydate = GETDATE()
SELECT #MT = DATEPART(MONTH,#mydate)
SELECT #MY = DATEPART(YEAR,#mydate)
---
SELECT * FROM ....
How do I set the parameter values in the stored proc like I would in the query?
Try this..
I put only one parameter because the others depend on it.
First approach
USE ..
GO
..
ALTER PROCEDURE yourSP
(#mydateParam DATETIME)
AS
BEGIN ...
SELECT #mydate = #mydateParam
SELECT #MT = DATEPART(MONTH,#mydate)
SELECT #MY = DATEPART(YEAR,#mydate)
---
SELECT * FROM ....
To call the SP:
DECLARE #mydateParam DATETIME
SELECT #mydateParam = GETDATE()
execute yourSP #mydateParam
Second approach
ALTER Procedure yourSP
#mydateParam datetime = null
AS
BEGIN ...
IF #mydateParam is null
SET #mydateParam = getdate()
SELECT #mydate = #mydateParam
SELECT #MT = DATEPART(MONTH,#mydate)
SELECT #MY = DATEPART(YEAR,#mydate)
---
SELECT * FROM ....
To call the SP:
DECLARE #mydateParam DATETIME
SELECT #mydateParam = GETDATE()
execute yourSP #mydateParam
or
execute yourSP
I have a question about a performance of stored procedures in the ADS. I created a simple database with the following structure:
CREATE TABLE MainTable
(
Id INTEGER PRIMARY KEY,
Name VARCHAR(50),
Value INTEGER
);
CREATE UNIQUE INDEX MainTableName_UIX ON MainTable ( Name );
CREATE TABLE SubTable
(
Id INTEGER PRIMARY KEY,
MainId INTEGER,
Name VARCHAR(50),
Value INTEGER
);
CREATE INDEX SubTableMainId_UIX ON SubTable ( MainId );
CREATE UNIQUE INDEX SubTableName_UIX ON SubTable ( Name );
CREATE PROCEDURE CreateItems
(
MainName VARCHAR ( 20 ),
SubName VARCHAR ( 20 ),
MainValue INTEGER,
SubValue INTEGER,
MainId INTEGER OUTPUT,
SubId INTEGER OUTPUT
)
BEGIN
DECLARE #MainName VARCHAR ( 20 );
DECLARE #SubName VARCHAR ( 20 );
DECLARE #MainValue INTEGER;
DECLARE #SubValue INTEGER;
DECLARE #MainId INTEGER;
DECLARE #SubId INTEGER;
#MainName = (SELECT MainName FROM __input);
#SubName = (SELECT SubName FROM __input);
#MainValue = (SELECT MainValue FROM __input);
#SubValue = (SELECT SubValue FROM __input);
#MainId = (SELECT MAX(Id)+1 FROM MainTable);
#SubId = (SELECT MAX(Id)+1 FROM SubTable );
INSERT INTO MainTable (Id, Name, Value) VALUES (#MainId, #MainName, #MainValue);
INSERT INTO SubTable (Id, Name, MainId, Value) VALUES (#SubId, #SubName, #MainId, #SubValue);
INSERT INTO __output SELECT #MainId, #SubId FROM system.iota;
END;
CREATE PROCEDURE UpdateItems
(
MainName VARCHAR ( 20 ),
MainValue INTEGER,
SubValue INTEGER
)
BEGIN
DECLARE #MainName VARCHAR ( 20 );
DECLARE #MainValue INTEGER;
DECLARE #SubValue INTEGER;
DECLARE #MainId INTEGER;
#MainName = (SELECT MainName FROM __input);
#MainValue = (SELECT MainValue FROM __input);
#SubValue = (SELECT SubValue FROM __input);
#MainId = (SELECT TOP 1 Id FROM MainTable WHERE Name = #MainName);
UPDATE MainTable SET Value = #MainValue WHERE Id = #MainId;
UPDATE SubTable SET Value = #SubValue WHERE MainId = #MainId;
END;
CREATE PROCEDURE SelectItems
(
MainName VARCHAR ( 20 ),
CalculatedValue INTEGER OUTPUT
)
BEGIN
DECLARE #MainName VARCHAR ( 20 );
#MainName = (SELECT MainName FROM __input);
INSERT INTO __output SELECT m.Value * s.Value FROM MainTable m INNER JOIN SubTable s ON m.Id = s.MainId WHERE m.Name = #MainName;
END;
CREATE PROCEDURE DeleteItems
(
MainName VARCHAR ( 20 )
)
BEGIN
DECLARE #MainName VARCHAR ( 20 );
DECLARE #MainId INTEGER;
#MainName = (SELECT MainName FROM __input);
#MainId = (SELECT TOP 1 Id FROM MainTable WHERE Name = #MainName);
DELETE FROM SubTable WHERE MainId = #MainId;
DELETE FROM MainTable WHERE Id = #MainId;
END;
Actually, the problem I had - even so light stored procedures work very-very slow (about 50-150 ms) relatively to plain queries (0-5ms). To test the performance, I created a simple test (in F# using ADS ADO.NET provider):
open System;
open System.Data;
open System.Diagnostics;
open Advantage.Data.Provider;
let mainName = "main name #";
let subName = "sub name #";
// INSERT
let cmdTextScriptInsert = "
DECLARE #MainId INTEGER;
DECLARE #SubId INTEGER;
#MainId = (SELECT MAX(Id)+1 FROM MainTable);
#SubId = (SELECT MAX(Id)+1 FROM SubTable );
INSERT INTO MainTable (Id, Name, Value) VALUES (#MainId, :MainName, :MainValue);
INSERT INTO SubTable (Id, Name, MainId, Value) VALUES (#SubId, :SubName, #MainId, :SubValue);
SELECT #MainId, #SubId FROM system.iota;";
let cmdTextProcedureInsert = "CreateItems";
// UPDATE
let cmdTextScriptUpdate = "
DECLARE #MainId INTEGER;
#MainId = (SELECT TOP 1 Id FROM MainTable WHERE Name = :MainName);
UPDATE MainTable SET Value = :MainValue WHERE Id = #MainId;
UPDATE SubTable SET Value = :SubValue WHERE MainId = #MainId;";
let cmdTextProcedureUpdate = "UpdateItems";
// SELECT
let cmdTextScriptSelect = "
SELECT m.Value * s.Value FROM MainTable m INNER JOIN SubTable s ON m.Id = s.MainId WHERE m.Name = :MainName;";
let cmdTextProcedureSelect = "SelectItems";
// DELETE
let cmdTextScriptDelete = "
DECLARE #MainId INTEGER;
#MainId = (SELECT TOP 1 Id FROM MainTable WHERE Name = :MainName);
DELETE FROM SubTable WHERE MainId = #MainId;
DELETE FROM MainTable WHERE Id = #MainId;";
let cmdTextProcedureDelete = "DeleteItems";
let cnnStr = #"data source=D:\DB\test.add; ServerType=local; user id=adssys; password=***;";
let cnn = new AdsConnection(cnnStr);
try
cnn.Open();
let cmd = cnn.CreateCommand();
let parametrize ix prms =
cmd.Parameters.Clear();
let addParam = function
| "MainName" -> cmd.Parameters.Add(":MainName" , mainName + ix.ToString()) |> ignore;
| "SubName" -> cmd.Parameters.Add(":SubName" , subName + ix.ToString() ) |> ignore;
| "MainValue" -> cmd.Parameters.Add(":MainValue", ix * 3 ) |> ignore;
| "SubValue" -> cmd.Parameters.Add(":SubValue" , ix * 7 ) |> ignore;
| _ -> ()
prms |> List.iter addParam;
let runTest testData =
let (cmdType, cmdName, cmdText, cmdParams) = testData;
let toPrefix cmdType cmdName =
let prefix = match cmdType with
| CommandType.StoredProcedure -> "Procedure-"
| CommandType.Text -> "Script -"
| _ -> "Unknown -"
in prefix + cmdName;
let stopWatch = new Stopwatch();
let runStep ix prms =
parametrize ix prms;
stopWatch.Start();
cmd.ExecuteNonQuery() |> ignore;
stopWatch.Stop();
cmd.CommandText <- cmdText;
cmd.CommandType <- cmdType;
let startId = 1500;
let count = 10;
for id in startId .. startId+count do
runStep id cmdParams;
let elapsed = stopWatch.Elapsed;
Console.WriteLine("Test '{0}' - total: {1}; per call: {2}ms", toPrefix cmdType cmdName, elapsed, Convert.ToInt32(elapsed.TotalMilliseconds)/count);
let lst = [
(CommandType.Text, "Insert", cmdTextScriptInsert, ["MainName"; "SubName"; "MainValue"; "SubValue"]);
(CommandType.Text, "Update", cmdTextScriptUpdate, ["MainName"; "MainValue"; "SubValue"]);
(CommandType.Text, "Select", cmdTextScriptSelect, ["MainName"]);
(CommandType.Text, "Delete", cmdTextScriptDelete, ["MainName"])
(CommandType.StoredProcedure, "Insert", cmdTextProcedureInsert, ["MainName"; "SubName"; "MainValue"; "SubValue"]);
(CommandType.StoredProcedure, "Update", cmdTextProcedureUpdate, ["MainName"; "MainValue"; "SubValue"]);
(CommandType.StoredProcedure, "Select", cmdTextProcedureSelect, ["MainName"]);
(CommandType.StoredProcedure, "Delete", cmdTextProcedureDelete, ["MainName"])];
lst |> List.iter runTest;
finally
cnn.Close();
And I'm getting the following results:
Test 'Script -Insert' - total: 00:00:00.0292841; per call: 2ms
Test 'Script -Update' - total: 00:00:00.0056296; per call: 0ms
Test 'Script -Select' - total: 00:00:00.0051738; per call: 0ms
Test 'Script -Delete' - total: 00:00:00.0059258; per call: 0ms
Test 'Procedure-Insert' - total: 00:00:01.2567146; per call: 125ms
Test 'Procedure-Update' - total: 00:00:00.7442440; per call: 74ms
Test 'Procedure-Select' - total: 00:00:00.5120446; per call: 51ms
Test 'Procedure-Delete' - total: 00:00:01.0619165; per call: 106ms
The situation with the remote server is much better, but still a great gap between plaqin queries and stored procedures:
Test 'Script -Insert' - total: 00:00:00.0709299; per call: 7ms
Test 'Script -Update' - total: 00:00:00.0161777; per call: 1ms
Test 'Script -Select' - total: 00:00:00.0258113; per call: 2ms
Test 'Script -Delete' - total: 00:00:00.0166242; per call: 1ms
Test 'Procedure-Insert' - total: 00:00:00.5116138; per call: 51ms
Test 'Procedure-Update' - total: 00:00:00.3802251; per call: 38ms
Test 'Procedure-Select' - total: 00:00:00.1241245; per call: 12ms
Test 'Procedure-Delete' - total: 00:00:00.4336334; per call: 43ms
Is it any chance to improve the SP performance? Please advice.
ADO.NET driver version - 9.10.2.9
Server version - 9.10.0.9 (ANSI - GERMAN, OEM - GERMAN)
Thanks!
The Advantage v10 beta includes a variety of performance improvements directly targeting stored procedure performance. Here are some things to consider with the current shipping version, however:
In your CreateItems procedure it would likely be more efficient to replace
#MainName = (SELECT MainName FROM __input);
#SubName = (SELECT SubName FROM __input);
#MainValue = (SELECT MainValue FROM __input);
#SubValue = (SELECT SubValue FROM __input);
with the use of a single cursor to retrieve all parameters:
DECLARE input CURSOR;
OPEN input as SELECT * from __input;
FETCH input;
#MainName = input.MainName;
#SubName = input.SubName;
#MainValue = input.MainValue;
#SubValue = input.SubValue;
CLOSE input;
That will avoid 3 statement parse/semantic/optimize/execute operations just to retrieve the input parameters (I know, we really need to eliminate the __input table altogether).
The SelectItems procedure is rarely ever going to be as fast as a select from the client, especially in this case where it really isn't doing anything except abstracting a parameter value (which can easily be done on the client). Remember that because it is a JOIN, the SELECT to fill the __output table is going to be a static cursor (meaning an internal temporary file for the server to create and fill), but now in addition you have the __output table which is yet another temporary file for the server, plus you have additional overhead to populate this __output table with data that has already been place in the static cursor temp table, just for the sake of duplicating it (server could do a better job of detecting this and replacing __output with the existing static cursor reference, but it currently doesn't).
I will try to make some time to try your procedures on version 10. If you have the test tables you used in your testing feel free to zip them up and send them to Advantage#iAnywhere.com and put attn:JD in the subject.
There is one change that would help with the CreateItems procedure. Change the following two statements:
#MainId = (SELECT MAX(Id)+1 FROM MainTable);
#SubId = (SELECT MAX(Id)+1 FROM SubTable );
To this:
#MainId = (SELECT MAX(Id) FROM MainTable);
#MainId = #MainId + 1;
#SubId = (SELECT MAX(Id) FROM SubTable );
#SubId = #SubId + 1;
I looked at the query plan information (in Advantage Data Architect) for the first version of that statement. It looks like the optimizer does not break that MAX(id)+1 into the component pieces. The statement select max(id) from maintable can be optimized using the index on the ID field. It appears that max(id)+1 is not optimized. So making that change would be fairly significant particularly as the table grows.
Another thing that might help is to add a CACHE PREPARE ON; statement to the top of each script. This can help with certain procedures when running them multiple times.
Edit The Advantage v10 beta was released today. So I ran your CreateItems procedure with both v9.1 and the new beta version. I ran 1000 iterations against the remote server. The speed difference was significant:
v9.1: 101 seconds
v10 beta: 2.2 seconds
Note that I ran a version with the select max(id) change I described above. This testing was on my fairly old development PC.