Before you see the code, I want to try and explain what I want to happen. This is a personal project, not worried about sql injection, nothing production, etc., etc..
My application lets a user create an account, select which classes they've taken, then views them.
What I have currently: A user creates a username/password that is stored in a Users table. The username is also stored as a global variable. At the same time, a copy of the master course list (107 rows) is made and renamed as the users username (from the global variable.) After creating the username/password, the user is brought to a form where they select courses they've taken and their corresponding grade.
Where I'm stuck: The user can select which courses they've taken and choose their corresponding grade. The users table is made on the fly so I don't know any other way to access the table during run time besides using Dynamic SQL. I just want to update the user table with their selection during run-time.
From here, my code basically says "If check marked, update users course table with their grade and status where course# = x." Is my issue the global variable? Should I use my Users table somehow? Should I use QUOTENAME in some way instead of UPDATE? My code with #sql keeps coming up with syntax errors so I feel like a simple fix could do the trick.
private void btnNext_Click(object sender, EventArgs e)
{
if (checkIntrotoPublicSpeaking.Checked || checkEffectiveOralCommunication.Checked || checkProfComm.Checked)
{
List<SqlParameter> sqlOralComm = new List<SqlParameter>();
sqlOralComm.Add(new SqlParameter("Username", GlobalVariables.username));
sqlOralComm.Add(new SqlParameter("IntrotoPublicSpeaking", cboxIntrotoPublicSpeaking.Text));
sqlOralComm.Add(new SqlParameter("EffectiveOralCommunication", cboxEffectiveOralCommunication.Text));
sqlOralComm.Add(new SqlParameter("ProfComm", cboxProfComm.Text));
DAL.ExecSP("CreateOralComm", sqlOralComm);
}
}
and
ALTER PROCEDURE [dbo].[CreateOralComm]
-- Add the parameters for the stored procedure here
#Username nvarchar(30),
#IntrotoPublicSpeaking nvarchar(3),
#EffectiveOralCommunication nvarchar(3),
#ProfComm nvarchar(3)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #sql as nvarchar(max)
SET #sql = 'UPDATE ' + #Username + ' SET Grade = ' +
#IntrotoPublicSpeaking + ', Status = ''Completed'' WHERE Course =
7600105'
EXEC sp_executesql #sql;
/*
SELECT QUOTENAME(#Username)
SET Grade = 'A', Status = 'Completed'
WHERE Course = 7600105;
*/
END
GO
I feel like a nuisance. I know I'm not that good and there's probably A LOT of easier ways to go about this but I'm really trying my best to get through this with the knowledge I have.
Related
I’m using Visual Studio with DevExpress Winforms in conjunction with Microsoft SQL Server Manager, and I’m currently trying to write a stored procedure that will activate on a button click, copying the focused row to a different table.
I’ve looked into it and haven’t been able to find a solution that does what I need it to. I would prefer not to use checkboxes if possible, but am open to it.
My current code within the stored procedure is:
CREATE PROCEDURE [dbo].[procedurename]
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT * FROM tbl_b)
INSERT INTO tbl_b (column names here)
SELECT DISTINCT (column names here)
FROM tbl_a
END
I’ve been able to successfully call this procedure when testing the program with the button, and it functions mostly as intended, moving the data from one table to the other with no duplicates, however it moves the entire table when I only want to move a focused row.
I’m using gridView for this, and I’ve tried solutions that use DataGridView that don’t seem to work.
This is a desktop based program.
You need a parameter in your stored procedure that holds an identification of the row you want to copy
CREATE PROCEDURE [dbo].[procedurename] (#ID int)
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT * FROM tbl_b)
INSERT INTO tbl_b (column names here)
SELECT DISTINCT (column names here)
FROM tbl_a
where tbl_A.PrimaryKeyColumn = #ID
END
In the Click event of your button you could then do something like this
var id = myGridView.GetRowCellValue(myGridView.FocusedRowHandle, "YourPrimaryKeyName");
and now you can use this id variable to pass to your stored procedure
However, you do have another problem in your stored procedure
IF NOT EXISTS (SELECT * FROM tbl_b)
This check will never allow you to do your insert once there is 1 or more rows in that table.
I think you need something like
IF NOT EXISTS (SELECT 1 FROM tbl_b where tbl_b.PrimaryKey = #ID)
All our CRUD operations have a CommitTransaction() at the end before returning the results of a Method. For instance, we have a method Create() that creates a record if there is no existing record with a Status: 'Float'. Otherwise, it selects the record and updates its contents.
This is working as checked in the Database. The problem is, the method is returning its previous data and not the updated one:
public class Sample
{
public int ID {get; set;}
public string Code {get;set;}
public decimal Total {get; set;}
}
In Create(), Code and Total is entered by the user which should update the data in the database.
E.g.
ID | Code | Total
1 | CodeName | 100
The user updates
Code : 'Code1'
Total : 200
But the Method still returns
Code : 'CodeName '
Total : 100
But if checked in the Database, it is already
ID | Code | Total
1 | Code1 | 200
I have observed that this only happens when a CommitTransaction() is used which is not advisable to remove but a correct data return is also needed.
Here is a simplified replica of Create():
private string procCRUD = "procCreate #ID={0},#Code={1},#Total={2}";
public Sample Create(Sample data)
{
IQueryable<ComponentType> query = contextBase.Samples.FromSql(procCRUD,"create",data.ID, data.Code, data.Total); // contextBase.Samples is the DbContext
commit(); // this commits the transaction
return query;
}
procCRUD is a stored procedure:
DROP PROCEDURE IF EXISTS procCreate
GO
CREATE PROCEDURE procCreate
#proc varchar(20) = 'create',
#ID bigint = 0,
#Code varchar(20)= NULL,
#Total int= 0
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL varchar(MAX)
SET #SQL = ''
IF (#proc='create') BEGIN
IF (NOT EXISTS(SELECT ID FROM Sample WHERE Status='Float'))
BEGIN
INSERT INTO Sample(Code, Total) VALUES( #Code, #Total)
SET #ID = ##IDENTITY;
END
ELSE
BEGIN
SELECT #ID=ID FROM Sample WHERE Status='Float'
UPDATE Sample SET
Code = #Code
,Total = #Total
WHERE ID=#ID
END
END
SET #SQL = 'SELECT TOP 1 * FROM Sample '
EXEC SP_EXECUTESQL #SQL
END
GO
I have tried manually setting the return value e.g. query.Code = data.Code before the return but it is time-consuming if I have more than 10 Class properties. Also, some properties are not entered by the user but retrieved from the database depends on another property entered.
I wonder if some of you also encountered this problem. Any help will be appreciated. Thank you.
(edited)
You need to better explain what you are trying to do and what is the actual code behind your
contextBase.Samples.FromSql
At least, the following seems to be relevant, based on your (not complete) description.
Copying from base to parent.
I have tried manually setting the return value e.g. query.Code = data.Code before the return but it is time-consuming if I have more than 10 Class properties.
Please read the solution already suggested here.
You will end up with defining a Map extension and using
query.Map(data);
The above should resolve your actual problem, but there are other inconsistencies in your question, that might affect the result.
SQL
procCRUD is a stored procedure:
SET #SQL = 'SELECT TOP 1 * FROM Sample '
EXEC SP_EXECUTESQL #SQL
END
GO
In the end of your stored procedure you are managing a select to return the correct value, but - as far as I can see - you are missing the correct where condition, something like a WHERE ID=#ID (or Status='Float', I don't know your app logic).
By the way, you should only Commit() after a BeginTransaction, but that is not shown in your question.
Nothing in your description really makes sense. According to you, you:
do a select
commit a transaction
return the values
Wonder that the value returned and the one in the Database do not match?
If you want to retreive values as a Update or Insert operation is running, the only reliable syntax in SQL is OUTPUT: https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql
Returns information from, or expressions based on, each row affected by an INSERT, UPDATE, DELETE, or MERGE statement. These results can be returned to the processing application for use in such things as confirmation messages, archiving, and other such application requirements. The results can also be inserted into a table or table variable. Additionally, you can capture the results of an OUTPUT clause in a nested INSERT, UPDATE, DELETE, or MERGE statement, and insert those results into a target table or view.
If you - for example - want to get teh Primary Key of something you just inserted, OUTPUT Inserted.ID is the only way that will reliably work. Unfortunatily, most non-MSSQL DBMS do not have such a feature.
I am facing a peculiar issue with loading a list of tables from a specific database (well rather a group of databases) while attached to the master database. Currently my query loads all of the databases on the server, then loops through those databases sending information back to the client via RAISERROR. As this loop is executing I need a nested loop to load all of the tables for the current database for later transmission as a SELECT once the query has completed. The issue I'm running into is that this will be executed as a single query inside of C# code. Ideally I would like to load everything in SQL and return it to the client for processing. For example:
WHILE (#dbLoop < #dbCount) BEGIN
-- Do cool things and send details back to client.
SET #dbName = (SELECT _name FROM dbTemp WHERE _id = #dbLoop);
-- USE [#dbName]
-- Get a count of the tables from info schema on the newly specified database.
WHILE (#tableLoop < #tableCount) BEGIN
-- USE [#dbName]
-- Do super cool things and load tables from info schema.
SET #tableLoop += 1;
END
SET #dbLoop += 1;
END
-- Return the list of tables from all databases to the client for use with SQLDataAdapter.
SELECT * FROM tableTemp;
This topic is pretty straight forward; I just need a way to access tables in a specified database (preferably by name) without having to change the connection on the SqlConnection object, and without having to have a loop inside of my C# code to process the same query on each database on the C# side. It would be more efficient to load everything in SQL and send it back to the application. Any help that can be provided on this would be great!
Thanks,
Jamie
All the tables are in the meta data you can just do a query against that and join to your list of schemas you want to look at.
SELECT tab.name
FROM sys.tables AS tab
JOIN sys.schemas AS sch on tab.schema_id = sch.schema_id
JOIN dbTemp temp on sch.name = temp.[_name]
This returns a list of the table to return back as a result set.
The statement USE [#dbName] takes effect AFTER it is run (usually via the GO statement.
USE [#dbName]
GO
The above 2 lines would make you start using the new Database. You cannot use this in the middle of your SQL or SP.
One other option which you can use is to use the dot notation, i.e., dbname..tablename syntax to query your tables.
double dot notation post
Okay, after spending all day working on this, I have finally come up with a solution. I load all the databases into a table variable, then I begin looping through those databases and send back their details to the client. After the database details themselves have been sent to the client via RAISERROR I then utilize sp_executesql to execute a new sub-query with the current database specified to get the list of tables for processing at the end of the primary. The example below demonstrates the basic structure of this process for others experiencing this issue in the future.
Thank you all once again for your help!
-Jamie
DECLARE #LoopCounter INT = 1, #DatabaseCount INT = 0;
DECLARE #SQL NVARCHAR(MAX), #dbName NVARCHAR(MAX);
DECLARE #Databases TABLE ( _id INT, _name NVARCHAR(MAX) );
DECLARE #Tables TABLE ( _name NVARCHAR(MAX), _type NVARCHAR(15) );
INSERT INTO #Databases
SELECT ROW_NUMBER() OVER(ORDER BY name) AS id, name
FROM sys.databases
WHERE name NOT IN ( 'master', 'tempdb', 'msdb', 'model' );
SET #DatabaseCount = (SELECT COUNT(*) FROM #Databases);
WHILE (#LoopCounter <= #DatabaseCount) BEGIN
SET #dbName = (SELECT _name FROM #Databases WHERE _id = #LoopCounter);
SET #SQL NVARCHAR(MAX) = 'SELECT TABLE_NAME, TABLE_TYPE
FROM [' + #dbName + '].INFORMATION_SCHEMA.TABLES';
INSERT INTO #Tables EXEC sp_executesql #SQL;
SET #LoopCounter += 1;
END
SQL Server 2016 accessed by ASP.NET 4.6.2 MVC web application
I have table "Building" and a Building can have multiple "Components". For example, Building1 has Component1 and Component2 ... etc
It was requested of me to be able to lock a building. Lock means that a component can no longer be modified (CREATE/UPDATE/DELETE). Well, as you can imagine, this is a huge application and a component can be modified in 100+ places. No one can even answer the question, "Where all do I need to lock?".
My thought is to lock everywhere I can think of and then as a safety net create a SQL Trigger that prevents all modifications if the column on the Component table "IsLocked BIT" is true. Currently, the only way I know if a component is locked is if the IsLocked column equals true.
So, I say all of that for this. How do I create a SQL Server Trigger that prevents a row of data from being modified if the row being modified has column IsLocked = 1?
Edit 1
In my opinion, this is not a duplicate. Using Instead of Delete or Instead of... will not work for me. If I do the instead of ... then inside of that I will need to provide commit logic. I don't want to provide commit logic. I just want to run a check prior to insert, update, delete.
Edit 2 - Instead of Update/Delete is best choice
If instead of... is my best choice than can someone rewrite what I have using the instead of update/delete? I don't know how to do it. Please keep in mind that requests will be coming from a web app. I won't know if they are updating one column or the entire entity or what they will passing in. I know that the way I have it written that it will catch any insert/update/delete and prevent it if locked. If there is a better way then please write it and explain why it is better.
Here is the solution I came up with:
ALTER TRIGGER [dbo].[PreventLockedModification]
ON [dbo].[Component]
FOR INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
--DETERMINE INSERT(I) UPDATE(U) OR DELETE(D)
----------------------------------------------------------------------------------------------------
DECLARE #action as char(1);
SET #action = 'I'; -- Set Action to Insert by default.
IF EXISTS(SELECT * FROM DELETED)
BEGIN
SET #action =
CASE
WHEN EXISTS(SELECT * FROM INSERTED) THEN 'U' -- Set Action to Updated.
ELSE 'D' -- Set Action to Deleted.
END
END
----------------------------------------------------------------------------------------------------
DECLARE #ErrorMsg nvarchar(100) = 'This row is locked and cannot be updated';
DECLARE #IsLocked bit;
DECLARE #BuildingId bigint;
DECLARE #UnitId bigint;
DECLARE #IsComplete_Building bit;
DECLARE #IsComplete_Unit bit;
----------------------------------------------------------------------------------------------------
IF #action = 'U' or #action = 'D'
BEGIN
SELECT #IsLocked = IsLocked FROM deleted;
IF #IsLocked = 1
BEGIN
RAISERROR (#ErrorMsg, 16, 1);
ROLLBACK TRANSACTION
END
END
----------------------------------------------------------------------------------------------------
ELSE IF #action = 'I'
BEGIN
SELECT #BuildingId = BuildingId FROM inserted;
SELECT #UnitId = UnitId FROM inserted;
SELECT #IsComplete_Building = IsComplete FROM Building WHERE BuildingId = #BuildingId
SELECT #IsComplete_Unit = IsComplete FROM Unit WHERE UnitId = #UnitId
IF #IsComplete_Building = 1 or #IsComplete_Unit = 1
BEGIN
RAISERROR (#ErrorMsg, 16, 1);
ROLLBACK TRANSACTION
END
END
----------------------------------------------------------------------------------------------------
END
Is there a way to batch stored procedure calls in MySql with the .Net / Connector to increase performance?
Here's the scenario... I'm using a stored procedure that accepts a few parameters as input. This procedure basically checks to see whether an existing record should be updated or a new one inserted (I'm not using INSERT INTO .. ON DUPLICATE KEY UPDATE because the check involves date ranges, so I can't really make a primary key out of the criteria).
I want to call this procedure a lot of times (let's say batches of 1000 or so). I can of course, use one MySqlConnection and one MySqlCommand instance and keep changing the parameter values, and calling .ExecuteNonQuery().
I'm wondering if there's a better way to batch these calls?
The only thought that comes to mind is to manually construct a string like 'call sp_myprocedure(#parama_1,#paramb_1);call sp_myprocedure(#parama_2,#paramb2);...', and then create all the appropriate parameters. I'm not convinced this will be any better than calling .ExecuteNonQuery() a bunch of times.
Any advice? Thanks!
EDIT: More info
I'm actually trying to store data from an external data source, on a regular basis. Basically I'm taking rss feeds of Domain auctions (from various sources like godaddy, pool, etc.), and updating a table with the auction info using this stored procedure (let's call it sp_storeSale). Now, in this table that the sale info gets stored, I want to keep historical records for sales for a given domain, so I have a domain table, and a sale table. The sale table has a many to one relationship with the domain table.
Here's the stored procedure:
-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$
CREATE PROCEDURE `DomainFace`.`sp_storeSale`
(
middle VARCHAR(63),
extension VARCHAR(10),
brokerId INT,
endDate DATETIME,
url VARCHAR(500),
category INT,
saleType INT,
priceOrBid DECIMAL(10, 2),
currency VARCHAR(3)
)
BEGIN
DECLARE existingId BIGINT DEFAULT NULL;
DECLARE domainId BIGINT DEFAULT 0;
SET #domainId = fn_getDomainId(#middle, #extensions);
SET #existingId = (
SELECT id FROM sale
WHERE
domainId = #domainId
AND brokerId = #brokerId
AND UTC_TIMESTAMP() BETWEEN startDate AND endDate
);
IF #existingId IS NOT NULL THEN
UPDATE sale SET
endDate = #endDate,
url = #url,
category = #category,
saleType = #saleType,
priceOrBid = #priceOrBid,
currency = #currency
WHERE
id = #existingId;
ELSE
INSERT INTO sale (domainId, brokerId, startDate, endDate, url,
category, saleType, priceOrBid, currency)
VALUES (#domainId, #brokerId, UTC_TIMESTAMP(), #endDate, #url,
#category, #saleType, #priceOrBid, #currency);
END IF;
END
As you can see, I'm basically looking for an existing record that is not 'expired', but has the same domain, and broker, in which case I assume the auction is not over yet, and the data is an update to the existing auction. Otherwise, I assume the auction is over, it is a historical record, and the data I've got is for a new auction, so I create a new record.
Hope that clears up what I'm trying to achieve :)
I'm not entirely sure what you're trying to do but it sounds kinda house-keeping or maintenance related so I won't be too ashamed at posting the following suggestion.
Why dont you move all of your logic into the database and process it all server side ?
The following example uses a cursor (shock/horror) but it's perfectly acceptable to use them in such circumstances.
If you can avoid using cursors at all - great, but the main point of my suggestion is about moving the logic from your application tier back into the data tier to save on the round trips. You'd call the following sproc once and it would process the entire range of data in single call.
call house_keeping(curdate() - interval 1 month, curdate());
Also, if you can provide just a bit more information about what you're trying to do we might be able to suggest other approaches.
Example stored procedure
drop procedure if exists house_keeping;
delimiter #
create procedure house_keeping
(
in p_start_date date,
in p_end_date date
)
begin
declare v_done tinyint default 0;
declare v_id int unsigned;
declare v_expired_date date;
declare v_cur cursor for
select id, expired_date from foo where
expired_date between p_start_date and p_end_date;
declare continue handler for not found set v_done = 1;
open v_cur;
repeat
fetch v_cur into v_id, v_expired_date;
/*
if <some condition> then
insert ...
else
update ...
end if;
*/
until v_done end repeat;
close v_cur;
end #
delimiter ;
Just incase you think I'm completely mad in suggesting cursors you might want to read this
Optimal MySQL settings for queries that deliver large amounts of data?
Hope this helps :)