Sending SQL a boolean and update a table as 1 or -1 and how to "update" an empty row for the initial values - sql

I have a Songs table with a likes column that holds the number of likes users sent . Each user sends a boolean (1 or 0) through a C# app which adds to the likes column.
About my procedure:
I want to know if there is more an efficient and short way of writing the part 1 of the function?
I had to manually insert '0' instead of the NULL for the first time for the function to work. It wasn't working because the initial value for Likes column is NULL. Is there a way to affect the row for the first time when it has NULL in it?
For part 2 of the function with [Users_Likes_Songs] table, I want to update if the user send a like (true = 1) or removed it (false = 0).
How can I update this table for the first time when the users 'like' must be valued as '1', when its rows are completely empty?
I thank you very much if you can help me.
The procedure:
CREATE PROCEDURE Songs_Likes
#User_ID INT,
#SongID INT,
#Song_like BIT
AS
BEGIN
--- part 1 of the function
IF (#Song_like = 1)
BEGIN
UPDATE [Songs]
SET [Likes] = [Likes] + #Song_like
WHERE [Song_ID] = #SongID
END
IF (#Song_like = 0)
BEGIN
UPDATE [Songs]
SET [Likes] = [Likes] - 1
WHERE [Song_ID] = #SongID
END
--- part 2 of the function with the second table
UPDATE [Users_Likes_Songs]
SET [LikeSong] = #Song_like
WHERE ([UserID] = #User_ID) AND ([SongID] = #SongID)
END

I think that the better method would be to change your design to calculate the likes and have a table that stores the likes for each user. In simple terms, something like:
USE Sandbox;
GO
CREATE SCHEMA music;
GO
CREATE TABLE music.song (SongID int IDENTITY(1,1),
Artist nvarchar(50) NOT NULL,
Title nvarchar(50) NOT NULL,
ReleaseDate date);
CREATE TABLE music.[User] (UserID int IDENTITY(1,1),
[Login] nvarchar(128));
CREATE TABLE music.SongLike (LikeID bigint IDENTITY(1,1),
SongID int,
UserID int,
Liked bit);
CREATE UNIQUE NONCLUSTERED INDEX UserLike ON music.SongLike(SongID, UserID); --Stops multiple likes
GO
--To add a LIKE you can then have a SP like:
CREATE PROC music.AddLike #SongID int, #UserID int, #Liked bit AS
BEGIN
IF EXISTS (SELECT 1 FROM music.SongLike WHERE UserID = #UserID AND SongID = #SongID) BEGIN
UPDATE music.SongLike
SET Liked = #Liked
WHERE UserID = #UserID
AND SongID = #SongID
END ELSE BEGIN
INSERT INTO music.SongLike (SongID,
UserID,
Liked)
VALUES (#SongID, #UserID, #Liked);
END
END
GO
--And, if you want the number of likes:
CREATE VIEW music.SongLikes AS
SELECT S.Artist,
S.Title,
S.ReleaseDate,
COUNT(CASE SL.Liked WHEN 1 THEN 1 END) AS Likes
FROM music.Song S
JOIN music.SongLike SL ON S.SongID = SL.SongID
GROUP BY S.Artist,
S.Title,
S.ReleaseDate;
GO

For 1) this is a bit clearer, shorter and a bit more efficient.
UPDATE [Songs]
SET [Likes] = COALESCE([Likes], 0) + CASE WHEN #Song_like = 1 THEN 1
WHEN #Song_like = 0 THEN -1
ELSE 0 END
WHERE [Song_ID] = #SongID;
For the second part you can do something like this:
IF NOT EXISTS (SELECT 1
FROM [Users_Likes_Songs]
WHERE [UserID] = #User_ID
AND [SongID] = #SongID)
INSERT INTO [Users_Likes_Songs] (User_ID, SongID, [LikeSong])
VALUES (#User_ID, #SongID, #Song_like)
ELSE
UPDATE [Users_Likes_Songs]
SET [LikeSong] = #Song_like WHERE ([UserID] = #User_ID) AND ([SongID] = #SongID)

You can try this query in your procedure
UPDATE [songs]
SET [likes] = Isnull ([likes], 0) + ( CASE WHEN #Song_like THEN 1 ELSE -1 END)
WHERE [song_id] = #SongID

Related

Stored procedure not inserting data into table

I'm trying to implement a stored procedure like this where it's going to compare UserID from another table. If that user id already exists in dbo.user info table, it will insert data into [dbo].[BulkUploadTagDetail] table.
Every time I execute this stored procedure running into an issue that the data doesn't get inserted into my database table.
CREATE PROCEDURE [dbo].[InsertBulkUploadTagDetail]
(#BulkTagID INT,
#PortalUserID UNIQUEIDENTIFIER,
#EmailAddress VARCHAR(256),
#BulkUploadFileName VARCHAR(256),
#CreatedOn DATETIME2(7),
#IsCompleted BIT = 0)
AS
BEGIN
SET NOCOUNT ON
IF EXISTS (SELECT UserID
FROM dbo.UserInfo
WHERE UserID = #PortalUserID)
BEGIN
UPDATE [unp]
SET [unp].[BulkTagID] = #BulkTagID,
[unp].[UserID] = #PortalUserID,
[unp].[EmailAddress] = #EmailAddress,
[unp].[BulkUploadFileName] = #BulkUploadFileName,
[unp].[CreatedOn] = GETUTCDATE(),
[unp].[IsCompleted] = #IsCompleted
FROM
[dbo].[InsertBulkUploadTagDetail] unp
INNER JOIN
[dbo].[UserInfo] uinfo ON [unp].[UserID] = [uinfo].[UserID]
WHERE
[unp].[UserID] = #PortalUserID
END
ELSE
BEGIN
INSERT INTO [dbo].[BulkUploadTagDetail]
([BulkTagID], [PortalUserID], [EmailAddress],
[BulkUploadFileName], [CreatedOn], [IsCompleted])
VALUES (#BulkTagID, #PortalUserID, #EmailAddress,
#BulkUploadFileName, #CreatedOn, #IsCompleted)
END
END

Stored procedure for finding out if student is eligible for course with pre reqs

I have this stored procedure for a course registration system I am working on. My intention is to return a value of -1 if the query returns a course which a student has not taken according to the course pre requisites.
The pre req table only has two columns; CourseID being the course and PreReqCourse_ID being the required course for that specified course. If the student has taken all pre req courses then it should return a value of 1. I keep getting a value of -1 even when I run the query for a student who has taken the required pre req courses. Any help would be much appreciated!
CREATE PROCEDURE CheckPreReq
#StudentID INT,
#CourseID INT
AS
DECLARE #theCount INT
IF EXISTS (SELECT *
FROM PreReq
INNER JOIN Student_History ON (PreReq.Course_ID = #CourseID)
WHERE Student_History.Course_ID != PreReq.PreReqCourse_ID
AND Student_History.Student_ID = #StudentID)
BEGIN
SET #theCount =-1
END
ELSE
BEGIN
SET #theCount = 1
END
RETURN #theCount
Would something like this work?
DECLARE #PreReqsTotal tinyint
DECLARE #PreReqsFulfilled tinyint
-- Count pre-req courses.
SELECT #PreReqsTotal = COUNT(*)
FROM PreReq
WHERE [CourseID] = #CourseId
-- Count how many fulfilled by student.
SELECT #PreReqsFulfilled = count(*)
FROM Student_History hist
JOIN PreReq pre
on hist.Course_ID = pre.PreReqCourse_ID
WHERE pre.CourseID = #CourseID
and hist.Student_ID = #StudentID
RETURN CASE WHEN #PreReqsTotal = #PreReqsFulfilled THEN 1 ELSE -1 END
...or something like this:
IF EXISTS
(
SELECT blah.*
FROM
(
SELECT pre.*
,[PreFulfilled] = CASE WHEN hist.Course_ID is null THEN 0 ELSE 1 END
FROM PreReq pre
LEFT JOIN
Student_History hist
on pre.PreReqCourse_ID = hist.Course_ID
and hist.Student_ID = #StudentID
WHERE pre.CourseID = #CourseID
) blah
WHERE blah.[PreFulfilled] = 0 -- Unfulfilled PreReq.
)
BEGIN
RETURN -1 -- Has an unfulfilled PreReq.
END
RETURN 1 -- No unfulfilled PreReqs.
You should JOIN the PreReq table with the Student_History on the the PreReq.PreReqCourse_ID and Student_History.CourseID columns (take a look at my example). Then your SP should work.
--create tmp example tables
IF OBJECT_ID('tempdb..#PreReq') IS NOT NULL DROP TABLE #PreReq
CREATE TABLE #PreReq(
CourseID int,
PreReqCourse_ID int
)
--insert Course 3 which depends on Course 2 and 1 in #PreReq
INSERT INTO #PreReq
values(3,2),(3,1)
IF OBJECT_ID('tempdb..#Student_History') IS NOT NULL DROP TABLE #Student_History
CREATE TABLE #Student_History(
CourseID int not null,
StudentID int not null
);
--insert Student 1 who has visited Course 1 and 2
insert into #Student_History
VALUES(1,1),(2,1)
--declare variables
DECLARE #CourseID AS INT = 3
,#StudentID AS INT = 1
--COUNT on how many Courses #CourseID depends
,#necessaryCourses AS INT
--COUNT on how many Courses the Student has taken
,#countTakenCourses AS INT
,#theCount AS INT
SET #necessaryCourses = (SELECT count(*) FROM #PreReq WHERE CourseID = #CourseID);
SET #countTakenCourses = (
SELECT count(*)
FROM #PreReq p
--JOIN with Student_History to check if the student has visited the necessary course
JOIN #Student_History h on p.PreReqCourse_ID = h.CourseID
WHERE p.CourseID = #CourseID AND h.StudentID = #StudentID
)
IF #necessaryCourses = #countTakenCourses
BEGIN
set #theCount = 1
END
ELSE
BEGIN
set #theCount = -1
END
SELECT #theCount AS theCount

SQL query – doing a loop but using an array of variables

Here is a sample of the code I'm writing
DECLARE #totlunch int = 0;
DECLARE #totbreak int = 0;
DECLARE #over int = 0;
SELECT #totlunch = SUM(CASE when [StatusKey] = 'at lunch' then StateDuration else 0 end),
#totbreak = SUM(CASE when [StatusKey] = 'break' then StateDuration else 0 end)
FROM [I3_IC].[dbo].[AgentActivityLog]
WHERE UserId in ('mdavila')
AND StatusDateTime between '2014-08-28 00:00:00' AND '2014-08-28 23:59:59'
--group by UserId
print #totlunch;
print #totbreak;
if(#totlunch > 3600)BEGIN
SET #over = #over + (#totlunch - 3600);
END
if(#totbreak > 1800)BEGIN
SET #over = #over + (#totbreak - 1800);
END
print #over;
I want to do this task for a group of "UserId"s, insterad of juat "mdavila" but I'm not sure how to loop it as SQL queries seem to not support arrays. Any help in how I can accomplish this task would be greatly appreciated. Thanks!
Please note that I need to keep track of each user individually. I will populate a temp table with the data I want, but I just need to know a way of looping this code for a group of users.
If you are using SQL Server, you can use a Table variable to loop through records.
I have simplified it here to show you the concept - it should be enough to get you started.
-- Table variable to store the list of user ID
declare #UserIds table (UserId INT)
declare #CurrentUserID INT
-- Load the table with the list of users we want to work with
INSERT INTO #UserIds (UserId)
SELECT userid
FROM AgentActivityLog
-- loop through each user
WHILE EXISTS (SELECT UserId FROM #UserIds)
BEGIN
-- select a user from the list of users
SELECT TOP 1 #CurrentUserID = UserId
FROM #UserIds
ORDER BY UserId ASC
-- Do stuff with #CurrentUserID
--Remove the current user id from the table variable - we are done with it
DELETE FROM #UserIds WHERE UserId = #CurrentUserID
END

TSQL - While in While code

Can someone please tell me why this wont work. I want to loop inside a loop .....
BEGIN
SET NOCOUNT ON;
Declare #TempLocations Table (PK int Identity(1,1) not null Primary key, LocationID Int)
Declare #TempItems Table (PK1 int Identity(1,1) not null Primary key, ItemID int)
Declare #TempTable Table (ID Int Identity(1,1), LocationID int, ItemID int)
Declare #MaxLocationID int,
#MaxItemID Int,
#LocationID int,
#ItemID int
-- Load "Can be sold from" Locations into Temp Table
Insert Into #TempLocations (LocationID)
Select LocationID from WMS.Locations
Where CanBeSoldFrom = 'Checked'
Set #MaxItemID = (Select MAX(PK1) From #TempItems)
Set #LocationID = 1
-- Load "IsActive" Items into Temp Table
Insert Into #TempItems (ItemID)
Select ItemID from IMS.ItemDetails
Where IsActive = 'Checked'
Set #MaxLocationID = (Select MAX(PK) From #TempLocations)
Set #ItemID = 1
--Main Code
While #LocationID <= #MaxLocationID
Begin
While #ItemID <= #MaxItemID
Begin
Insert into #TempTable (LocationID, ItemID)
Values (#LocationID, #ItemID)
Set #ItemID = #ItemID + 1
end
Set #LocationID = #LocationID + 1
End
Select * from #TempTable
END
The result I am Tryinig to get is this
#tempTable =
LocationID = 1
ItemID = 1
ItemID = 2
ItemID = 3
ItemID = 4
LocationID = 2
ItemID = 1
ItemID = 2
ItemID = 3
ItemID = 4
and so on ......
This shouldn't be done in a procedural code at all. Use pure SQL and let the DB engine do it's job, it will perform much better, and less code = less bugs. I'm not sure I completely understand what results you want, but I think this does it:
select
LocationID,
ItemID
from
(
Select LocationID from WMS.Locations
Where CanBeSoldFrom = 'Checked'
)
cross join
(
Select ItemID from IMS.ItemDetails
Where IsActive = 'Checked'
)
order by
LocationID,
ItemID
Your query selects the #MaxItemID before anything is filled into #TempItems. Therefor #MaxItemID is null. You have to switch the Statements Set #MaxLocationID = (Select MAX(PK) From #TempLocations) and Set #MaxItemID = (Select MAX(PK1) From #TempItems).
I agree with Jeremy though, it would be better to do that with set-based-programming.

Sql Optimization on advertising system

I am currently developing on an advertising system, which have been running just fine for a while now, apart from recently when our views per day have shot up from about 7k to 328k. Our server cannot take the pressure on this anymore - and knowing that I am not the best SQL guy around (hey, I can make it work, but not always in the best way) I am asking here for some optimization guidelines. I hope that some of you will be able to give rough ideas on how to improve this - I don't specifically need code, just to see the light :).
As it is at the moment, when an advert is supposed to be shown a PHP script is called, which in return calls a stored procedure. This stored procedure does several checks, it tests up against our customer database to see if the person showing the advert (given by a primary key id) is an actual customer under the given locale (our system is running on several languages which are all run as separate sites). Next up is all the advert details fetched out (image location as an url, height and width of the advert) - and lest step calls a separate stored procedure to test if the advert is allowed to be shown (is the campaign expired by either date or number of adverts allowed to show?) and if the customer has access to it (we got 2 access systems running, a blacklist and a whitelist one) and lastly what type of campaign we're running, is the view unique and so forth.
The code consists of a couple of stored procedures that I will post in here.
--- procedure called from PHP
CREATE PROCEDURE [dbo].[ExecView]
(
#publisherId bigint,
#advertId bigint,
#localeId int,
#ip varchar(15),
#ipIsUnique bit,
#success bit OUTPUT,
#campaignId bigint OUTPUT,
#advert varchar(500) OUTPUT,
#advertWidth int OUTPUT,
#advertHeight int OUTPUT
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #unique bit
DECLARE #approved bit
DECLARE #publisherEarning money
DECLARE #advertiserCost money
DECLARE #originalStatus smallint
DECLARE #advertUrl varchar(500)
DECLARE #return int
SELECT #success = 1, #advert = NULL, #advertHeight = NULL, #advertWidth = NULL
--- Must be valid publisher, ie exist and actually be a publisher
IF dbo.IsValidPublisher(#publisherId, #localeId) = 0
BEGIN
SELECT #success = 0
RETURN 0
END
--- Must be a valid advert
EXEC #return = FetchAdvertDetails #advertId, #localeId, #advert OUTPUT, #advertUrl OUTPUT, #advertWidth OUTPUT, #advertHeight OUTPUT
IF #return = 0
BEGIN
SELECT #success = 0
RETURN 0
END
EXEC CanAddStatToAdvert 2, #advertId, #publisherId, #ip, #ipIsUnique, #success OUTPUT, #unique OUTPUT, #approved OUTPUT, #publisherEarning OUTPUT, #advertiserCost OUTPUT, #originalStatus OUTPUT, #campaignId OUTPUT
IF #success = 1
BEGIN
INSERT INTO dbo.Stat (AdvertId, [Date], Ip, [Type], PublisherEarning, AdvertiserCost, [Unique], Approved, PublisherCustomerId, OriginalStatus)
VALUES (#advertId, GETDATE(), #ip, 2, #publisherEarning, #advertiserCost, #unique, #approved, #publisherId, #originalStatus)
END
END
--- IsValidPublisher
CREATE FUNCTION [dbo].[IsValidPublisher]
(
#publisherId bigint,
#localeId int
)
RETURNS bit
AS
BEGIN
DECLARE #customerType smallint
DECLARE #result bit
SET #customerType = (SELECT [Type] FROM dbo.Customer
WHERE CustomerId = #publisherId AND Deleted = 0 AND IsApproved = 1 AND IsBlocked = 0 AND LocaleId = #localeId)
IF #customerType = 2
SET #result = 1
ELSE
SET #result = 0
RETURN #result
END
-- Fetch advert details
CREATE PROCEDURE [dbo].[FetchAdvertDetails]
(
#advertId bigint,
#localeId int,
#advert varchar(500) OUTPUT,
#advertUrl varchar(500) OUTPUT,
#advertWidth int OUTPUT,
#advertHeight int OUTPUT
)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT #advert = T1.Advert, #advertUrl = T1.TargetUrl, #advertWidth = T1.Width, #advertHeight = T1.Height FROM Advert as T1
INNER JOIN Campaign AS T2 ON T1.CampaignId = T2.Id
WHERE T1.Id = #advertId AND T2.LocaleId = #localeId AND T2.Deleted = 0 AND T2.[Status] <> 1
IF #advert IS NULL
RETURN 0
ELSE
RETURN 1
END
--- CanAddStatToAdvert
CREATE PROCEDURE [dbo].[CanAddStatToAdvert]
#type smallint, --- Type of stat to add
#advertId bigint,
#publisherId bigint,
#ip varchar(15),
#ipIsUnique bit,
#success bit OUTPUT,
#unique bit OUTPUT,
#approved bit OUTPUT,
#publisherEarning money OUTPUT,
#advertiserCost money OUTPUT,
#originalStatus smallint OUTPUT,
#campaignId bigint OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #campaignLimit int
DECLARE #campaignStatus smallint
DECLARE #advertsLeft int
DECLARE #campaignType smallint
DECLARE #campaignModeration smallint
DECLARE #count int
SELECT #originalStatus = 0
SELECT #success = 1
SELECT #approved = 1
SELECT #unique = 1
SELECT #campaignId = CampaignId FROM dbo.Advert
WHERE Id = #advertId
IF #campaignId IS NULL
BEGIN
SELECT #success = 0
RETURN
END
SELECT #campaignLimit = Limit, #campaignStatus = [Status], #campaignType = [Type], #publisherEarning = PublisherEarning, #advertiserCost = AdvertiserCost, #campaignModeration = ModerationType FROM dbo.Campaign
WHERE Id = #campaignId
IF (#type <> 0 AND #type <> 2 AND #type <> #campaignType) OR ((#campaignType = 0 OR #campaignType = 2) AND (#type = 1)) -- if not a click or view type, then type must match the campaign (ie, only able to do leads on lead campaigns, no isales or etc), click and view campaigns however can do leads too
BEGIN
SELECT #success = 0
RETURN
END
-- Take advantage of the fact that the variable only gets touched if there is a record,
-- which is supposed to override the existing one, if there is one
SELECT #publisherEarning = Earning FROM dbo.MapCampaignPublisherEarning
WHERE CanpaignId = #campaignId AND PublisherId = #publisherId
IF #campaignStatus = 1
BEGIN
SELECT #success = 0
RETURN
END
IF NOT #campaignLimit IS NULL
BEGIN
SELECT #advertsLeft = AdvertsLeft FROM dbo.Campaign WHERE Id = #campaignId
IF #advertsLeft < 1
BEGIN
SELECT #success = 0
RETURN
END
END
IF #campaignModeration = 0 -- blacklist
BEGIN
SELECT #count = COUNT([Status]) FROM dbo.MapCampaignModeration WHERE CampaignId = #campaignId AND PublisherId = #publisherId AND [Status] = 3
IF #count > 0
BEGIN
SELECT #success = 0
RETURN
END
END
ELSE -- whitelist
BEGIN
SELECT #count = COUNT([Status]) FROM dbo.MapCampaignModeration WHERE CampaignId = #campaignId AND PublisherId = #publisherId AND [Status] = 2
IF #count < 1
BEGIN
SELECT #success = 0
RETURN
END
END
IF #ipIsUnique = 1
BEGIN
SELECT #unique = 1
END
ELSE
BEGIN
IF (SELECT COUNT(T1.Id) FROM dbo.Stat AS T1
INNER JOIN dbo.IQ_Advert AS T2
ON T1.AdvertId = T2.Id
WHERE T2.CampaignId = #campaignId
AND T1.[Type] = #type
AND T1.[Unique] = 1
AND T1.PublisherCustomerId = #publisherId
AND T1.Ip = #ip
AND DATEADD(SECOND, 86400, T1.[Date]) > GETDATE()
) = 0
SELECT #unique = 1
ELSE
BEGIN
SELECT #unique = 0, #originalStatus = 1 -- not unique, and set status to be ip conflict
END
END
IF #unique = 0 AND #type <> 0 AND #type <> 2
BEGIN
SELECT #unique = 1, #approved = 0
END
IF #originalStatus = 0
SELECT #originalStatus = 5
IF #approved = 0 OR #type <> #campaignType
BEGIN
SELECT #publisherEarning = 0, #advertiserCost = 0
END
END
I am thinking this needs more than just a couple of indexes thrown in to help it, but rather a total rethinking of how to handle it. I have been heard that running this as a batch would help, but I am not sure how to get this implemented, and really not sure if i can implement it in a such way where I keep all these nice checks before the actual insert or if I have to give up on some of this.
Anyhow, all help would be appreciated, if you need any of the table layouts, let me know :).
Thanks for taking the time to look at it :)
Make sure to reference tables with the ownership prefix. So instead of:
INNER JOIN Campaign AS T2 ON T1.CampaignId = T2.Id
Use
INNER JOIN dbo.Campaign AS T2 ON T1.CampaignId = T2.Id
That will allow the database to cache the execution plan.
Another possibility is to disable database locking, which has data integrity risks, but can significantly increase performance:
INNER JOIN dbo.Campaign AS T2 (nolock) ON T1.CampaignId = T2.Id
Run a sample query in SQL Analyzer with "Show Execution Plan" turned on. This might give you a hint as to the slowest part of the query.
it seems like FetchAdvertDetails hit the same tables as the start of CanAddStatToAdvert (Advert and Campaign). If possible, I'd try to eliminate FetchAdvertDetails and roll its logic into CanAddStatToAdvert, so you don't have the hit Advert and Campaign the extra times.
Get rid of most of the SQL.
This stored procedure does several
checks, it tests up against our
customer database to see if the person
showing the advert (given by a primary
key id) is an actual customer under
the given locale (our system is
running on several languages which are
all run as separate sites). Next up is
all the advert details fetched out
(image location as an url, height and
width of the advert) - and lest step
calls a separate stored procedure to
test if the advert is allowed to be
shown (is the campaign expired by
either date or number of adverts
allowed to show?) and if the customer
has access to it (we got 2 access
systems running, a blacklist and a
whitelist one) and lastly what type of
campaign we're running, is the view
unique and so forth.
Most of this should not be done in the database for every request. In particular:
Customer and local can be stored in memory. Expire them after 5 minutes or so, but do not ask for this info on every repetitive request.
Advert details can also be stored. Every advert will have a "key" to identify it (Number)?. Ust a dictionary / hashtable in memory.
Eliminate as many SQL Parts as you can. Dumping repetitive work on the SQL Database is a typical mistake.