store timestamp of an action in a table - sql

I have a query here :
alter proc spbadge
#WeekNumber nvarchar(255)
as
begin
update pointsbadge
set
badge1 =(case when WeeklyHoursLogged >= '40' then 1 else 0 end),
badge2 = (case when WeeklyHoursLogged<'40' then 1 else 0 end),
badge3 = (case WHEN WeeklyHoursLogged >= 50 and WeeklyHoursLogged < 55 THEN '1' else 0 end),
badge4 = (case WHEN WeeklyHoursLogged >= 55 and WeeklyHoursLogged < 60 THEN '1' else 0 end),
badge5 = (case WHEN WeeklyHoursLogged >= 60 THEN '1' else 0 end);
end
Now,after running the above query with the conditions,I have this updated "pointsBadge" table -
Scenario :
Here you will see 5 badges (badge1 till badge5). When any employee is rewarded a badge it becomes 1, else 0. For example: Brinda wins all the badges, whereas lyka wins only badge1.
Problem : I want to have an another table "employee_badge" - where whenever any employee is rewarded a badge ,i.e whenever her value is 1,in this "employee_badge" table ,the employeeid,badgeid and the time gets recorded.
For instance like this,
I am storing the image and ID of the badges in a different table like this :
How do you guys think this can be achieved.can I modify the above stored procedure or you suggest a better efficient solution for this .I believe I need to use an update trigger here.But ,how will I use it on the above stored procedure.

Please check out with the following code, if it satisfies your requirement: you can adjust query according to your table structure and data
--Badge Point Information
CREATE TABLE pointsBadge(EmployeeName VARCHAR(50), EmployeeID INT, badge1 INT, badge2 INT, badge3 INT, badge4 INT, badge5 INT)
INSERT INTO pointsBadge VALUES
('Lyka', 1122, 1, 1, 0, 0, 0),
('Brinda', 2233, 0, 0, 0, 0, 0),
('Anil', 34, 1, 1, 0, 0, 1)
--New table to store badge data employee wise with current timestamp
CREATE TABLE employee_badge(id INT IDENTITY(1,1), employee_id INT, badge_id INT, earned_on DATETIME)
--Badge Information Table
CREATE TABLE #badges(BadgeId INT IDENTITY(1, 1), BadgeName VARCHAR(50))
INSERT INTO #badges VALUES
('Badge1'),
('Badge2'),
('Badge3'),
('Badge4'),
('Badge5'),
('Badge6')
--Trigger to insert data into employee_badge table with updated data
IF OBJECT_ID('[dbo].[TRGUpdate_badge_info]', 'TR') IS NOT NULL
DROP TRIGGER dbo.TRGUpdate_badge_info
GO
CREATE TRIGGER dbo.TRGUpdate_badge_info ON dbo.pointsBadge
FOR UPDATE
AS
INSERT INTO employee_badge
SELECT d.EmployeeID,
b.BadgeId,
GETDATE()
FROM pointsBadge p
INNER JOIN DELETED d ON p.EmployeeID = d.EmployeeID
INNER JOIN #badges b ON b.BadgeName = CASE WHEN (p.badge1 <> d.badge1) THEN 'Badge1'
WHEN (p.badge2 <> d.badge2) THEN 'Badge2'
WHEN (p.badge3 <> d.badge3) THEN 'Badge3'
WHEN (p.badge4 <> d.badge4) THEN 'Badge4'
WHEN (p.badge5 <> d.badge5) THEN 'Badge5'
END
GO
UPDATE pointsBadge SET badge5 = 2 WHERE employeeID = 1122
SELECT * FROM employee_badge

Related

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

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

Trigger functionality doesn't work as expected for History insertion

