I am trying to write a stored procedure to search through text fields of a table as follows:
TABLE: [User]
[Id] BIGINT PRIMARY KEY, IDENTITY (1, 1)
[Name] NVARCHAR(100) NOT NULL
[Email] NVARCHAR(100) NOT NULL, UNIQUE
The production database has many columns and a very large data set. The point of this SP is to speed up searching as much as practically possible.
What I tried:
EntityFrameworkCore LINQ queries.
Generating SQL on-the-fly using ADO .NET.
The stored procedure below.
The SP has yeilded the best results so far but the results are not accurate.
TEST SCRIPT
USE [TestDatabase]
--DELETE FROM [User] -- Commented for your safety.
DBCC CHECKIDENT ('[User]', RESEED, 0)
INSERT INTO [User] ([Name], [Email]) VALUES ('Name 01', 'Email01#Email.com')
INSERT INTO [User] ([Name], [Email]) VALUES ('Name 02', 'Email02#Email.com')
INSERT INTO [User] ([Name], [Email]) VALUES ('Name 03', 'Email03#Email.com')
EXECUTE SpUserSearch 0
EXECUTE SpUserSearch 1
EXECUTE SpUserSearch 0, NULL, NULL
EXECUTE SpUserSearch 1, NULL, NULL
EXECUTE SpUserSearch 0, 'Name 01', '#'
EXECUTE SpUserSearch 1, 'Name 01', '#'
RESULTS:
The first 4 queries should have returned ALL rows.
Query 1: Expected Rows: 3, Returned Rows: 0.
Query 2: Expected Rows: 3, Returned Rows: 0.
Query 3: Expected Rows: 3, Returned Rows: 0.
Query 4: Expected Rows: 3, Returned Rows: 0.
Query 5: Expected Rows: 1, Returned Rows: 3.
Query 6: Expected Rows: 3, Returned Rows: 3.
STORED PROCEDURE:
CREATE OR ALTER PROCEDURE SpUserSearch
#Condition BIT = 0, -- AND=0, OR=1.
#Name NVARCHAR(100) = NULL,
#Email NVARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #UseName BIT
DECLARE #UseEmail BIT
IF ((#Name IS NULL) OR (LEN(#Name) = 0)) SET #UseName = 0 ELSE SET #UseName = 1
IF ((#Email IS NULL) OR (LEN(#Email) = 0)) SET #UseEmail = 0 ELSE SET #UseEmail = 1
IF (#Condition = 0)
SELECT [Id], [Name], [Email]
FROM [User]
WHERE
((#UseName = 1) OR ([Name] LIKE '%' + #Name + '%'))
AND
((#UseEmail = 1) OR ([Email] LIKE '%' + #Email + '%'))
ELSE
SELECT [Id], [Name], [Email]
FROM [User]
WHERE
((#UseName = 1) OR ([Name] LIKE '%' + #Name + '%'))
OR
((#UseEmail = 1) OR ([Email] LIKE '%' + #Email + '%'))
RETURN (##ROWCOUNT)
END
There are two questions here:
What am I doing wrong in the SP logic?
Is this the most performant way to condition on the WHERE clause? I am not sure if CURSORs apply in this context.
Any advice would be appreciated.
Your logic is wrong: you need #UseName = 0 and #UseEmail = 0 in the AND half of the procedure. You also need to swap (#UseName = 1) OR to (#UseName = 1) AND in the OR half.
Although it's hard to say what the intention would be for #Condition if only one search value is supplied: what if [Name] or [Email] in the row is null?
CREATE OR ALTER PROCEDURE SpUserSearch
#Condition BIT = 0, -- AND=0, OR=1.
#Name NVARCHAR(100) = NULL,
#Email NVARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #UseName BIT
DECLARE #UseEmail BIT
IF ((#Name IS NULL) OR (LEN(#Name) = 0)) SET #UseName = 0 ELSE SET #UseName = 1
IF ((#Email IS NULL) OR (LEN(#Email) = 0)) SET #UseEmail = 0 ELSE SET #UseEmail = 1
IF (#Condition = 0)
SELECT [Id], [Name], [Email]
FROM [User]
WHERE
((#UseName = 0) OR ([Name] LIKE '%' + #Name + '%'))
AND
((#UseEmail = 0) OR ([Email] LIKE '%' + #Email + '%'))
ELSE
SELECT [Id], [Name], [Email]
FROM [User]
WHERE
((#UseName = 1) AND ([Name] LIKE '%' + #Name + '%'))
OR
((#UseEmail = 1) AND ([Email] LIKE '%' + #Email + '%'))
RETURN (##ROWCOUNT)
END
Performance-wise: this is never going to be great, because of the leading wildcard.
You may also get parameter-sniffing problems, for which the solution is usually to build the query dynamically, as I show in an answer to your other question.
Question 1
I think the problem is around the OR predicates inside the parenthesis in each condition, I believe it should be an AND predicate like shown below:
CREATE OR ALTER PROCEDURE SpUserSearch
#Condition BIT = 0, -- AND=0, OR=1.
#Name NVARCHAR(100) = NULL,
#Email NVARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #UseName BIT
DECLARE #UseEmail BIT
IF ((#Name IS NULL) OR (LEN(#Name) = 0)) SET #UseName = 0 ELSE SET #UseName = 1
IF ((#Email IS NULL) OR (LEN(#Email) = 0)) SET #UseEmail = 0 ELSE SET #UseEmail = 1
IF (#Condition = 0)
SELECT [Id], [Name], [Email]
FROM [User]
WHERE
((#UseName = 1) AND ([Name] LIKE '%' + #Name + '%'))
AND
((#UseEmail = 1) AND ([Email] LIKE '%' + #Email + '%'))
ELSE
SELECT [Id], [Name], [Email]
FROM [User]
WHERE
((#UseName = 1) AND ([Name] LIKE '%' + #Name + '%'))
OR
((#UseEmail = 1) AND ([Email] LIKE '%' + #Email + '%'))
RETURN (##ROWCOUNT)
END
Question 2
I agree with Larnu's comment that, because you're using those wildcards, you probably can't work much on performance.
#UseName and #UseEmail are not necessary. you can simply null the variable if it's a whitespace, and then make use of ISNULL under the WHERE along with the #Condition as well. (no need for the IF/ELSE
Here is the procedure wearing a swimming goggles :) :
CREATE OR ALTER PROCEDURE SpUserSearch
#Condition BIT = 0, -- AND=0, OR=1.
#Name NVARCHAR(100) = NULL,
#Email NVARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
-- this would covers NULL and Whitespace
SET #Name = CASE WHEN #Name IS NOT NULL AND LEN(#Name) = 0 THEN NULL ELSE '%' + #Name + '%' END
SET #Email = CASE WHEN #Email IS NOT NULL AND LEN(#Email) = 0 THEN NULL ELSE '%' + #Email + '%' END
SELECT [Id], [Name], [Email]
FROM
[User]
WHERE
(
#Condition = 0
AND
(
[Name] LIKE ISNULL(#Name, [Name])
AND [Email] LIKE ISNULL(#Email, [Email])
)
)
OR
(
#Condition = 1
AND
(
[Name] LIKE ISNULL(#Name, [Name])
OR [Email] LIKE ISNULL(#Email, [Email])
)
)
RETURN (##ROWCOUNT)
END
Related
I want to use the return value from one stored procedure to another stored procedure. I was searching on the internet and try several solutions, but all of them are not working, I don't know what the mistake I make.
The stored procedure that I want to use its return value is:
CREATE PROCEDURE dbo.pro_ForeignKeyCheck
(#tableName VARCHAR(100),
#columnName VARCHAR(100),
#idValue INT)
AS BEGIN
SET NOCOUNT ON
DECLARE fksCursor CURSOR FAST_FORWARD FOR
SELECT
tc.table_name, ccu.column_name
FROM
information_schema.table_constraints tc
JOIN
information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name
JOIN
information_schema.referential_constraints rc ON tc.constraint_name = rc.constraint_name
JOIN
information_schema.table_constraints tc2 ON rc.unique_constraint_name = tc2.constraint_name
JOIN
information_schema.constraint_column_usage ccu2 ON tc2.constraint_name = ccu2.constraint_name
WHERE
tc.constraint_type = 'Foreign Key'
AND tc2.table_name = #tableName
AND ccu2.column_name = #columnName
ORDER BY
tc.table_name
DECLARE #fkTableName VARCHAR(100),
#fkColumnName VARCHAR(100),
#fkFound BIT,
#params NVARCHAR(100),
#sql NVARCHAR(500)
OPEN fksCursor
FETCH NEXT FROM fksCursor INTO #fkTableName, #fkColumnName
SET #fkFound = 0
SET #params = N'#fkFound BIT OUTPUT'
WHILE ##fetch_status = 0 AND COALESCE(#fkFound, 0) <> 1
BEGIN
SELECT #sql = 'set #fkFound = (select top 1 1 from [' + #fkTableName + '] where [' + #fkColumnName + '] = ' + cast(#idValue as varchar(10)) + ')'
PRINT #sql
EXEC sp_executesql #sql, #params, #fkFound OUTPUT
FETCH NEXT FROM fksCursor INTO #fkTableName, #fkColumnName
END
CLOSE fksCursor
DEALLOCATE fksCursor
SELECT COALESCE(#fkFound, 0)
RETURN 0
END
and this use to check if the primary key value used in all child tables, we call it like this
EXECUTE pro_ForeignKeyCheck 'tablename','columnName', 1
or
EXECUTE pro_ForeignKeyCheck #tablename = 'tablename', #columnName = 'columnName', #idValue = 1
and it will work, but I cannot use the return value in other stored procedure
CREATE PROCEDURE [dbo].[pro_Delete_acount]
#UserID int,
#Action NVARCHAR(10)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #count int , #ErrNo int , #ErrMsg varchar(2000), #exit int
set #exit=0
if #Action ='ADMIN'
begin
/*---------Call Store Procedure pro_ForeignKeyCheck to check if there is value are exit in child table*/
--EXEC #exit = pro_ForeignKeyCheck1 #tablename='tb_M_admin',#columnName='admin_id',#idValue= #UserID
--select #exit
--EXEC #exit = pro_ForeignKeyCheck #tablename='tb_M_admin',#columnName='admin_id',#idValue= 1
EXEC #exit = pro_ForeignKeyCheck 'tb_M_admin','admin_id', 0
--select #exit
select -2[Ret_Status],#exit[ErrNo],0[ErrMsg] -- test
end
end
Could anyone help me with that?
Thanks all
Add
#Status int OUTPUT
in the pro_ForeignKeyCheck so it starts with
CREATE PROCEDURE dbo.pro_ForeignKeyCheck1
#tableName VARCHAR(100),
#columnName VARCHAR(100),
#idValue int,
#Status int OUTPUT
and at the end of it did as follow
--select coalesce(#fkFound,0)
select #Status = coalesce(#fkFound,0)
--return 0
stop the last to line and add new one
In the other stored procedure, call it as follows
EXEC pro_ForeignKeyCheck1 'tb_M_admin','admin_id', 0 ,#exit output
select #exit
and now the return value will be used.
Thanks to all
EXEC #exit = pro_ForeignKeyCheck 'tb_M_admin','admin_id', 0
---
select coalesce(#fkFound,0)
return 0 --< this will be assigned to #exit
replace this code with
return IsNull(#fkFound, 0)
You may leave select for other purposes but it cannot affect RETURN value. So you may remove it either.
This is oversimplified example of call of one SP from another. I hope it will give you some ideas.
create procedure dbo.first_proc
#bd datetime,
#d int output
as
select #d= DATEDIFF(day,#bd,getdate())
go
create procedure dbo.sec_proc
#birthday datetime
as
declare #days int
exec dbo.first_proc #birthday, #days output
select 'you live '+cast(#days as varchar) + ' days' result
go
exec dbo.sec_proc '1959-09-17'
I need a little help with this. The stored procedure below doesn't seem to ever match exiting unique identifier
ALTER PROCEDURE [dbo].[spInsertUpdateThisStuff]
#Id uniqueidentifier OUTPUT,
#Content nvarchar(255)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #tAudit table (id uniqueidentifier)
IF EXISTS(SELECT * FROM [dbo].[myData] WHERE [ID] = #Id)
-- UPDATE
BEGIN
UPDATE [dbo].[myData]
SET [ID] = #ID,
[Content] = #Content
OUTPUT inserted.[ID] INTO #tAudit
WHERE [ID] = #Id
SELECT id FROM #tAudit
END
ELSE
BEGIN
-- INSERT
SET #ID = NEWID()
INSERT INTO [dbo].CBData ([ID], [Content])
OUTPUT inserted.[ID] INTO #tAudit
VALUES(#Id, #Content)
SELECT id FROM #tAudit
END;
SET #ID = (SELECT id FROM #tAudit);
END
the C#
cmd.Parameters.Add("#ID", SqlDbType.UniqueIdentifier).Value = (currentRecord.ID == null) ? Guid.Empty : currentRecord.ID;
cmd.Parameters["#ID"].Direction = ParameterDirection.Output;
cmd.ExecuteNonQuery();
currentRecord.ID = Guid.Parse(cmd.Parameters["#ID"].Value.ToString());
It seems the first IF statement does not ever become true, but if test (SELECT * FROM [dbo].[myData] WHERE [ID] = #Id) with a the matching UID it comes back with data.
This statement is problematic,,
DECLARE #tAudit table (id uniqueidentifier)
IF EXISTS(SELECT * FROM [dbo].[myData] WHERE [ID] = #Id)
-- UPDATE
BEGIN
#id is an Output Parameter and is null by default and you are trying to check that..
basically NEWID() won't be duplicated,so update part is redundant
How are you declaring the parameter in your code? Is it ParameterDirection.Output?
Try changing it to ParameterDirection.InputOutput. Your SQL looks okay. And you can pass an input value to a parameter declared as OUTPUT in your stored procedure. So maybe it's just that ParameterDirection.
You can also change this
IF EXISTS(SELECT * FROM [dbo].[myData] WHERE [ID] = #Id)
to
IF #Id IS NOT NULL AND EXISTS(SELECT * FROM [dbo].[myData] WHERE [ID] = #Id)
If your ID column isn't nullable then it's going to work the same either way. But this is a little more explicit, showing that it's recognized that #Id might be null because it's an OUTPUT parameter.
This is my fix.
This is my new stored procedure
ALTER PROCEDURE [dbo].[spInsertUpdatemyData]
#ID uniqueidentifier,
#IDOut uniqueidentifier OUTPUT,
#CONTENT nvarchar(255)
AS
BEGIN
--SET NOCOUNT ON;
DECLARE #tAudit table (outputID uniqueidentifier)
IF EXISTS(SELECT * FROM [dbo].[myData] WHERE [ID] = #ID)
-- UPDATE
BEGIN
UPDATE [dbo].[CBData]
SET [ID] = #ID,
[Content] = #Content
OUTPUT inserted.[ID] INTO #tAudit
WHERE [ID] = #ID
SELECT outputID FROM #tAudit;
END
ELSE
BEGIN
-- INSERT
INSERT INTO [dbo].myData
([ID],[Content])
OUTPUT inserted.[ID] INTO #tAudit
VALUES(NEWID(),#Content);
SELECT outputID FROM #tAudit
END;
set #IDOut = (SELECT outputID FROM #tAudit);
END
and the relative C#
//Add Parameter for output to sql command then Change Direction of parameter
cmd.Parameters.Add("#IDOut", SqlDbType.UniqueIdentifier).Value = Guid.Empty ;
cmd.Parameters["#IDOut"].Direction = ParameterDirection.InputOutput;
cmd.ExecuteNonQuery();
currentRecord.ID = Guid.Parse(cmd.Parameters["#IDOut"].Value.ToString());
cmd.Transaction.Commit();
I have this Linq query:
IQueryable<SPR> query = db.SPRs;
if (!string.IsNullOrEmpty(search.accountNumber))
{
query = query.Where(b => b.CustomerAccountNumber.Contains(search.accountNumber));
}
if (!string.IsNullOrEmpty(search.accountName))
{
query = query.Where(b => b.CustomerNumber.Contains(search.accountName));
}
if (!string.IsNullOrEmpty(search.submittedBy))
{
query = query.Where(b => b.SubmittedBy.Contains(search.submittedBy));
}
if (!string.IsNullOrEmpty(search.smName))
{
query = query.Where(b => b.SMUserName == search.smName);
}
var result = query.ToList();
I am just appending the where clause if conditions are true. The issue is that it is not just adding a And in the generated SQL where clause like I want it to.
Here is the generated SQL if I have the SubmittedBy and SMUserName filled with data.
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[CustomerNumber] AS [CustomerNumber],
[Extent1].[CustomerAccountNumber] AS [CustomerAccountNumber],
[Extent1].[SMUserName] AS [SMUserName],
[Extent1].[SubmittedBy] AS [SubmittedBy],
[Extent1].[Notes] AS [Notes]
FROM
[dbo].[SPRs] AS [Extent1]
WHERE
([Extent1].[SubmittedBy] LIKE #p__linq__0 ESCAPE N'~')
AND (([Extent1].[SMUserName] = #p__linq__1) OR (([Extent1].[SMUserName] IS NULL)
AND (#p__linq__1 IS NULL)))
Not sure how this last line OR (([Extent1].[SMUserName] IS NULL) AND (#p__linq__1 IS NULL))) is getting added which is messing the query up.
Can someone please tell me how I can have just AND in the eventual query when the if conditions are satisfied?
Since you are working with sql server a more performance efficient and sleek way would be to handle the optional parameters inside a stored procedure and make use of Dynamic sql with sp_executesql to benefit from Parameterised Execution Plans.
CREATE PROCEDURE getSPR
#SubmittedBy Varchar(100) = NULL --<--- Use appropriate datatypes
,#CustomerAccountNumber Varchar(100) = NULL
,#CustomerNumber Varchar(100) = NULL
,#SMUserName Varchar(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
Declare #Sql Nvarchar(max);
SET #Sql = N'SELECT [Id]
,[CustomerNumber]
,[CustomerAccountNumber]
,[SMUserName]
,[SubmittedBy]
,[Notes]
FROM [dbo].[SPRs]
WHERE 1 = 1 '
+ CASE WHEN #SubmittedBy IS NOT NULL THEN
N' AND [SubmittedBy] LIKE ''%'' + #SubmittedBy + ''%''' ELSE N' ' END
+ CASE WHEN #CustomerAccountNumber IS NOT NULL THEN
N' AND [CustomerAccountNumber] LIKE ''%'' + #CustomerAccountNumber + ''%''' ELSE N' ' END
+ CASE WHEN #CustomerNumber IS NOT NULL THEN
N' AND [CustomerNumber] LIKE ''%'' + #CustomerNumber + ''%''' ELSE N' ' END
+ CASE WHEN #SMUserName IS NOT NULL THEN
N' AND [SMUserName] = #SMUserName ' ELSE N' ' END
Exec sp_executesql #sql
,N' #SubmittedBy Varchar(100),#CustomerAccountNumber Varchar(100)
,#CustomerNumber Varchar(100), #SMUserName Varchar(100)'
,#SubmittedBy
,#CustomerAccountNumber
,#CustomerNumber
,#SMUserName
END
I am using a T-sql (Sql server 2008) stored procedure which returns a custmized values based on many business conditions. (This must be done on database because i have a generic call to many stored procedures from C# code).
My issue here is with the line-break. I use in my code Char(13);Char(13) + char(10); char(10)... but it doesn't work.
NB: If i test an INSERT statment it works but in the SELECT statment no line break ,it generates a White space (Done on a simple query).
Bellow my code
ALTER PROCEDURE [dbo].[getDebtorAddress] (
#idFolder int
)
AS
BEGIN
DECLARE
#type bit ,
#firstName varchar(100) ,
#familyName varchar(100) ,
#raisonSocial varchar(100),
#jeuneFille varchar(100) ,
#saleName varchar (100) ,
#enseigne varchar (100) ,
#sigle varchar (100) ,
#address varchar (max),
#lieuDit varchar(100) ,
#postalCode varchar (100),
#city varchar (100),
#country varchar (100),
#finalAddress nvarchar(max)
SET NOCOUNT ON;
SELECT DISTINCT
#firstName = Actor.M_EN_NOM,
#familyName = Actor.M_EN_PRENOM1,
#raisonSocial = Actor.M_EN_RAISONSOCIALE,
#jeuneFille = Actor.M_EN_NOMJEUNEFILLE,
#saleName = Actor.M_EN_NOMCOMMERCIAL,
#enseigne = Actor.M_EN_ENSEIGNE,
#sigle = Actor.M_EN_SIGLE,
#address = ActorCP.M_PR_CP_ADRESSE,
#lieuDit = ActorCP.M_PR_CP_LIEUDIT,
#postalCode = PostalCode.C_PA_CP_CODEPOSTALE,
#city = PostalCode.C_PA_CP_VILLE,
#country = Country.C_PA_PA_LIBELLE,
#type = Actor.M_EN_TYPE
FROM M_EN Actor
LEFT OUTER JOIN M_DE Debtor ON Actor.M_EN_ID =Debtor.M_EN_ID
LEFT OUTER JOIN M_DO Folder ON Debtor.M_DE_ID= Folder.M_DE_ID
LEFT OUTER JOIN M_PR_CP ActorCP ON Actor.M_EN_ID = ActorCP.M_EN_ID
LEFT OUTER JOIN C_PA_CP PostalCode ON ActorCP.C_PA_CP_ID = PostalCode.C_PA_CP_ID
LEFT OUTER JOIN C_PA_PA Country ON ActorCP.C_PA_PA_ID = Country.C_PA_PA_ID
WHERE Folder.M_DO_ID = #idFolder
if(#type=0)
begin
if (#firstName is not Null and #firstName !='')
set #finalAddress= #firstName
if(#familyName is not Null and #familyName != '')
set #finalAddress = #finalAddress + ' ' + #familyName +CHAR(13)
if(#jeuneFille is not null and #jeuneFille !='' )
set #finalAddress=#finalAddress + ' ' + #jeuneFille + CHAR(13)
if(#address is not null and #address != '')
set #finalAddress=#finalAddress + REPLACE(#address,'|',char(13))
if(#lieuDit is not null and #lieuDit != '')
set #finalAddress = #finalAddress + #lieuDit + CHAR(13)
if(#postalCode is not null and #postalCode != '')
set #finalAddress = #finalAddress + #postalCode + CHAR(13)
if(#country is not null and #country != '')
set #finalAddress = #finalAddress + #country + CHAR(13)
end
else
begin
if (#raisonSocial is Null or #raisonSocial = '')
Begin
if(#firstName is not null and #firstName != '')
set #finalAddress = #finalAddress + #firstName + CHAR(13)
if(#familyName is not null and #familyName != '')
set #finalAddress = #finalAddress + #familyName + CHAR(13)
end
else
set #finalAddress = #finalAddress + #raisonSocial + CHAR(13)
if(#jeuneFille is not null and #jeuneFille !='' )
set #finalAddress=CHAR(13) + #finalAddress + ' ' + #jeuneFille + CHAR(13)
if(#saleName is not null and #saleName != '')
set #finalAddress=#finalAddress + #saleName + CHAR(13)
if(#enseigne is not null and #enseigne != '')
set #finalAddress=#finalAddress + #enseigne + CHAR(13)
if(#sigle is not null and #sigle != '')
set #finalAddress=#finalAddress + #sigle + CHAR(13)
if(#address is not null and #address != '')
set #finalAddress=#finalAddress + REPLACE(#address,'|',char(13))
end
Create Table #TempAddress
(
TempAddress_ID int ,
adresse nvarchar(max)
)
INSERT INTO #TempAddress (#TempAddress.adresse) VALUES(#finalAddress)
SELECT #TempAddress.adresse from #TempAddress
DROP TABLE #TempAddress
END
the current result is
aaaaa bbbbb ccccc dddddd
but my expected result is
aaaaa
bbbbb
ccccc
ddddd
Any suggestion or help ?
Thanks
As I explained in my comment, on Windows a newline is CHAR(13)+CHAR(10), but whether that is rendered as a newline depends on how you print it.
in the SELECT statment no line break ,it generates a White space [...] does not work in a simple SELECT
"simple SELECT" does not explain what you're trying to do. Please rember that you are the only one here that can see your screen. You need to be explicit if you want others to understand your problem.
If you mean that the grid view in SQL Server Management Studio doesn't display newlines, you're correct: it doesn't, until you click "Results to Text" button in the toolbar as suggested in the first comment.
Your T-SQL is working fine it is inserting char(13), however depending on your client you need to add a extra char(10).
char(13) + char(10)
It is probably a better idea to do the formatting like this at client side and not in your sql query.
You can also use this syntax:
......note this last single quote after line 2...
| -> press enter and begin a new line with another...
SELECT 'use ' + DB_NAME() + ';' + '
' + 'CREATE USER ' + users.name + ' for ' + users.name + ';'
| -> continued quote in line 3.
This will produce the following output:
use MyDatabase;
CREATE USER User1 for User1;
I've seen this:
How can you dynamically select a table with entity framework 4.x?
However I cannot use a base class in my situation. I am looking to create a user group and week sensitive table for auditing, so I cannot know what these will be called prior to them being created at runtime.
So if I login first week of the year, it would be:
Group1_01_2014
Is this possible? I've tried to simply change the database name at creation, but I get the standard exception about database migrations.
Thanks.
Edit: Before someone says splitting the database is silly, know that I'm auditing ALOT. The idea is to be able to audit most transactions, store them as long as needed, without severely affecting performance.
Edit2: That said if someone has a better solution I'm all ears.
Solution
Ended up using a stored procedure:.
CREATE FUNCTION GetAuditTableName
(
#db_code CHAR (4)
)
RETURNS CHAR (12)
BEGIN
DECLARE #wkNo CHAR (2)
DECLARE #year CHAR (4)
SELECT #wkNo = REPLACE(STR(DATEPART(WEEK, GETDATE()), 2), SPACE(1), '0')
SELECT #year = STR(DATEPART(YEAR, GETDATE()), 4)
RETURN #db_code + '_' + #year + '_' + #wkNo
END
GO
CREATE PROCEDURE [dbo].[Audit_Insert]
#audi_id_first NVARCHAR (20),
#audi_id_second NVARCHAR (20),
#audi_by NVARCHAR (100),
#audi_on DATETIME,
#audi_details XML,
#tabn_code CHAR (4),
#audt_id INT,
#audi_db_code CHAR (4)
AS
BEGIN
DECLARE #tableName CHAR (12)
SET #tableName = [dbo].GetAuditTableName(#audi_db_code)
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name=#tableName and xtype='U')
BEGIN
DECLARE #createSql NVARCHAR(MAX);
SELECT #createSql = 'CREATE TABLE [dbo].' + QUOTENAME(#tableName) + ' (
[audi_id] INT IDENTITY (1, 1) NOT NULL,
[audi_id_first] NVARCHAR (20) NULL,
[audi_id_second] NVARCHAR (20) NULL,
[audi_by] NVARCHAR (100) NOT NULL,
[audi_on] DATETIME NOT NULL,
[audi_details] XML NULL,
[tabn_code] CHAR (4) NULL,
[audt_id] INT NOT NULL,
CONSTRAINT [PK_dbo.' + #tableName + '] PRIMARY KEY CLUSTERED ([audi_id] ASC),
CONSTRAINT [FK_dbo.' + #tableName + '_dbo.tabn_table_name_tabn_code] FOREIGN KEY ([tabn_code]) REFERENCES [dbo].[tabn_table_name] ([tabn_code]),
CONSTRAINT [FK_dbo.' + #tableName + '_dbo.audt_audit_type_audt_id] FOREIGN KEY ([audt_id]) REFERENCES [dbo].[audt_audit_type] ([audt_id])
)'
EXEC sp_executesql #createSql
END
DECLARE #insertSql NVARCHAR(MAX);
SELECT #insertSql = N'INSERT [dbo].' + QUOTENAME(#tableName) + ' ([audi_id_first], [audi_id_second], [audi_by], [audi_on], [audi_details], [tabn_code], [audt_id])
VALUES (#audi_id_first, #audi_id_second, #audi_by, #audi_on, #audi_details, #tabn_code, #audt_id)'
EXEC sp_executesql #insertSql, N'#audi_id_first NVARCHAR (20), #audi_id_second NVARCHAR (20), #audi_by NVARCHAR (100), #audi_on DATETIME, #audi_details XML, #tabn_code CHAR (4), #audt_id INT', #audi_id_first, #audi_id_second, #audi_by, #audi_on, #audi_details, #tabn_code, #audt_id
DECLARE #idSql NVARCHAR(MAX);
SELECT #idSql = 'DECLARE #audi_id INT
SELECT #audi_id = [audi_id]
FROM [dbo].' + QUOTENAME(#tableName) + '
WHERE ##ROWCOUNT > 0 AND [audi_id] = scope_identity()
SELECT t0.[audi_id]
FROM [dbo].' + QUOTENAME(#tableName) + ' AS t0
WHERE ##ROWCOUNT > 0 AND t0.[audi_id] = #audi_id'
EXEC sp_executesql #idSql
END
GO
then calling this from C#:
public void AddRecord(Audit record)
{
var Id1 = new SqlParameter("#audi_id_first", SqlDbType.NVarChar);
Id1.Value = (object)record.Id1 ?? System.DBNull.Value;
var Id2 = new SqlParameter("#audi_id_second", SqlDbType.NVarChar);
Id2.Value = (object)record.Id2 ?? System.DBNull.Value;
var UserName = new SqlParameter("#audi_by", SqlDbType.NVarChar);
UserName.Value = record.UserName;
var Stamp = new SqlParameter("#audi_on", SqlDbType.DateTime);
Stamp.Value = record.Stamp;
var Details = new SqlParameter("#audi_details", SqlDbType.Xml);
Details.Value = (object)record.Details ?? System.DBNull.Value;
var TableCode = new SqlParameter("#tabn_code", SqlDbType.Char);
TableCode.Value = record.TableCode;
var TypeId = new SqlParameter("#audt_id", SqlDbType.Int);
TypeId.Value = record.TypeId;
var DatabaseCode = new SqlParameter("#audi_db_code", SqlDbType.Char);
DatabaseCode.Value = CallingPrefix;
this.Database.ExecuteSqlCommand("EXEC Audit_Insert #audi_id_first, #audi_id_second, #audi_by, #audi_on, #audi_details, #tabn_code, #audt_id, #audi_db_code",
Id1, Id2, UserName, Stamp, Details, TableCode, TypeId, DatabaseCode);
}
My next struggle is getting it to work with IQueryable OData requests.
Solution 2
CREATE PROCEDURE [dbo].[CreateAuditTableIfNoneExists]
#tableName CHAR (12)
AS
BEGIN
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name=#tableName and xtype='U')
BEGIN
DECLARE #createSql NVARCHAR(MAX);
SELECT #createSql = 'CREATE TABLE [dbo].' + QUOTENAME(#tableName) + ' (
[audi_id] INT IDENTITY (1, 1) NOT NULL,
[audi_id_first] NVARCHAR (20) NULL,
[audi_id_second] NVARCHAR (20) NULL,
[audi_by] NVARCHAR (100) NOT NULL,
[audi_on] DATETIME NOT NULL,
[audi_details] XML NULL,
[tabn_code] CHAR (4) NULL,
[audt_id] INT NOT NULL,
CONSTRAINT [PK_dbo.' + #tableName + '] PRIMARY KEY CLUSTERED ([audi_id] ASC),
CONSTRAINT [FK_dbo.' + #tableName + '_dbo.tabn_table_name_tabn_code] FOREIGN KEY ([tabn_code]) REFERENCES [dbo].[tabn_table_name] ([tabn_code]),
CONSTRAINT [FK_dbo.' + #tableName + '_dbo.audt_audit_type_audt_id] FOREIGN KEY ([audt_id]) REFERENCES [dbo].[audt_audit_type] ([audt_id])
)'
EXEC sp_executesql #createSql
END
END
and on database initialisation make sure the table is present:
public class AuditInitialiser : IDatabaseInitializer<AuditContext>
{
public void InitializeDatabase(AuditContext context)
{
if (context.Database.CreateIfNotExists())
{
Seed(context);
}
var tableName = new SqlParameter("#tableName", SqlDbType.Char);
tableName.Value = context.AuditTableName;
context.Database.ExecuteSqlCommand("EXEC CreateAuditTableIfNoneExists #tableName", tableName);
}
However this only works for me because I am using an API, so is stateless and constructs the DbContext every time. This ensures the table is always present. Wouldn't work if I was doing my working out on a stated system.
I'm not a huge fan of stored procedures, but this might be a case where you want to use EF 6's (EF5??)'s ability to bind to stored procs. Then you could pass the table to use as parameter. See if this helps: How to call Stored Procedure in Entity Framework 6 (Code-First)?
(you could also perhaps use EntitySQL to solve this)