Stored procedure to insert concatenated string into table - sql-server

I have two tables, ParentPayor and ChildPayor.
ParentID is the primary key in ParentPayor and ParentID is a foreign key in the ChildPayor table. The ChildPayor table has a column State.
I would like to create a stored procedure that concatenates each State in the ChildPayor table, and inserts the string into the ParentPayor column States, where ChildPayor.ParentID = ParentPayor.ParentID.
I just discovered STRING_AGG to concatenate:
STRING_AGG (State, ',')
FROM ChildPayors AS States
WHERE ParentPayorID = 32
But I would like to be able to concatenate all States within the ChildPayor, and insert into ParentPayor where the ParentIDs match. Does this make sense?
Something like (I know this is incorrect):
SELECT STRING_AGG (State, ',')
FROM ChildPayors, ParentPayors AS States
WHERE ParentPayors.ParentPayorID = ChildPayors.ParentPayorID
INSERT INTO ParentPayors(States)
VALUES (States)

I will often use CROSS APPLY to calculate intermediate values. Try something like:
DECLARE #ParentPayor TABLE (ParentID INT, ParentName VARCHAR(100), States VARCHAR(1000))
INSERT #ParentPayor
VALUES
(1, 'AAA', null),
(2, 'BBB', null),
(3, 'CCC', null),
(4, 'DDD', null)
DECLARE #ChildPayor TABLE (ChildID INT, ParentID INT, StateName VARCHAR(100))
INSERT #ChildPayor
VALUES
(1, 1, 'New York'),
(2, 2, 'Texas'),
(3, 2, 'Virginia'),
(4, 2, 'California'),
(5, 4, 'Virginia'),
(6, 4, 'New York')
UPDATE P
SET States = S.States
FROM #ParentPayor P
CROSS APPLY (
SELECT States = STRING_AGG(C.StateName, ', ') WITHIN GROUP(ORDER BY C.StateName)
FROM #ChildPayor C
WHERE C.ParentID = P.ParentID
) S
SELECT *
FROM #ParentPayor P
Output:
ParentID
ParentName
States
1
AAA
New York
2
BBB
California, Texas, Virginia
3
CCC
(null)
4
DDD
New York, Virginia
The UPDATE statement above would then become the body of your stored procedure.
On further reflection, a subselect could also be used. The following JOIN could be used in place of the CROSS APPLY:
LEFT JOIN (
SELECT C.ParentID, States = STRING_AGG(C.StateName, ', ') WITHIN GROUP(ORDER BY C.StateName)
FROM #ChildPayor C
GROUP BY C.ParentID
) S ON S.ParentID = P.ParentID

Related

How to create a T-SQL function to read values which have been updated as part of the same [update] transaction