I have a trigger that contains 2 main functions
The first function works good witch it upgrade AccountType, and the other one not stable witch insert a history for this operation in another Table--> AccountTypeMonior,
Create TRIGGER [dbo].[CustomerAccountUpgrade]
ON [dbo].[Customer]
AFTER UPDATE
AS
Declare
#None nvarchar(10) = 'None',
#Standard nvarchar(10) = 'Standard',
#Basic nvarchar(10) ='Basic',
#Classic nvarchar(10) ='Classic',
#Golden nvarchar(10) ='Golden',
#Platinum nvarchar(10) = 'Platinum'
-- Regular Customers
BEGIN
BEGIN
UPDATE dbo.Customer
SET AccountTypeID =
Case
When (i.TotalSales < 0)
Then 0
When (i.TotalSales = 0)
Then 10
When (i.TotalSales BETWEEN 1 AND 5000)
Then 8
When (i.TotalSales BETWEEN 5000.01 AND 10000)
Then 3
When (i.TotalSales BETWEEN 10000.01 AND 20000)
Then 4
When (i.TotalSales > 20000)
Then 5
End
FROM Inserted i JOIN Deleted d ON i.ID = d.ID -- use the primary key here
WHERE d.AccountNumber = i.AccountNumber
AND i.TotalSales != d.TotalSales -- TotalSales was updated
AND dbo.Customer.ID = i.ID -- use the PK here
AND i.CustomText1 != '1' AND i.CustomText2 ! = 'S' -- Not Assinged or Starhouse
-- After Update Insert History to Account Type Monitor Table
INSERT INTO AccountTypeMonitor (ReferenceID,ReferenceType,OldSales,NewSales ,[Status] ,FromType,ToType,DateCreated )
(SELECT
i.ID,'AccountType',d.TotalSales,i.TotalSales,
Case
When i.TotalSales > d.TotalSales Then
'Upgrade'
When i.TotalSales < d.TotalSales Then
'Downgrade'
End
,
Case
When d.AccountTypeID = 0
Then #None
When d.AccountTypeID = 3
Then #Classic
When d.AccountTypeID =4
Then #Golden
When d.AccountTypeID = 5
Then #Platinum
When d.AccountTypeID = 8
Then #Basic
End,
Case
When (i.AccountTypeID = 0)
Then #None
When (i.AccountTypeID = 8)
Then #Standard
When (i.AccountTypeID = 8)
Then #Basic
When (i.AccountTypeID = 3)
Then #Classic
When (i.AccountTypeID =4)
Then #Golden
When (i.AccountTypeID =5)
Then #Platinum
End,
GetDate()
FROM Inserted i JOIN Deleted d ON i.ID = d.ID -- use the primary key here
WHERE d.AccountNumber = i.AccountNumber
AND i.TotalSales != d.TotalSales -- TotalSales was updated
AND i.CustomText1 != '1' AND i.CustomText2 ! = 'S'
)
END
Problem:
when i make a transaction for the customer its upgrade well, but i set wrong data in the history table
like this
001 New Test Regular 0 11000 Upgrade Golden Golden 2015-07-25 11:52:35.840 Account Number: 001 has Upgrade from Golden to Golden
Which should be for example:
Basic To Golden
NOT
Golden To Golden
I suspect that the problem is with your AccountTypeID handling in the cursor vs the actual update clause you're running. Since you haven't included everything it's just a guess, but there's nothing else in your code you have included in the question that would explain it.
The solution is simple, remove the updating logic for AccountTypeID from the cursor, because that's the wrong way to do it anyhow. Drop the column from the table, and add a computed column to replace it:
alter table Customer add AccountTypeID as
Case
When TotalSales < 0 Then 0
When TotalSales = 0 Then 10
When TotalSales <= 5000 Then 8
When TotalSales <= 10000 Then 3
When TotalSales <= 20000 Then 4
else 5
end
This way the data is up-to-date all the time without any triggers.

SQL Create View and using it in Function

