Bulk create rows with a foreign key dependency? - sql-server

I wrote some SQL statements that work for updating a single customer. I have to update all the customers when this code gets pushed out.
Right now the customer ID is hardcoded and the SQL statements insert one record based on that ID. Prototype works, now I want to do like 10,000 inserts for all of the customers using the same algorithm.
DECLARE #customerID BIGINT = 47636;
DECLARE #limitFourAdjustment MONEY;
DECLARE #appliesToDateTime DATETIME2(7) = SYSUTCDATETIME();
DECLARE #dp_y INT = DATEPART(YEAR, #appliesToDateTime);
DECLARE #dp_m INT = DATEPART(MONTH, #appliesToDateTime);
DECLARE #dp_w INT = DATEPART(WEEK, #appliesToDateTime);
DECLARE #dp_d INT = DATEPART(DAY, #appliesToDateTime);
DECLARE #dp_h INT = DATEPART(HOUR, #appliesToDateTime);
DECLARE #d_h DATETIME2(7) = DATEADD(HOUR, DATEDIFF(HOUR, 0, #appliesToDateTime), 0);
SELECT
#limitFourAdjustment = -COALESCE(SUM(COALESCE(Amount, 0)), 0)
FROM
[dbo].Transactions
WHERE
CustomerID = #customerID AND
IsSystemVoid = 0 AND
TransactionTypeID IN (SELECT ID FROM TransactionTypes WHERE TransactionTypeGroupID = 3)
INSERT INTO dbo.CustomerAccounts_TransactionSummation (CustomerID, LimitTypeID, Y, M, W, D, H, YMDH, Amount)
VALUES (#customerID, 4, #dp_y, #dp_m, #dp_w, #dp_d, #dp_h, #d_h, #limitFourAdjustment);
I tried adding a while loop, seems like not the fastest solution. Maybe collect the ID's first and then feed it to through the loop? My first attempt below doesn't work because I just get the last customer ID, not a unique one every time.
SELECT #numberOfCustomers = COUNT(*)
FROM dbo.Customers
WHILE(#numberOfCustomers > 0)
BEGIN
SELECT #customerID = ID FROM dbo.Customers
OTHER LOGIC FROM ABOVE
SET #numberOfCustomers = #numberOfCustomers - 1;
END
So the question is, how to run these SQL statements (first code block) on every customer's ID?

The key to working with databases is getting your mind around set based operations as opposed to procedural operations. Databases are designed to operate naturally on sets of data at a time, but you have to change how you think about the problem to one where you are manipulating the entire set of data as opposed to one record at a time.
So here is the SQL which I think carry out your complete update in one hit:
INSERT INTO dbo.CustomerAccounts_TransactionSummation (CustomerID, LimitTypeID, Y, M, W, D, H, YMDH, Amount)
SELECT
id
, 4
, #dp_y
, #dp_m
, #dp_w
, #dp_d
, #dp_h
, #d_h
, -COALESCE(SUM(COALESCE(Amount, 0)), 0) limitFourAdjustment
FROM [dbo].Transactions
WHERE IsSystemVoid = 0
and TransactionTypeID IN (SELECT ID FROM TransactionTypes WHERE TransactionTypeGroupID = 3)
--and CustomerID = #customerID
Note that the insert can be combined directly with a select as opposed to using values.

Related

Allocate Unique Random case number SQL 2008

I have a list of teams in one table and list of cases in another table. I have to allocate a unique random case number to each one of the members in the team. What is the best way to generate unique random case number for each team member. I have read about NewID() and CRYPT_GEN_RANDOM(4) functions. I tried using them but not getting unique number for each team member. Can some one please help me. Thanks for your time. I am using SQL 2008.
I have a 'Teams' table which has team members, their ids(TM1,TM2 etc.) and their names.
I have another 'Cases' table which has ID numbers like 1,2,3,4 etc. I want to allocate random case to each team member. The desired output should be as below.
Team member Random_case_allocated
TM1 3
TM2 5
TM3 7
TM4 2
TM5 8
TM6 6
I have tried
SELECT TOP 1 id FROM cases
ORDER BY CRYPT_GEN_RANDOM(4)
It is giving the same id for all team members. I want a different case id for each team member. Can someone please help. Thank you.
The TOP(1) ORDER BY NEWID() will not work the way you are trying to get it to work here. The TOP is telling the query engine you are only interested on the first record of the result set. You need to have the NEWID() evaluate for each record. You can force this inside of a window function, such as ROW_NUMBER(). This could optimized I would imagine, however, it was what I could come up with from the top of my head. Please note, this is not nearly a truly random algorithm.
UPDATED With Previous Case Exclusions
DECLARE #User TABLE(UserId INT)
DECLARE #Case TABLE(CaseID INT)
DECLARE #UserCase TABLE (UserID INT, CaseID INT, DateAssigned DATETIME)
DECLARE #CaseCount INT =10
DECLARE #SaveCaseID INT = #CaseCount
DECLARE #UserCount INT = 100
DECLARE #NumberOfUserAllocatedAtStart INT= 85
WHILE(#CaseCount > 0)BEGIN
INSERT #Case VALUES(#CaseCount)
SET #CaseCount = #CaseCount-1
END
DECLARE #RandomCaseID INT
WHILE(#UserCount > 0)BEGIN
INSERT #User VALUES(#UserCount)
SET #UserCount = #UserCount-1
IF(#NumberOfUserAllocatedAtStart > 0 )BEGIN
SET #RandomCaseID = (ABS(CHECKSUM(NewId())) % (#SaveCaseID))+1
INSERT #UserCase SELECT #UserCount,#RandomCaseID,DATEADD(MONTH,-3,GETDATE())
SET #RandomCaseID = (ABS(CHECKSUM(NewId())) % (#SaveCaseID))+1
INSERT #UserCase SELECT #UserCount,#RandomCaseID,DATEADD(MONTH,-5,GETDATE())
SET #RandomCaseID = (ABS(CHECKSUM(NewId())) % (#SaveCaseID))+1
INSERT #UserCase SELECT #UserCount,#RandomCaseID,DATEADD(MONTH,-2,GETDATE())
SET #NumberOfUserAllocatedAtStart=#NumberOfUserAllocatedAtStart-1
END
END
;WITH RowNumberWithNewID AS
(
SELECT
U.UserID, C.CaseID, UserCase_CaseID = UC.CaseID,
RowNumber = ROW_NUMBER() OVER (PARTITION BY U.UserID ORDER BY NEWID())
FROM
#User U
INNER JOIN #Case C ON 1=1
LEFT OUTER JOIN #UserCase UC ON UC.UserID=U.UserID AND UC.CaseID=C.CaseID AND UC.DateAssigned > DATEADD(MONTH, -4, UC.DateAssigned)
WHERE
UC.CaseID IS NULL OR UC.CaseID <> C.CaseID
)
SELECT
UserID,
CaseID,
PreviousCases = STUFF((SELECT ', '+CONVERT(NVARCHAR(10), UC.CaseID) FROM #UserCase UC WHERE UC.UserID=RN.UserID FOR XML PATH('')),1,1,'')
FROM RowNumberWithNewID RN
WHERE
RN.RowNumber=1

INSERT SELECT SQL Query with Variables

I am trying to INSERT record(s) where some of the fields in the record(s) come from one table and the rest of the fields are created from variables. Here is the INSERT SELECT query where I am trying to create the record(s):
DECLARE #projid INT
,#status INT
,#created DATETIME
,#duedate DATETIME
,#numdays INT
,#divid INT
SET #divid =
(SELECT DIVID
FROM DIV
WHERE StepOrder=1)
SET #numdays =
(SELECT CompTarget
FROM DIV
WHERE DIVID=#divid)
SET #projid = 10
SET #status = 0
SET #duedate =
DATEADD(day,#numdays,#created)
SET #created = '4/18/2017'
INSERT INTO DIVTasks (ProjectID, GroupID, Status, Created, DueDate, DIVID)
SELECT #projid, GroupID, #status, #created, #duedate, DIVID
FROM DIV
WHERE StepOrder=1
There are 3 records with the StepOrder the equals 1 and they have different "CompTarget" numbers.
What I need it to do is take the "Created" date add the "CompTarget" number and return the "DueDate" for each record.
The above query returns this error but does enter 3 records with the "DueDate" as Null:
Msg 512, Level 16, State 1, Line 8
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Both
SET #divid =
(SELECT DIVID
FROM DIV
WHERE StepOrder=1)
and
SET #numdays =
(SELECT CompTarget
FROM DIV
WHERE DIVID=#divid)
will cause the same error, since you wrote there are 3 records with StepOrder equals 1 in that table.
However, I think you are over complicating things.
Try this insert....select instead:
DECLARE #projid INT = 10
,#status INT = 0
,#created DATETIME = '2017-04-18' -- note the change of format here!
INSERT INTO DIVTasks (ProjectID, GroupID, Status, Created, DueDate, DIVID)
SELECT #projid, GroupID, #status, #created, DATEADD(day,CompTarget,#created), DIVID
FROM DIV
WHERE StepOrder=1
Note - using yyyy-mm-dd as the date's string representation format makes it unambiguous.

SQL-Server Trigger on Insert into a table, 1 column into 6 in the same table

I have spent a lot of time investigating if this can be done outside of the database but to be honest I don't think so, well not very easily. We access the data in the tables via Access 2010 using VBA so I thought I could do it
via a action in the front end software. Easy to complete however there are two many permutations I cant control.
I have a table [TableData] with multiple columns. We have some externally supplied software that populates the table about 20-30 rows at a time. One of the fields [Fluctuation] currently allows us to transfer data up to 60 chars in length and our intention is to send data in the format 1.1,1.2,1.3,1.4,1.5,1.6 where we have six numbers of up to two decimal places separated by commas, no spaces. Column names Fluc1, Fluc2, Flu3 etc.
What I would like to do is create a trigger within the SQL database that operates once the row is inserted to split the above into six new columns only if 6 values separated by five commas exist.
I then need to complete maths on the 6 values but at least i will have them to complete the numbers to complete the maths on.
I have no knowledge of triggers so any help given would be very much appreciated.
Sample data examples are:
101.23,100.45,101.56,102.89,101,74,100.25
1.05,1.09,1.05,0.99,0.99,0.98
etc
I have VBA code to split the data and was going to do this via a SELECT query after the fact but as I cant control the data being entered from the external software thought a trigger would be more useful.
VBA code.
'This function returns the string data sperated by commas
Public Function FluctuationSeperation(strFluctuationData As String) As Variant
Dim strTest As String
Dim strArray() As String
Dim intCount As Integer
strArray = Split(strFluctuationData, ",")
Dim arr(5) As Variant
For intCount = LBound(strArray) To UBound(strArray)
arr(intCount) = Trim(strArray(intCount))
Next
FluctuationSeperation = arr
End Function
When writing a trigger you need to take care that it can launch for multiple inserted rows. There is inserted built in table alias available for that purpose. You need to iterate through all the inserted records and update them individually. You need to use your primary key (I have assumed a column id) to match inserted records with records to update.
CREATE TRIGGER TableData_ForInsert
ON [TableData]
AFTER INSERT
AS
BEGIN
DECLARE #id int
DECLARE #Fluctuation varchar(max)
DECLARE i CURSOR FOR
SELECT id, Fluctuation FROM inserted
FETCH NEXT FROM i INTO #id, #Fluctuation
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #pos1 int = charindex(',',#Fluctuation)
DECLARE #pos2 int = charindex(',',#Fluctuation, #pos1+1)
DECLARE #pos3 int = charindex(',',#Fluctuation, #pos2+1)
DECLARE #pos4 int = charindex(',',#Fluctuation, #pos3+1)
UPDATE [TableData]
SET fluc1 = ltrim(substring(#Fluctuation,1,#pos1-1)),
fluc2 = ltrim(substring(#Fluctuation,#pos1+1,#pos2-#pos1-1)),
fluc3 = ltrim(substring(#Fluctuation,#pos2+1,#pos3-#pos2-1)),
fluc4 = ltrim(substring(#Fluctuation,#pos3+1,#pos4-#pos3-1)),
fluc5 = ltrim(substring(#Fluctuation,#pos4+1,999))
WHERE id = #id -- need to find TableData record to update by inserted id
FETCH NEXT FROM i INTO #id, #Fluctuation
END
END
But because cursors are in many cases considered as a bad practice, it is better to write the same as a set based command. It can be achieved with APPLY clause like this:
CREATE TRIGGER TableData_ForInsert
ON [TableData]
AFTER INSERT
AS
BEGIN
UPDATE t SET
fluc1 = SUBSTRING(t.fluctuation, 0, i1.i),
fluc2 = SUBSTRING(t.fluctuation, i1.i+1, i2.i - i1.i -1),
fluc3 = SUBSTRING(t.fluctuation, i2.i+1, i3.i - i2.i -1),
fluc4 = SUBSTRING(t.fluctuation, i3.i+1, i4.i - i3.i -1),
fluc5 = SUBSTRING(t.fluctuation, i4.i+1, 999)
FROM [TableData] t
OUTER APPLY (select charindex(',', t.fluctuation) as i) i1
OUTER APPLY (select charindex(',', t.fluctuation, i1.i+1) as i) i2
OUTER APPLY (select charindex(',', t.fluctuation, i2.i+1) as i) i3
OUTER APPLY (select charindex(',', t.fluctuation, i3.i+1) as i) i4
JOIN INSERTED new ON new.ID = t.ID -- need to find TableData record to update by inserted id
END
This code example is missing handling malformed strings, it expects allways 5 numbers delimited by 4 commas.
For more tips how to split strings in SQL Server check this link.
Test case:
DECLARE #test TABLE
(
id int,
Fluctuation varchar(max),
fluc1 numeric(9,3) NULL,
fluc2 numeric(9,3) NULL,
fluc3 numeric(9,3) NULL,
fluc4 numeric(9,3) NULL,
fluc5 numeric(9,3) NULL
)
INSERT INTO #test (id, Fluctuation) VALUES(1, '1.2,5,8.52,6,7.521')
INSERT INTO #test (id, Fluctuation) VALUES(2, '2.2,6,9.52,7,8.521')
INSERT INTO #test (id, Fluctuation) VALUES(3, '2.5,3,4.52,9,7.522')
INSERT INTO #test (id, Fluctuation) VALUES(4, '2.53,4.52,97.522') -- this fails
UPDATE t SET
fluc1 = CASE WHEN i1.i<0 THEN NULL ELSE SUBSTRING(t.fluctuation, 0, i1.i) END,
fluc2 = CASE WHEN i2.i<0 THEN NULL ELSE SUBSTRING(t.fluctuation, i1.i+1, i2.i - i1.i -1) END,
fluc3 = CASE WHEN i3.i<0 THEN NULL ELSE SUBSTRING(t.fluctuation, i2.i+1, i3.i - i2.i -1) END,
fluc4 = CASE WHEN i4.i<0 THEN NULL ELSE SUBSTRING(t.fluctuation, i3.i+1, i4.i - i3.i -1) END,
fluc5 = CASE WHEN i4.i<0 THEN NULL ELSE SUBSTRING(t.fluctuation, i4.i+1, 999) END
FROM #test t
OUTER APPLY (select charindex(',', t.fluctuation) as i) i1
OUTER APPLY (select charindex(',', t.fluctuation, i1.i+1) as i) i2
OUTER APPLY (select charindex(',', t.fluctuation, i2.i+1) as i) i3
OUTER APPLY (select charindex(',', t.fluctuation, i3.i+1) as i) i4
SELECT * FROM #test

Comparison results between two select statements in a stored procedure

I want to start off by saying that I am brand new to Stored Procedures, and am basically teaching myself how to do them. Any suggestions or advice will be greatly appreciated. I would mail you chocolate if I could.
The Gist: My organization's clients take a survey on their initial visit and on each 6th subsequent visits. We need to know if the individual has shown improvement over time. The way we decided to do this is compare the 1st to the most recent. So if they have been to 18 sessions, it would be the 1st and 3rd surveys that are compared (because they would have completed the survey 3 times over 18 sessions).
I have been able to obtain the "first" score and the "recent" score with two complex, multiple layered-nested select statements inside of one stored procedure. The "first" one is a TOP(1) linking on unique id (DOCID) and then ordered by date. The "recent" one is a TOP(1) linking on unique id (DOCID) and then ordered by date descending. This gets me exactly what I need within each statement, but it does not output what I need correctly which is obviously to the ordering in the statements.
The end result will be to create a Crystal Report with it for grant reporting purposes.
Declare
#StartDate Date,
#EndDate Date,
#First_DOCID Int,
#First_Clientkey Int,
#First_Date_Screening Date,
#First_Composite_Score Float,
#First_Depression_Score Float,
#First_Emotional_Score Float,
#First_Relationship_Score Float,
#Recent_DOCID Int,
#Recent_Clientkey Int,
#Recent_Date_Screening Date,
#Recent_Composite_Score Float,
#Recent_Depression_Score Float,
#Recent_Emotional_Score Float,
#Recent_Relationship_Score Float,
#Difference_Composit_Score Float,
#Difference_Depression_Score Float,
#Difference_Emotional_Score Float,
#Difference_Relationship_Score Float
SET #StartDate = '1/1/2016'
SET #EndDate = '6/1/2016'
BEGIN
SELECT #First_DOCID = CB24_1.OP__DOCID, #First_Date_Screening = CB24_1.Date_Screening, #First_Clientkey = CB24_1.ClientKey, #First_Composite_Score = CB24_1.Composite_score, #First_Depression_Score = CB24_1.Depression_Results, #First_Emotional_Score = CB24_1.Emotional_Results, #First_Relationship_Score = CB24_1.Relationships_Results
FROM FD__CNSLG_BASIS24 AS CB24_1
WHERE (CB24_1.OP__DOCID =
(Select TOP(1) CB24_2.OP__DOCID
...
ORDER BY CB24_2.Date_Screening))
ORDER BY ClientKey DESC
END
BEGIN
SELECT #Recent_DOCID = CB24_1.OP__DOCID, #Recent_Date_Screening = CB24_1.Date_Screening, #Recent_Clientkey = CB24_1.ClientKey, #Recent_Composite_Score = CB24_1.Composite_score, #Recent_Depression_Score = CB24_1.Depression_Results, #Recent_Emotional_Score = CB24_1.Emotional_Results, #Recent_Relationship_Score = CB24_1.Relationships_Results
FROM FD__CNSLG_BASIS24 AS CB24_1
WHERE (CB24_1.OP__DOCID =
(Select TOP(1) CB24_2.OP__DOCID
...
ORDER BY CB24_2.Date_Screening DESC))
ORDER BY ClientKey
END
SET #Difference_Composit_Score = (#Recent_Composite_Score - #First_Composite_Score)
SET #Difference_Depression_Score = (#Recent_Depression_Score - #First_Depression_Score)
SET #Difference_Emotional_Score = (#Recent_Emotional_Score - #First_Emotional_Score)
SET #Difference_Relationship_Score = (#Recent_Relationship_Score - #First_Relationship_Score)
SELECT
#First_DOCID AS First_Docid,
#First_Clientkey AS First_Clientkey,
#First_Date_Screening AS First_Date_Screening,
#First_Composite_Score AS First_Composite_Score,
#First_Depression_Score AS First_Depression_Score,
#First_Emotional_Score AS First_Emotional_Score,
#First_Relationship_Score AS First_Relationship_Score,
#Recent_DOCID AS Recent_DOCID,
#Recent_Clientkey AS Recent_Clientkey,
#Recent_Date_Screening AS Recent_Date_Screening,
#Recent_Composite_Score AS Recent_Composite_Score,
#Recent_Depression_Score AS Recent_Depression_Score,
#Recent_Emotional_Score AS Recent_Emotional_Score,
#Recent_Relationship_Score AS Recent_Relationship_Score,
#Difference_Composit_Score AS Difference_Composit_Score,
#Difference_Depression_Score AS Difference_Depression_Score,
#Difference_Emotional_Score AS Difference_Emotional_Score,
#Difference_Relationship_Score AS Difference_Relationship_Score
In SQL you don't want unnecessary declared variables.
Here's a contrived but reproducible example which utilizes common table expressions and window functions that should get you in the right direction. I created the stored procedure from the template with the necessary input parameters (which in real life you'd like to avoid).
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE dbo.Client_Improvement_Results
(#StartDate DATETIME, #EndDate DATETIME)
AS
BEGIN
SET NOCOUNT ON;
-- Insert statements for procedure here
-- You would never do this in real-life but for a simple reproducible example...
DECLARE #Survey TABLE
(
Clientkey INT,
Date_Screening DATE,
Composite_Score FLOAT
)
INSERT INTO #Survey
VALUES
(1, '2014-04-01', 42.1),
(1, '2014-04-10', 46.1),
(1, '2014-04-20', 48.1),
(2, '2014-05-10', 40.1),
(2, '2014-05-20', 30.1),
(2, '2014-05-30', 10.1)
;
--Use Common Table Expression & Window Functions to ID first/recent visit by client
WITH CTE AS (
SELECT
S.Clientkey
,S.Composite_Score
,S.Date_Screening
,First_Date_Screening = MIN(S.Date_Screening) OVER(PARTITION BY S.Clientkey)
,Recent_Date_Screening = MAX(S.Date_Screening) OVER(PARTITION BY S.Clientkey)
FROM #Survey AS S
)
--Self join of CTE with proper filters
--applied allows you to return differences in one row
SELECT
f.Clientkey
,f.First_Date_Screening
,f.Recent_Date_Screening
,Difference_Score = r.Composite_Score - f.Composite_Score
FROM
CTE AS f --first
INNER JOIN CTE AS r --recent
ON f.Clientkey = r.Clientkey
WHERE
f.Date_Screening = f.First_Date_Screening
AND r.Date_Screening = r.Recent_Date_Screening
END
GO
Here is the solution I came up with after everyone amazing advice.
I want to go back and replace the TOP(1) with another new thing I learned at some point:
select pc.*
from (select pc.*, row_number() over (partition by Clientkey, ProgramAdmitKey order by Date_Screening) as seqnum
from FD__CNSLG_BASIS24 PC) pc
where seqnum = 1
I will have to play with the above script a bit first, however. It doesn't like to be inserted into the larger script below.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
BEGIN
SET NOCOUNT ON;
Declare
#StartDate Date,
#EndDate Date
SET #StartDate = '1/1/2016'
SET #EndDate = '6/1/2016'
WITH CNSL_Clients AS (
SELECT PC_CNT.Clientkey, PC_Cnt.ProgramAdmitKey, PC_Cnt.OP__DOCID
FROM FD__Primary_Client as PC_Cnt
INNER JOIN VW__Cnsl_Session_Count_IndvFamOnly as cnt
ON PC_Cnt.Clientkey = CNT.Clientkey AND PC_Cnt.ProgramAdmitKey = CNT.ProgramAdmitKey
WHERE ((pc_CNT.StartDate between #StartDate AND #EndDate) OR (pc_CNT.StartDate <= #StartDate AND pc_CNT.ENDDate >= #StartDate) OR (pc_CNT.StartDate <= #StartDate AND pc_CNT.ENDDate is null))
AND CNT.SessionCount>=6
),
FIRST_BASIS AS (
SELECT CB24_1.OP__DOCID, CB24_1.Date_Screening, CB24_1.ClientKey, CB24_1.ProgramAdmitKey, CB24_1.Composite_score, CB24_1.Depression_Results,CB24_1.Emotional_Results, CB24_1.Relationships_Results
FROM FD__CNSLG_BASIS24 AS CB24_1
WHERE (CB24_1.OP__DOCID =
(Select TOP(1) CB24_2.OP__DOCID
FROM FD__CNSLG_BASIS24 AS CB24_2
Inner JOIN CNSL_Clients
ON CB24_2.ClientKey = CNSL_Clients.ClientKey AND CB24_2.ProgramAdmitKey = CNSL_Clients.ProgramAdmitKey
WHERE (CB24_1.ClientKey = CB24_2.ClientKey) AND (CB24_1.ProgramAdmitKey = CB24_2.ProgramAdmitKey)
ORDER BY CB24_2.Date_Screening))
),
RECENT_BASIS AS (
SELECT CB24_1.OP__DOCID, CB24_1.Date_Screening, CB24_1.ClientKey, CB24_1.ProgramAdmitKey, CB24_1.Composite_score, CB24_1.Depression_Results,CB24_1.Emotional_Results, CB24_1.Relationships_Results
FROM FD__CNSLG_BASIS24 AS CB24_1
WHERE (CB24_1.OP__DOCID =
(Select TOP(1) CB24_2.OP__DOCID
FROM FD__CNSLG_BASIS24 AS CB24_2
Inner JOIN CNSL_Clients
ON CB24_2.ClientKey = CNSL_Clients.ClientKey AND CB24_2.ProgramAdmitKey = CNSL_Clients.ProgramAdmitKey
WHERE (CB24_1.ClientKey = CB24_2.ClientKey) AND (CB24_1.ProgramAdmitKey = CB24_2.ProgramAdmitKey)
ORDER BY CB24_2.Date_Screening DESC))
)
SELECT F.OP__DOCID AS First_DOCID,R.OP__DOCID as Recent_DOCID,F.ClientKey, F.ProgramAdmitKey, F.Composite_Score AS FComposite_Score, R.Composite_Score as RComposite_Score, Composite_Change = R.Composite_Score - F.Composite_Score, F.Depression_Results AS FDepression_Results, R.Depression_Results AS RDepression_Resluts, Depression_Change = R.Depression_Results - F.Depression_Results, F.Emotional_Results AS FEmotional_Resluts, R.Emotional_Results AS REmotionall_Reslu, Emotional_Change = R.Emotional_Results - F.Emotional_Results, F.Relationships_Results AS FRelationships_Resluts, R.Relationships_Results AS RRelationships_Resluts, Relationship_Change = R.Relationships_Results - F.Relationships_Results
FROM First_basis AS F
FULL Outer JOIN RECENT_BASIS AS R
ON F.ClientKey = R.ClientKey AND F.ProgramAdmitKey = R.ProgramAdmitKey
ORDER BY F.ClientKey
END
GO

Selecting rows partially matching rows in another table

I have a table for the actions.
The table has several slots for the same time on the same day. Same action can't be booked for the same time twice. I'm trying to come up with the way to list all the IDs for an action 'A', such as every available time is listed only once, even if there are both slots available, but if 'A' is book for some time already and another slot for this time is empty, that slot wouldn't be showing.
And it comes to me that I don't know T-SQL that good.
I overcame this by selecting all the rows where 'A' is booked, selecting all distinct (date, time start and time end) which are not booked and doing check whether 'A' is already booked for this time. But all this checking is done on the software level, and those multiple requests to the server and looping in the program to perform the same job as one LIKELY SIMPLE sql request don't look very efficient to me.
If there a way to do something like:
SELECT ID FROM mytable
WHERE Action IS NULL AND (date, time_start, time_end **'ALL TOGETHER IN ONE ROW'**)
NOT IN (SELECT date, time_start, time_end FROM mytable
WHERE Action = 'A')
HAVING 'THOSE THREE BEING DISTINCT'
By other words can I select rows which partially match other rows? It would be simple if I had only one column to compare, but there are three.
In SQL Server we generally use WHILE instead of FOR. I believe what you're trying to do could be fulfilled as follows if you want to loop through the table (ideally your ID field would be the PRIMARY KEY as well). This is just inserting it into a temp table for now, but potentially it should give you the results you want:
-- DECLARE and set counters
DECLARE #curr INT, #prev INT, #max INT
SELECT #curr = 0, #prev = 0, #max = MAX(ID) FROM myTable
-- Make a simple temp table
CREATE TABLE #temp (ID INT)
-- Start looping
WHILE (#curr < #max)
BEGIN
-- Set our counter for the next row
SELECT #curr = MIN(ID) FROM myTable WHERE ID > #prev
-- Populate temp table with a self-join to compare slots
-- Slot must match on date + time but NOT have equal SLOT value
-- Will only INSERT if we meet our criteria i.e. neither slot booked
INSERT INTO #temp
SELECT DISTINCT A.ID
FROM myTable A
JOIN myTable B ON B.[Date] = A.[date] AND B.time_start = A.time_start AND B.time_end = A.time_end
WHERE A.[Action] IS NULL -- Indicates NO booking
AND B.[Action] IS NULL -- Indicates NO booking
AND A.SLOT <> B.SLOT
AND A.ID = #curr
-- Update our counter
SET #prev = #curr
END
-- Get all our records
SELECT * FROM #temp
-- Remove the sleeping dog ;)
DROP TABLE #temp
There is a little bit of redundancy here because it checks ALL rows, even if a condition has been found in the first row of that time slot, but you can tweak it from here if you need to.
You should really avoid using field names like "Date" and "Action" because these are reserved words in SQL.
You question is a bit unclear, but I think this will point you in a productive direction. SQL is designed to perform operation on sets of rows, not to loop through processing one row at a time. The following code will correlate your data into one row for each pair of slots at each date/time. You can use a CASE expression, as shown, to add a column that indicates the status of the row, and you can then add a WHERE clause, not shown, to perform any additional filtering.
-- Sample data.
declare #Samples as Table ( SampleId Int, Slot Int, EventDate Date, StartTime Time(0), EndTime Time(0), Action VarChar(10) );
insert into #Samples ( SampleId, Slot, EventDate, StartTime, EndTime, Action ) values
( 200, 1, '20150501', '00:00:00', '00:30:00', NULL ),
( 201, 2, '20150501', '00:00:00', '00:30:00', NULL ),
( 202, 1, '20150501', '00:30:00', '01:00:00', 'A' ),
( 203, 2, '20150501', '00:30:00', '01:00:00', NULL ),
( 204, 1, '20150501', '01:00:00', '01:30:00', NULL ),
( 205, 2, '20150501', '01:00:00', '01:30:00', 'A' ),
( 206, 1, '20150501', '01:30:00', '02:00:00', 'B' ),
( 207, 2, '20150501', '01:30:00', '02:00:00', 'B' );
select * from #Samples;
-- Data correleated for each date/time.
select Slot1.EventDate, Slot1.StartTime, Slot1.EndTime,
Slot1.Action as Action1, Slot2.Action as Action2,
Coalesce( Slot1.Action, Slot2.Action ) as SummaryAction,
case when Slot1.Action = Slot2.Action then 'ERROR!' else 'Okay.' end as Status
from #Samples as Slot1 inner join
#Samples as Slot2 on Slot2.EventDate = Slot1.EventDate and Slot2.StartTime = Slot1.StartTime and
Slot1.Slot = 1 and Slot2.Slot = 2;

Resources