Mysql normalization? Or another way? - c#

After reading up on some normalization, it seems I am not quite grasping the concept.
What I am attempting to do is create a table that holds information for an item raffle.
So information for the item is ItemName, and Defindex.
After that, I need to have the "tickets" for the raffle, and to which user they went to. I want to limit the number of tickets sold.
So TicketA, TicketB, TicketC, TicketD.
Is there a query that I can use to insert a player name into TicketA if it's not full, or TicketB if A is full, so on until all tickets are sold?

Here's a sample to get you started: SQL Fiddle
Setup the basic tables to keep track of raffles and tickets.
CREATE TABLE Raffle
(
Id BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
,Name VARCHAR(50) NOT NULL
,WinnerLimit INT NOT NULL
);
CREATE TABLE RaffleTicket
(
Id BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
,RaffleId BIGINT NOT NULL
,UserId BIGINT NOT NULL
);
Setup a view to determine whether a raffle has any tickets left.
CREATE VIEW RaffleStatus
AS
SELECT Id AS RaffleId,
CASE WHEN EXISTS
(
SELECT 1
FROM RaffleTicket rt
WHERE rt.RaffleId = r.Id
GROUP BY rt.RaffleId
HAVING COUNT(rt.RaffleId) = r.WinnerLimit
)
THEN 1
ELSE 0
END AS SoldOut
FROM Raffle r;
Create a procedure to prevent overselling tickets. Note this probably isn't safe for multithreaded use.
DELIMITER //
CREATE PROCEDURE InsertRaffleTicket (IN userId BIGINT, IN raffleId BIGINT)
BEGIN
INSERT INTO RaffleTicket(UserId, RaffleId)
SELECT userId, raffleId
FROM RaffleStatus rs
WHERE rs.RaffleId = raffleId
AND rs.SoldOut = 0;
END//
DELIMITER ;

I want to limit the number of tickets sold.
That is business logic, not something that should reside in your database.
What if you want to query all raffles user X has attended? select * from raffles where ticketA = 'UserX' or ticketB = 'UserX' or ticketC = 'UserX'...
Just normalize it and check the ticket count from code.

One table of people, one of tickets, one of raffles.
Each ticket has the ID of the person and of the raffle.
As the previous answer, beyond that is business logic that shouldn't be in the DB. At most, you should "release" a batch of tickets for a raffle by creating them in the tickets table and then when a person wants to buy one you take the first ticket with the correct raffle ID and a null person ID.

Related

Any other optimal way to update the parent table's columns when a child table is updated?