I have the following function and I need to take out the SELECT part and create a separate view.
CREATE FUNCTION dbo.dbf_get_penalty_points
( #pn_seq_party_id NUMERIC(18),
#pv_penalty_points_code CHAR(1) = 'Y') -- Use 'N' for total points, otherwise will return Current Penalty Points
RETURNS NUMERIC(18,0)
AS
BEGIN
DECLARE #n_penalty_points NUMERIC(18),
#d_latest_points_date DATETIME
SELECT #d_latest_points_date = dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate()))
SELECT #n_penalty_points = IsNull(Sum(penalty_points_amount),0)
FROM dbo.ar_penalty_point WITH(NOLOCK)
WHERE seq_party_id = #pn_seq_party_id
AND 1 = CASE
WHEN #pv_penalty_points_code = 'N' THEN 1
WHEN #pv_penalty_points_code = 'Y' AND added_date >= #d_latest_points_date AND reset_date IS NULL THEN 1
ELSE 0
END
RETURN #n_penalty_points
END
GO
SET QUOTED_IDENTIFIER OFF
GO
GRANT EXECUTE ON dbo.dbf_get_penalty_points TO standard
GO
I have tried and got this,
SELECT SUM(CASE WHEN added_date >=dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate()))
AND reset_date IS NULL THEN 1
ELSE 0) current_points,
IsNull(Sum(penalty_points_amount),0) total_points,
seq_party_id
FROM dbo.ar_penalty_point WITH(NOLOCK)
GROUP BY seq_party_id
Now I need to get rid of
dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate()))
From the SELECT part of the query. I am struck is there a better way to write my view ?
EDIT
The objective is to create a view that returns total_points and current_points. For better understanding refer the CREATE part following
CREATE FUNCTION dbo.dbf_get_penalty_points
( #pn_seq_party_id NUMERIC(18),
#pv_penalty_points_code CHAR(1) = 'Y') -- Use 'N' for total points, otherwise will return Current Penalty Points
Refer to -- Use 'N' for total points, otherwise will return Current Penalty Points in the comments
This is what I came up with
SELECT SUM(CASE WHEN (t.added_date >= t.target_date
AND t.reset_date IS NULL) THEN 1
ELSE 0 END) current_points,
IsNull(Sum(t.penalty_points_amount),0) total_points,
t.seq_party_id
FROM (
SELECT dbo.dbf_trunc_date(DateAdd(mm, - Abs(Convert(NUMERIC(18,0),dbo.dbf_get_sys_param('CMS2', 'PP_MONTHS'))), GetDate())) as target_date,
u.reset_date, u.penalty_points_amount,u.seq_party_id,u.added_date FROM
dbo.ar_penalty_point as u ) as t GROUP BY t.seq_party_id

How to combine the values of the same field from several rows into one string in a one-to-many select?

Imagine the following two tables:
create table MainTable (
MainId integer not null, -- This is the index
Data varchar(100) not null
)
create table OtherTable (
MainId integer not null, -- MainId, Name combined are the index.
Name varchar(100) not null,
Status tinyint not null
)
Now I want to select all the rows from MainTable, while combining all the rows that match each MainId from OtherTable into a single field in the result set.
Imagine the data:
MainTable:
1, 'Hi'
2, 'What'
OtherTable:
1, 'Fish', 1
1, 'Horse', 0
2, 'Fish', 0
I want a result set like this:
MainId, Data, Others
1, 'Hi', 'Fish=1,Horse=0'
2, 'What', 'Fish=0'
What is the most elegant way to do this?
(Don't worry about the comma being in front or at the end of the resulting string.)
There is no really elegant way to do this in Sybase. Here is one method, though:
select
mt.MainId,
mt.Data,
Others = stuff((
max(case when seqnum = 1 then ','+Name+'='+cast(status as varchar(255)) else '' end) +
max(case when seqnum = 2 then ','+Name+'='+cast(status as varchar(255)) else '' end) +
max(case when seqnum = 3 then ','+Name+'='+cast(status as varchar(255)) else '' end)
), 1, 1, '')
from MainTable mt
left outer join
(select
ot.*,
row_number() over (partition by MainId order by status desc) as seqnum
from OtherTable ot
) ot
on mt.MainId = ot.MainId
group by
mt.MainId, md.Data
That is, it enumerates the values in the second table. It then does conditional aggregation to get each value, using the stuff() function to handle the extra comma. The above works for the first three values. If you want more, then you need to add more clauses.
Well, here is how I implemented it in Sybase 13.x. This code has the advantage of not being limited to a number of Names.
create proc
as
declare
#MainId int,
#Name varchar(100),
#Status tinyint
create table #OtherTable (
MainId int not null,
CombStatus varchar(250) not null
)
declare OtherCursor cursor for
select
MainId, Name, Status
from
Others
open OtherCursor
fetch OtherCursor into #MainId, #Name, #Status
while (##sqlstatus = 0) begin -- run until there are no more
if exists (select 1 from #OtherTable where MainId = #MainId) begin
update #OtherTable
set CombStatus = CombStatus + ','+#Name+'='+convert(varchar, Status)
where
MainId = #MainId
end else begin
insert into #OtherTable (MainId, CombStatus)
select
MainId = #MainId,
CombStatus = #Name+'='+convert(varchar, Status)
end
fetch OtherCursor into #MainId, #Name, #Status
end
close OtherCursor
select
mt.MainId,
mt.Data,
ot.CombStatus
from
MainTable mt
left join #OtherTable ot
on mt.MainId = ot.MainId
But it does have the disadvantage of using a cursor and a working table, which can - at least with a lot of data - make the whole process slow.

Conditional summing in SQL

I'm having trouble getting a query to work. What I'm trying to do is sum a counter by user id but with conditions.
Currently my query gives the following output.
User ID EndDate Date Index
123 5/1/12 1/1/12 -1
123 5/1/12 1/25/12 1
123 5/1/12 2/13/12 -1
456 4/1/12 1/18/12 -1
456 4/1/12 2/15/12 -1
456 4/1/12 2/18/12 1
What I want to do with this list is sum the Index by User Id but with a catch. The Index must be summed in date order, also the min value of the index is -1 and max is 1, so the values can be -1, 0, 1 only. So with user 123, the process would be -1 then you add 1 then you add -1 for a final sum of -1. But for user 456 you start with -1 then you have -1 again but the sum must remain -1 then you have 1, so the final sum is 0. Below is what I'm been trying to do but I can't figure it out. I would really appreciate some help.
DECLARE #Period char(6)
DECLARE #StatusCount int
SET #Period = '201201'
SET #StatusCount = 0
SELECT Q1.UserID, Q1.End_Date,
Sum(Case
When Index = -1 Then Case When #IndexCount >=0 Then #IndexCount - 1 Else #IndexCount + 0 End
When Index = 1 Then Case When #IndexCount <=0 Then #IndexCount + 1 Else #IndexCount + 0 End
END) as FinalIndex
FROM
(
(SELECT UserID, End_Date, Enter_Dt, 1 as Index
FROM UserTable
WHERE (Code in ('A', 'B') and PRD = #Period)
GROUP BY UserID, End_Date, Enter_Dt)
UNION
(SELECT UserID, End_Date, Enter_Dt, -1 as Index
FROM UserTable
WHERE (Code in ('C', 'D') and PRD = #Period)
GROUP BY UserID, End_Date, Enter_Dt)
) as Q1
GROUP BY Q1.UserID, Q1.End_Date
ORDER BY Q1.UserID ASC, Q1.End_Date ASC
I think my main problem is I can't figure out how to accumulate the Index properly. I can't get IndexCount to remember the the previous value and then start again from 0 with the next User ID
The result I get with this query is
User ID EndDate Index
123 5/01/12 -1
456 4/01/12 -1
Which is just summing the Index
I'll illustrate a running total by userID solution here, leaving out the other details of your query for clarity. Basically, add a IndexRT column populate it with a running total that resets for each new userID.
EDIT: constrain running total to -1,0,1
create table #temp(userID int, [Index] int, IndexRT int)
insert into #temp (userID,[Index]) values (123,-1) , (123,1) , (123,-1) , (456,-1) , (456,-1) , (456,1)
declare #rt int; set #rt=0;
with a as (
select select TOP 100 percent *
,r=ROW_NUMBER()over(partition by userID order by userID)
from #temp order by userID,ROW_NUMBER()over(partition by userID order by userID)
)
update a set #rt = [IndexRT] = case when case when r=1 then [Index] else #rt + [Index] end between -1 and 1
then case when r=1 then [Index] else #rt + [Index] end
else #rt
end
select * from #temp;
go
drop table #temp;
Result:
userID Index IndexRT
----------- ----------- -----------
123 -1 -1
123 1 0
123 -1 -1
456 -1 -1
456 -1 -1
456 1 0
Sounds like you need to define a cycling sequence:
http://www.postgresql.org/docs/8.1/static/sql-createsequence.html
This will create the effect you've described in your problem, cycling -1,0,1 as rows iterate. You can achieve this in a select or upon inserting the record in your table.
This uses cursors, and seemed to work in SQL 2005 (I would advise checking your results thoroughly in case I missed something obvious here).
As a disclaimer, most database experts will tell you that cursors are horrible, inefficient, and should never be used. And yes-- you should use straight SELECT statements when possible. However: (1) I don't know that there is a straightforward SELECT statement for this type of accumulation, and (2) this will be a much more readable solution for anyone who has to maintain your code. If your data set is not unreasonably large (and you don't expect it to grow too much), cursors should be OK.
Note that I have substituted "Table3" for your entire FROM statement; I imported your sample table to create this example. The key element to note here is that I am sorting it first by User ID, then by Date in the DECLARE csrOrdered CURSOR statement. So you should just need to substitute your own sorted SQL statement in the cursor definition.
CREATE TABLE #sumResult (
[User ID] int,
[Sum Result] int
)
DECLARE csrOrdered CURSOR FOR
SELECT [User Id], [Index] FROM Table3 -- <- your table name goes here!
ORDER BY [User ID], Date, EndDate
DECLARE #uid int
DECLARE #index int
DECLARE #lastUid int
DECLARE #curSum int
SET #uid=0
SET #index=0
SET #lastUid=0
SET #curSum=0
OPEN csrOrdered
FETCH NEXT FROM csrOrdered
INTO #uid, #index
WHILE ##FETCH_STATUS=0
BEGIN
--Are we working on a new User ID? Then reset the sum and insert the last group into the table.
IF #uid<>#lastUid BEGIN
--Don't do an insert if we just got into the loop
IF #lastUid <> 0 BEGIN
INSERT INTO #sumResult ([User ID], [Sum Result]) VALUES (#lastUid, #curSum)
END
SET #curSum=0
SET #lastUid=#uid
END
SET #curSum = #curSum + #index
IF #curSum < -1 SET #curSum=-1
IF #curSum > 1 SET #curSum=1
FETCH NEXT FROM csrOrdered
INTO #uid, #index
END
IF #lastUid <> 0 BEGIN
INSERT INTO #sumResult ([User ID], [Sum Result]) VALUES (#lastUid, #curSum)
END
CLOSE csrOrdered
DEALLOCATE csrOrdered
SELECT * FROM #sumResult
DROP TABLE #sumResult