I need to use a function in an update of several records.
How to make a T-SQL function read values which have been updated during that update?
Below I created a simple example demonstrating what I get.
My example: table has Category column and I build code based on a category. Function finds the last one withing the category + 1. Or 10000 if it is the first record.
It would work just fine if update is a single record, but updates all records with the same value otherwise.
Below is working code:
CREATE TABLE Tbl
(
Id INT NOT NULL PRIMARY KEY,
Category CHAR(1) NOT NULL,
Code INT NULL
)
CREATE FUNCTION dbo.GetNextAvailableCode
(#Category CHAR(1))
RETURNS INT
AS
BEGIN
DECLARE #MaxCode INT
SELECT #MaxCode = MAX(Code)
FROM dbo.Tbl
WHERE Category = #Category
SET #MaxCode = ISNULL(#MaxCode + 1, 10000);
RETURN #MaxCode
END;
GO
INSERT INTO Tbl (Id, Category)
VALUES (1, 'A'), (2, 'A'), (3, 'A'),
(4, 'B'), (5, 'B'),
(6, 'C'), (7, 'C'), (8, 'C'), (9, 'C')
SELECT * FROM Tbl
UPDATE dbo.Tbl
SET Code = dbo.GetNextAvailableCode(Category)
WHERE Code IS NULL
SELECT * FROM Tbl
GO
DROP TABLE Tbl;
DROP FUNCTION dbo.GetNextAvailableCode;
Here is the result I'm getting:
What I'd like to get is the following:
But it's possible only if next function call can see already changed values...
Any idea how to implement such thing?
Since it is not possible to use function to achieve the desired effect, I re-wrote the update to be used without function and it worked!
Here is the update which produces the result I need:
;WITH data AS (
SELECT Id, Category, Code, ROW_NUMBER() OVER (PARTITION BY Category ORDER BY Id) AS RowNo
FROM dbo.Tbl
WHERE Code IS NULL
)
, maxData AS (
SELECT Category, MAX(Code) AS MaxCode
FROM dbo.Tbl
GROUP BY Category
)
UPDATE S
SET Code = ISNULL(T.MaxCode, 10000) + S.RowNo
FROM data S JOIN maxData T ON T.Category = S.Category
WHERE S.Code IS NULL
Result:

I want to convert some data to pivot in SQL Server with join and dynamic

The table, at last, is my target.
This is my demo database
create database pvtestDb;
go
use pvtestDb;
go
create table custTransaction
(
id int,
custNum int,
value nvarchar(50)
)
go
create table customers
(
id int,
custName nvarchar(50)
)
insert into Customers(id, custName)
values (1, 'aaa'), (2, 'bbb'), (3, 'ccc'), (4, 'ddd'),
(5, 'eee'), (6, 'fff'), (7, 'ggg'), (8, 'hhh'), (9, 'iii')
insert into custTransaction (id, custNum, value)
values (1, 3, 'a'), (1, 4, 'b'), (1, 5, 'c'),
(2, 3, 'd'), (2, 4, 'e'), (2, 6, 'f'),
(3, 3, 'g'), (3, 8, 'h'), (3, 9, 'i')
select * from customers
select * from custTransaction
select custName, custNum, value
from customers
join custTransaction on custTransaction.id = customers.id
I tried code like this but not worked at all
SELECT
custNum, [a], [b], [c], [d]
FROM
customers
JOIN
custTransaction ON custTransaction.id = customers.id
PIVOT
(COUNT([custName])
FOR [custName] IN ([a], [b], [c], [d])) AS p
I need to join between the two tables in first.
Any hints would be appreciated as I am stuck with this situation
Here's approach with dynamic SQL
declare #customers varchar(8000)
declare #sql varchar(8000)
select #customers = stuff((
select ',' + quotename(custName)
from customers
for xml path('')
), 1, 1, '')
set #sql = 'select
id, ' + #customers + '
from (
select
ct.id, c.custName, ct.value
from
customers c
join custTransaction ct on ct.custNum = c.id
) t
pivot (
max(value) for custName in (' + #customers + ')
) p'
exec (#sql)
Output
id aaa bbb ccc ddd eee fff ggg hhh iii
----------------------------------------------------------------
1 NULL NULL a b c NULL NULL NULL NULL
2 NULL NULL d e NULL f NULL NULL NULL
3 NULL NULL g NULL NULL NULL NULL h i

SQL or Tableau Replace values with existing one with reference to another field

My problem with the data is
Id Name
--------
1 a
2 b
1 a
1 a
1 NULL
2 b
2 b
2 NULL
2 b
2 NULL
ID is unique to the name
I would like the NULL values to be replaced with existing Records.
Or if there are any other alternatives
I have about 2000 IDs so it needs to be automated.
The below query can use to replace the NULLs with its Id's Name, since Id is unique to the Name.
UPDATE T1 SET T1.Name = T2.ValidName
FROM TestTable T1
JOIN ( SELECT Id, MAX(Name) AS ValidName
FROM TestTable
WHERE Name IS NOT NULL
GROUP BY Id ) T2 ON T2.Id = T1.Id
WHERE T1.Name IS NULL
Sample execution in SQL-Server:
DECLARE #TestTable TABLE (Id INT, Name VARCHAR (20));
INSERT INTO #TestTable (Id, Name) VALUES
(1, 'a'),
(2, 'b'),
(1, 'a'),
(1, 'a'),
(1, NULL),
(2, 'b'),
(2, 'b'),
(2, NULL),
(2, 'b'),
(2, NULL);
UPDATE T1 SET T1.Name = T2.ValidName
FROM #TestTable T1
JOIN ( SELECT Id, MAX(Name) AS ValidName
FROM #TestTable
WHERE Name IS NOT NULL
GROUP BY Id ) T2 ON T2.Id = T1.Id
WHERE T1.Name IS NULL
SELECT * FROM #TestTable
try this: replace with your tablename
declare #id int, #name varchar(10)
declare c cursor for
select distinct id,name from <table> where id is not null and name is not null
open c
fetch next from c into #id,#name
while ##FETCH_STATUS=0
begin
update <table> set name=#name where id=#id
fetch next from c into #id,#name
end
close c
deallocate c

T- Sql aggregate query

can anyone help me figure out what I am doing wrong with this query.I am trying to filter some records from a table that contains emails sent out to clients with the status of the emails.I need to eliminate all EmailIds that has a status of Sent(1) and Bounced(0). Anything other than these two statuses are considered as Delivered(4). So the output contains only EmailId with a status of Delivered(4) for all those EmailIds that doesnt have statuses of 1 and 0.In the example below,I should see EmailId 4 too with a Status of Delivered
This is my sample set up.Really appreciate any help you guys can provide me with
create table #status
(
Id int,
Name varchar(100)
)
insert into #status (Id, Name)
values (0, 'Bounced'), (1, 'Sent'), (2, 'Clicked'),
(3, 'Opened'), (4, 'Delivered')
create table #email
(
EmailId int ,
Email varchar(100),
StatusId int
)
insert into #email (EmailId, email, StatusId)
values (1, 'rjoseph#gmail.com', 1), (1, 'rjoseph#gmail.com', 0),
(2, 'nathan#comcast.net', 1), (2, 'nathan#comcast.net', 2),
(2, 'nathan#comcast.net', 3), (3, 'nora#comcast.net', 1),
(3, 'nora#comcast.net', 2), (3, 'nora#comcast.net', 3),
(4, 'neha#comcast.net', 1)
select
e.EmailId
into
#temp
from
#email e
inner join #status st
on st.Id = e.StatusId
where
(e.StatusId not in (1,0))
group by
e.EmailId
drop table #temp
drop table #email
drop table #status
This is kind of a kludgy way to get to this (you can do this without the temporary tables, but I'm doing that here to follow your own syntax). The first query grabs the rows which match 1 AND 0. The second query returns the email IDs which do not exist in the first query:
SELECT EmailID
INTO #temp
FROM #email
WHERE StatusID = 0
AND EXISTS (SELECT 1 FROM #email WHERE StatusID = 1)
SELECT DISTINCT e.EmailID
FROM #email AS e LEFT JOIN #temp AS t
ON e.EmailID = t.EmailID
WHERE t.EmailID IS NULL
BTW: The SELECT 1 FROM ... does not have anything to do with the StatusID #1. It may seem confusing because I used SELECT 1, but it could have been SELECT 5 or SELECT 'Z'. It's mostly meaningless.
Here's the same query without the temporary table:
SELECT DISTINCT e.EmailID
FROM #email AS e
WHERE e.EmailID NOT IN (
SELECT EmailID
FROM #email
WHERE StatusID = 0
AND EXISTS (SELECT 1 FROM #email WHERE StatusID = 1)
)

T-SQL SELECT query to return combined result of multiple tables

I am wondering if it is possible and more efficient to do something that I am presently doing in code, to do in T-SQL instead.
I have a database with courses. Each course can have different offerings which are variations of the course at different locations and at different awards.
Here's my (simplified) database structure and some sample data:
CREATE TABLE tblCourse (CourseId int, CourseName varchar(50))
CREATE TABLE tblOffering (OfferingId int, CourseId int, LocationId int, AwardId int)
CREATE TABLE tblLocation (LocationId int, LocationName varchar(50))
CREATE TABLE tblAward (AwardId int, AwardName varchar(50))
INSERT INTO tblCourse VALUES (1, 'Course A')
INSERT INTO tblCourse VALUES (2, 'Course B')
INSERT INTO tblOffering VALUES (1, 1, 1, 1)
INSERT INTO tblOffering VALUES (2, 1, 2, 1)
INSERT INTO tblOffering VALUES (3, 1, 3, 1)
INSERT INTO tblOffering VALUES (4, 1, 1, 2)
INSERT INTO tblOffering VALUES (5, 2, 3, 1)
INSERT INTO tblLocation VALUES (1, 'Location A')
INSERT INTO tblLocation VALUES (2, 'Location B')
INSERT INTO tblLocation VALUES (3, 'Location C')
INSERT INTO tblAward VALUES (1, 'Award A')
INSERT INTO tblAward VALUES (2, 'Award B')
What I want to retrieve from SQL is a single row for each course/award combination. Each row would have columns for each location and whether a course of that CourseId/AwardId combination was available. There would be now rows for course/award combinations that have no offerings.
The required result, from the sample data, would be a recordset like this:
CourseId | CourseName | AwardId | AwardName | LocationA | LocationB | LocationC
---------+------------+---------+-----------+-----------+-----------+----------
1 | Course A | 1 | Award A | True | True | True
1 | Course A | 2 | Award B | True | NULL | NULL
2 | Course B | 1 | Award A | NULL | NULL | True
(NULL could also be False)
At present I am doing a simple SELECT statement with various JOINS which gives me multiple rows for each course/award combination, then I loop through all rows in my code and build the required result. However, I don't think this is so efficient as I also need to page results.
I think I could do this fairly easily in a stored procedure by creating a temporary table and a bunch of separate queries, but I don't think that would be too efficient. Wondering if there is a better way of doing it in T-SQL???
So to clarify, what I am looking for is a T-SQL query or stored procedure that will produce the above sample recordset, and which I could adapt paging to.
NB. I am using SQL Server 2008
For Dynamic columns:
DECLARE #COLUMNS VARCHAR(max)
,#query varchar(1024)
,#True varchar(6)
SELECT #COLUMNS =
COALESCE(
#Columns + ',[' + L.LocationName + ']',
'[' + L.LocationName +']'
)
FROM tblLocation L
SELECT #True = '''True'''
SELECT #QUERY = 'SELECT C.CourseName
,A.AwardName
, pvt.*
FROM (SELECT O.OfferingID AS OID
,O.AwardID AS AID
,O.CourseID AS CID
,L.LocationName AS LID
FROM tblOffering O Inner Join tblLocation L on L.LocationID = O.LocationID) AS S
PIVOT
(
count(oID) For LID IN (' +#COLUMNS+ ')
) As pvt
inner join tblCourse C on C.CourseID = CID
inner join tblAward A on A.AwardID = pvt.AID'
EXEC (#QUERY)
GO
This will produce a paginated version of your example results:
declare #tblCourse as table (CourseId int, CourseName varchar(50))
declare #tblOffering as table (OfferingId int, CourseId int, LocationId int, AwardId int)
declare #tblLocation as table (LocationId int, LocationName varchar(50))
declare #tblAward as table (AwardId int, AwardName varchar(50))
INSERT INTO #tblCourse VALUES (1, 'Course A')
INSERT INTO #tblCourse VALUES (2, 'Course B')
INSERT INTO #tblOffering VALUES (1, 1, 1, 1)
INSERT INTO #tblOffering VALUES (2, 1, 2, 1)
INSERT INTO #tblOffering VALUES (3, 1, 3, 1)
INSERT INTO #tblOffering VALUES (4, 1, 1, 2)
INSERT INTO #tblOffering VALUES (5, 2, 3, 1)
INSERT INTO #tblLocation VALUES (1, 'Location A')
INSERT INTO #tblLocation VALUES (2, 'Location B')
INSERT INTO #tblLocation VALUES (3, 'Location C')
INSERT INTO #tblAward VALUES (1, 'Award A')
INSERT INTO #tblAward VALUES (2, 'Award B') -- This had id 1 in your example.
-- Set the following parameters to control paging:
declare #PageSize as Int = 5
declare #PageNumber as Int = 1
; with CourseAwardSummary as (
select distinct C.CourseId, C.CourseName, A.AwardId, A.AwardName,
case when exists ( select 42 from #tblOffering where CourseId = C.CourseId and AwardId = A.AwardId and LocationId = 1 ) then 'True' end as LocationA,
case when exists ( select 42 from #tblOffering where CourseId = C.CourseId and AwardId = A.AwardId and LocationId = 2 ) then 'True' end as LocationB,
case when exists ( select 42 from #tblOffering where CourseId = C.CourseId and AwardId = A.AwardId and LocationId = 3 ) then 'True' end as LocationC
from #tblCourse as C inner join
#tblOffering as O on O.CourseId = C.CourseId inner join
#tblAward as A on A.AwardId = O.AwardId
),
CourseAwardSummaryRows as (
select *, Row_Number() over ( order by CourseName, AwardName ) as RowNumber
from CourseAwardSummary
)
select CourseId, CourseName, AwardId, AwardName, LocationA, LocationB, LocationC
from CourseAwardSummaryRows
where ( #PageNumber - 1 ) * #PageSize + 1 <= RowNumber and RowNumber <= #PageNumber * #PageSize
order by CourseName, AwardName
The following query does this by joining and aggregating the offering table, and then joining the result to the course and award tables:
select c.CourseId, c.CourseName, oa.AwardId, oa.AwardName,
oa.LocationA, oa.LocationB, oa.LocationC
from tblCourse c left outer join
(select o.CourseId, o.AwardId, a.awardName
max(case when LocationName = 'Location A' then 'true' end) as LocationA,
max(case when LocationName = 'Location B' then 'true' end) as LocationB,
max(case when LocationName = 'Location C' then 'true' end) as LocationC
from tblOffering o join
tblLocation l
on o.LocationId = l.LocationId join
tblAward a
on a.awardID = o.AwardId
group by o.CourseId, o.AwardId, a.awardName
) oa
on oa.CourseId = c.CourseId

Resources