I have a few table structure look as below:
CREATE TABLE Person
(
PersonID INT PRIMARY KEY,
Name NVARCHAR(255),
LastUpdatedBy INT,
LastUpdatedDate DATETIME
);
CREATE TABLE Info
(
InfoID INT PRIMARY KEY,
PersonID INT,
Info NVARCHAR(255),
LastUpdatedBy INT,
LastUpdatedDate DATETIME
);
CREATE TABLE Setting
(
SettingID INT PRIMARY KEY,
PersonID INT,
Setting NVARCHAR(255),
LastUpdatedBy INT,
LastUpdatedDate DATETIME
);
I face a new procedure to follow that if there is any updates on Info or Setting table, I will need to do relevant updates to Person table on columns LastUpdatedBy and LastUpdatedDate.
What first come to my mind is to create a SQL trigger that automatically update Person table when Info or Setting table does. But take a quick glance through for a few articles stating that a SQL trigger should be avoided as it's an very expensive process when creating it,
While some people recommends to change in application code. For an example,
using (var db = new DbContext())
{
var result = db.Info.SingleOrDefault(x => x.InfoID == infoID);
if (result != null)
{
result.Info = "Some new value";
result.LastUpdatedBy = userID;
result.LastUpdatedDate = DateTime.UtcNow;
db.SaveChanges();
}
}
need to change and become like this.
using (var db = new DbContext())
{
var result = db.Info.SingleOrDefault(x => x.InfoID == infoID);
if (result != null)
{
result.Info = "Some new value";
result.LastUpdatedBy = userID;
result.LastUpdatedDate = DateTime.UtcNow;
var person = db.Person.SingleOrDefault(x => x.PersonID == result.PersonID);
if (person != null)
{
person.LastUpdatedBy = result.LastUpdatedBy;
person.LastUpdatedDate = result.LastUpdatedDate;
}
db.SaveChanges();
}
}
in reality, the application code is massive, a lot of code modification need to be made.
Assume there are 30+ tables, and each of them contain at least 100k of records. If creating of triggers are possible, it will be as the following:
CREATE TRIGGER TriggerName ON dbo.Info
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.Person
SET LastUpdatedBy = INSERTED.LastUpdatedBy ,
LastUpdatedDate = INSERTED.LastUpdatedDate
FROM INSERTED
WHERE dbo.Person.PersonID = INSERTED.PersonID
END
GO
Is the SQL trigger should really be avoided in this scenario? Please explain based on your answer if can. Any alternative solution is welcome, performance first.
Trigger is optimal (from a performance perspective) here; it's simply like running an update statement on a bunch of rows from the front end code. I don't see why you think there is a performance penalty. Your trigger code should look more like this though:
CREATE TRIGGER TriggerName ON dbo.Info
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.Person
SET LastUpdatedBy = INSERTED.LastUpdatedBy ,
LastUpdatedDate = INSERTED.LastUpdatedDate
FROM dbo.Person
INNER JOIN
INSERTED
ON dbo.Person.PersonID = INSERTED.PersonID
END
GO
There are other ways, such as making a Stored procedure that updates all tables in a transaction, or updating front end data access layer (if your front end has a lot to update, it implies it is structured wrong: one place should have responsibility for writing to this table. If your front end code has update statements peppered all through it, well.. that's a bad design) so a dedicated class maintains these two tables properly..
Right now I'd say a trigger is your easiest way out of the problem.. they aren't well liked, though not because of performance, but because they start to add confusing consequences.. imagine you as a c# developer with limited database experience, didn't know what a trigger was, and you're complaining "every time I update just this one table, all these other 27 tables change by magic! What's going on? Am I going crazy or what?" - triggers break rules like "keep all your data updating code in one place" and this is why people who engineer systems where specific parts have specific jobs, don't like them

Calling procedure from multi user

I have come up with scenario and i am really struggling with it. Please help me in this. i have table below and store procedure is calling for this table on button click in c# and data source is assign to grid.
Id Product QtyReceive QtyDispatch Location
1 HP2030 20 0 A
Below is procedure which is call on click event and it just select the require quantity of product which we require from specific location.
DECLARE #Data TABLE
(
Id int identity(1,1)
, Product varchar(10)
, QtyReceive int
, QTYDispatch int
, Location varchar(10)
)
DECLARE #Qty int = 10
INSERT #Data VALUES
('HP2030', 20 ,5,'A');
WITH sumqty AS
(
SELECT *, SUM(QtyReceive -QTYDispatch) OVER (PARTITION BY Product ORDER BY Id) AS TotalQty FROM #Data
)
,takeqty AS (
SELECT *,
CASE
WHEN #Qty >= TotalQty THEN QtyReceive
ELSE #Qty - ISNULL(LAG(TotalQty) OVER (PARTITION BY Product ORDER BY Id), 0)
END AS TakeQty
FROM sumqty
)
SELECT
Product
, TotalQty
, TakeQty
, Location
FROM takeqty WHERE TakeQty > 0
ORDER BY Location;
Now there is a problem let say two user sitting at two different computer and User 1 required 10 quantity and other user required 5 quantity. They click button to get quantity at same time and save it at same time and dispatch quantity update with quantity 10 instead of 10+5=15. It happens when both user hit save at same time. No of user can be vary. Is there any solution to this problem?
I try to understand the whole scenario.

Preventing duplication from SQL Query, ASP.net

I have the below SQL query using the Query Builder in Visual Studio. As you can see the same user is duplicated 3 times, this is due to the user having 3 different skills. How can I merge the 3 skills together in the SQL query or in a ListView control so that it only displays one result instead of 3 and that the user has their 3 skills listed?
SELECT users.role_id, users.username, users.first_name, users.last_name, users.description, roles.role_id, roles.role, skills.skill_id, skills.user_id, skills.skill
FROM users
INNER JOIN roles ON users.role_id = roles.role_id
INNER JOIN skills ON users.user_id = skills.user_id
WHERE (users.role_id = 3)
Use For XML Path(''), Type. It is a bit of a hack, because you're really creating an XML string without a root and fashioning odd elements, but it works well. Be sure to include the Type bit, otherwise the XML trick will attempt to convert special characters, like < and & into their XML escape sequences (here is an example).
Here is a simplified version of your problem in a SQL Fiddle. Below is the relevant Select snippet.
SELECT users.user_id, users.first_name,
STUFF(
(SELECT ', ' + skill
FROM skills
WHERE users.user_id = skills.user_id
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
, 1, 2, '') AS skill_list
FROM users
Try using Stuff and For Xml
Here's the Fiddle:
http://sqlfiddle.com/#!6/fcf71/5
See if it helps, it's just a sample so you will have to change the column names.
You can use PIVOT on the Skill then group those skills into one column.
To make it simple, I test it with some sample data like the following:
CREATE SCHEMA _Test
CREATE TABLE _Test.SkillSet(SkillId INT IDENTITY(1,1) PRIMARY KEY, SkillName NVARCHAR(64))
INSERT INTO _Test.SkillSet(SkillName) VALUES('C/C++')
INSERT INTO _Test.SkillSet(SkillName) VALUES('C#')
INSERT INTO _Test.SkillSet(SkillName) VALUES('Java')
CREATE TABLE _Test.Employees(EmpId INT IDENTITY(1,1) PRIMARY KEY, FullName NVARCHAR(256))
INSERT INTO _Test.Employees(FullName) VALUES('Philip Hatt')
INSERT INTO _Test.Employees(FullName) VALUES('John Rosh')
CREATE TABLE _Test.Employee_Skill(EmpId INT FOREIGN KEY REFERENCES _Test.Employees(EmpId), SkillId INT FOREIGN KEY REFERENCES _Test.SkillSet(SkillId))
INSERT INTO _Test.Employee_Skill(EmpId, SkillId) VALUES(1, 1)
INSERT INTO _Test.Employee_Skill(EmpId, SkillId) VALUES(1, 2)
INSERT INTO _Test.Employee_Skill(EmpId, SkillId) VALUES(1, 3)
INSERT INTO _Test.Employee_Skill(EmpId, SkillId) VALUES(2, 2)
INSERT INTO _Test.Employee_Skill(EmpId, SkillId) VALUES(2, 3)
WITH tEmpSkill
AS
(SELECT A.EmpId, A.FullName, C.SkillName
FROM _Test.SkillSet C RIGHT JOIN
(
_Test.Employees A LEFT JOIN _Test.Employee_Skill B
ON A.EmpId = B.EmpId
)
ON B.SkillId = C.SkillId
)
SELECT * FROM tEmpSkill
PIVOT(COUNT(SkillName) FOR SkillName IN([C/C++], [C#], [Java])) AS Competency
The query above gives me an intermediate result
PIVOT RESULT
Now you can easily make a string containing all the skills needed for each employee. You can also search for some articles to use the PIVOT with unknown number of columns (skill sets), which may better serve your need.
Hope this can help.

How to default database value when system date change

I'm currently doing a banking website for my project, using C# and asp.net. One of the function is 'Remaining Daily Limit'. I need the database value to change back to default once I changed the system date to another day. Example : If a user had $500 (default value) as a daily limit, and he used all up. The next day, he will have $500 again. May I know how should I go about it?
Here is a SQL script that will create how I would setup your db with some sample data:
CREATE TABLE tblBankCustomer
(
BankCustomerId INT NOT NULL IDENTITY(1,1) PRIMARY KEY
, FirstName NVARCHAR(100) NOT NULL
, LastName NVARCHAR(100) NOT NULL
, DailySpendingLimit DECIMAL(38, 2) NOT NULL CONSTRAINT DF_tblBankCustomer_DailySpendingLimit DEFAULT(500)
)
CREATE TABLE tblTransactionType
(
TransactionTypeId INT NOT NULL IDENTITY(1,1) PRIMARY KEY
, TransactionType VARCHAR(50) NOT NULL
)
INSERT tblTransactionType (TransactionType)
VALUES ('Deposit')
, ('Withdrawal')
CREATE TABLE tblTransaction
(
TransactionId INT NOT NULL IDENTITY(1,1) PRIMARY KEY
, BankCustomerId INT NOT NULL
, TransactionDate DATE NOT NULL
, Amount DECIMAL(38, 2) NOT NULL
, TransactionTypeId INT NOT NULL
)
ALTER TABLE tblTransaction
ADD CONSTRAINT FX_tblTransaction_tblBankCustomer
FOREIGN KEY (BankCustomerId)
REFERENCES tblBankCustomer(BankCustomerId)
ALTER TABLE tblTransaction
ADD CONSTRAINT FX_tblTransaction_tblTransactionType
FOREIGN KEY (TransactionTypeId)
REFERENCES tblTransactionType(TransactionTypeId)
INSERT tblBankCustomer
(
FirstName
, LastName
)
VALUES ('Jeremy', 'Pridemore')
, ('K', 'YQ')
INSERT tblTransaction
(
BankCustomerId
, TransactionDate
, Amount
, TransactionTypeId
)
VALUES
(1, CURRENT_TIMESTAMP, 48.50, 2) -- Jeremy, Today, $48.50, Withdrawal
, (1, CURRENT_TIMESTAMP, 300.00, 2) -- Jeremy, Today, $300, Withdrawal
, (1, CURRENT_TIMESTAMP, -200.00, 1) -- Jeremy, Today, $200, Deposit
, (2, CURRENT_TIMESTAMP, 285.00, 2) -- K, Today, $285, Withdrawal
, (2, CURRENT_TIMESTAMP, 215.00, 2) -- K, Today, $215, Withdrawal
GO
CREATE FUNCTION fGetRemainingSpendingLimit
(
#BankCustomerId INT
, #Date DATE
)
RETURNS DECIMAL(38, 2)
BEGIN
SET #Date = ISNULL(#Date, CURRENT_TIMESTAMP)
DECLARE #RemainingLimit DECIMAL(38, 2) =
(SELECT
SUM([Transaction].Amount)
FROM tblBankCustomer Customer
INNER JOIN tblTransaction [Transaction]
ON [Transaction].BankCustomerId = Customer.BankCustomerId
AND [Transaction].TransactionDate = #Date
INNER JOIN tblTransactionType TransactionType
ON TransactionType.TransactionTypeId = [Transaction].TransactionTypeId
AND TransactionType.TransactionType = 'Withdrawal'
WHERE Customer.BankCustomerId = #BankCustomerId)
RETURN #RemainingLimit
END
GO
-- Some sample selects
SELECT dbo.fGetRemainingSpendingLimit(1, NULL)
SELECT dbo.fGetRemainingSpendingLimit(2, NULL)
Then in C# you should know the customer's ID for the customer you're working with. If you're using somethiing like ADO.NET you can call this function directly and use the value in code.
Default values are usually stay in some config file. In your specific case, I would say that you can
or have a table with default values
or in the tables where is possible to have restore functionality have default column with corresponding values.
after, can restore them using some stored procedure.
You can create SQL job and schedule it to run every midnight, within the job you can execute a stored procedure reseting values to $500.
In my opinion, based on that there may be other requirements in the future, I'd have a table that looks like:
UserID AppliedOn Limit
1 1/1/2012 500
1 2/1/2012 750
This give you a historic view of limits, that you can give to the user or data-mine. In the same way, that is how I would apply current daily limits.
UserID AppliedOn Withdrawn
1 1/10/2012 125
1 1/10/2012 225
Now on 1/1/2012 it would be easy to determine what the amount left in the limit is without any jobs or triggers. And again you'd have historic values that can be data-mined for other features.
SELECT
ul.Limit - uw.Sum as LimitLeft
FROM
UserLimit ul
INNER JOIN (
SELECT
UserID,
AppliedOn,
SUM(Limit) as Sum
FROM
UserLimit
Group by
UserID,
AppliedOn) uw on ul.UserID = uw.UserID
and ul.AppliedOn = uw.AppliedOn
WHERE
ul.UserID = #userID
AND ul.AppliedOn = #dateInQuestion
(my raw SQL skills might be a bit rusty due to Entity Framework here).

Retrieve last record of SQL Server table

Here is the my problem: I have a SQL Server database called emp. It has an employee table (with userid int column). I need to retrieve the last record of the userid in employee table with increment userid value + 1. At the moment I did it on the my GUI. So how do I write a sql query for it?
To return the the record with the highest userid, you can do:
SELECT TOP 1 userid
FROM employee
ORDER BY userid DESC
or...
SELECT MAX(userid)
FROM employee
If your plan is to then increment the userid manually and insert a new record with that new ID, I'd recommend against doing that - what if 2 processes try to do it at the same time? Instead, use an IDENTITY column as userid and let the incrementing be handled for you automatically
You shouldn't be manually incrementing the userid column, use an IDENTITY column instead. That will automatically add 1 for you for every new row.
CREATE TABLE Employees (
UserId INT IDENTITY PRIMARY KEY NOT NULL,
UserName NVARCHAR(255) NOT NULL,
// etc add other columns here
)
If you really really have to select the highest userid it is a very simple query:
SELECT MAX(UserId) + 1
FROM Employees
[Edit]
Based on your comments, you should use the SELECT MAX(UserId) + 1 FROM Employees query. But be aware that this does not guarantee the number will be the ID. Normally you would not show an Id value until after the record has been saved to the database.
This will give you last inserted record, If you don't have Identity column.
EXECUTE ('DECLARE GETLAST CURSOR DYNAMIC FOR SELECT * FROM [User]')
OPEN GETLAST
FETCH LAST FROM GETLAST
CLOSE GETLAST
DEALLOCATE GETLAST
If you have set identity than you can use following.
SELECT top(1) ID from [YourTable] order by ID desc
To have the new userid before saving, create a NextId table.
Before inserting the user, get the new value from NextId:
UserId = SELECT Coalesce(NextId, 0) + 1 from NextId
Then update the NextID table:
UPDATE NEXTID SET NextId = IserID
And then use that value in your user creation code
You can get gaps, there are more complicated methods to avoid them; but I think this will do

Categories

Resources