SQL Join then combine columns from the result - sql

So I have two db2 tables. One contains work order information like id, requester name, user, description, etc. Second table that has notes, which is keyed to the id of the other table. The notes field is a 255 text field (Yeah don't suggest changing it, I have no control over it). So there could be multiple results, or none, in the note field depending on obviously how many notes there are.
I have a query which fetches the results. The problem is that I am getting multiple results form the join because there are multiple entries.
So my question is how do I concat/merge the results from the notes table into one field for every result? Thanks
Code:
SELECT
p.ABAANB AS WO_NUMBER,
p.ABAJTX AS Description,
i.AIAKTX as Notes
FROM
htedta.WFABCPP p LEFT JOIN HTEDTA.WFAICPP i
ON i.AIAANB = p.ABAANB
WHERE
p.ABABCD = 'ISST' AND p.ABAFD8 = 0

Have you tried LISTAGG
https://www.ibm.com/developerworks/mydeveloperworks/blogs/SQLTips4DB2LUW/entry/listagg?lang=en
It will allow you to merge all those pesky fields that are causing the additional records... Something like this...
SELECT p.ABAANB AS WO_NUMBER, p.ABAJTX AS Description, LISTAGG(i.AIAKTX, ' ') as Notes
FROM htedta.WFABCPP p
LEFT JOIN HTEDTA.WFAICPP i
ON i.AIAANB = p.ABAANB
WHERE p.ABABCD = 'ISST'
AND p.ABAFD8 = 0
GROUP BY p.ABAAMB, p.ABAJTX

What version of DB2 are you on? If you're using DB2 Linux/Unix/Windows (LUW), then this should work for you:
SELECT p.ABAANB AS WO_NUMBER,
p.ABAJTX AS Description,
,SUBSTR(
xmlserialize(
xmlagg(
xmltext(
concat(',' , TRIM(i.AIAKTX))
)
) AS VARCHAR(4000)
)
,2) AS NOTES
FROM htedta.WFABCPP p
LEFT JOIN HTEDTA.WFAICPP i
ON i.AIAANB = p.ABAANB
WHERE p.ABABCD = 'ISST'
AND p.ABAFD8 = 0
GROUP BY p.ABAANB,
p.ABAJTX

If you are running DB2 at least 9.7, you should be able to use the XMLAGG function similar to this
WITH CombinedNotes( aiaanb, aiaktx) AS (
SELECT aiaanb
, REPLACE( REPLACE(
CAST( XML2CLOB(
XMLAGG( XMLELEMENT(
NAME 'A'
, aiaktx
))
) AS VARCHAR( 3000))
, '<A>',''), '</A>', '')
FROM Htedta.WfaIcpp)
SELECT p.ABAANB AS WO_NUMBER, p.ABAJTX AS Description, i.AIAKTX as Notes
FROM htedta.WFABCPP p
LEFT JOIN CombinedNotes i
ON i.AIAANB = p.ABAANB
WHERE p.ababcd = 'ISST'
AND p.abafd8 = 0;
If you have an earlier version of DB2, or your login for some reason does not permit the XML functions, you can solve the problem with a recursive query. Both techniques are described in the following blog
http://ibmmainframes.com/about44805.html

Related

Single Query To Select Based On Parameters If Or not supplied

I have used SqlDataSource and have a select query based on District & Zone as below
SELECT a.[committee_id] memberid, a.[membername], a.[memberemail], a.[memberdesignation], a.[membercreatedby],b.districtname AS district,b.districtid,c.zone_name AS zone,c.zoneid
FROM [committee_details] a
LEFT JOIN district_master b on b.districtid=a.districtid
LEFT JOIN zone_master c on c.districtid=a.districtid and c.zoneid = a.zoneid
WHERE (a.[membercreatedby] = 'director') AND ((convert(varchar,a.districtid) LIKE '%2%') AND (convert(varchar,a.zoneid) LIKE '%25%')) ORDER BY a.[committee_id] DESC
It's an inline query. I have tried above query but not able to figure out how to Select condition based.
I want if district supplied then Select according to District, if both District & Zone supplied then Select according to both & If nothing supplied then Select All. But should be in single query. How should I do this?
First, fix your query so you are not using meaningless table aliases. Use table abbreviations! I would also drop all the square braces; they just make the query harder to write and to read.
Basically, you want comparisons with NULL in the WHERE clause. I have no idea why your sample code uses LIKE, particularly columns that appear to be numbers. Nothing in the question explains why LIKE is used for the comparison, so the idea is:
SELECT cd.committee_id as memberid, cd.membername,
cd.memberemail, cd.memberdesignation, cd.membercreatedby,
dm.districtname AS district, dm.districtid,
zm.zone_name AS zone, zm.zoneid
FROM committee_details cd LEFT JOIN
district_master dm
ON cd.districtid = dm.districtid LEFT JOIN
zone_master zm
ON zm.districtid = cd.districtid AND
zm.zoneid = cd.zoneid
WHERE cd.membercreatedby = 'director') AND
(cd.districtid = #district or #district is null) AND
(cd.zoneid = #zone or #zone is null)
ORDER BY cd.[committee_id] DESC;
If you were using LIKE, then I would phrase the logic like:
WHERE cd.membercreatedby = 'director') AND
(cast(cd.districtid as varchar(255)) like #district) AND
(cast(cd.zoneid as varchar(255)) like #zone)
And pass in the patterns as '%' when you want all values to match. This assumes that the columns in cd are not NULL. If they can be NULL, then you want an explicit comparison, as in the first example.
If I got the question right then you can use parameters and compare to the column itself if the values are not supplied or not present.
try the following:
SELECT a.[committee_id] memberid, a.[membername], a.[memberemail], a.[memberdesignation], a.[membercreatedby],b.districtname AS district,b.districtid,c.zone_name AS zone,c.zoneid
FROM [committee_details] a
LEFT JOIN district_master b on b.districtid=a.districtid
LEFT JOIN zone_master c on c.districtid=a.districtid and c.zoneid = a.zoneid
WHERE (a.[membercreatedby] = 'director')
AND b.districtname = isnull(nullif(#districtname, ''), b.districtname)
AND c.zone_name = isnull(nullif(#zone_name, ''), c.zone_name)
ORDER BY a.[committee_id] DESC

Oracle sql: Using two listagg

I'm using the following code to bring through 'DEL' text and 'PAL' text. The 'DEL' and 'PAL' text could be across several lines (and not necessarily the same amount of lines of each).
select trim(listagg(tx1.text, ', ') within group (order by tx1.text)) del_text,
trim(listagg(tx2.text, ', ') within group (order by tx2.text)) pal_text
from oes_ordtxt tx1
inner join oes_ordtxt tx2
on tx1.key1 = tx2.key1
and tx1.key2 = tx2.key2
and tx1.key3 = tx2.key3
and tx2.doctyp = 'PAL'
where tx1.key1 = '0018104834'
and tx1.key2 = '00001'
and tx1.key3 = '001'
and tx1.doctyp = 'DEL'
The problem I have is that where I have multiple rows on 'DEL text and only one row on 'PAL' text the 'PAL' text repeats, e.g.
The 'PAL_TEXT' is duplicating as only one PAL_TEXT exists but, three DEL_TEXT exists.
Is there a way to remove the duplicates?
Thanks, SMORF
It doesn't matter how many tables in aggregation (unfortunately I can't check syntax without your data structure):
select (select listagg(column_value,', ') within group (order by column_value) from table (del_text)) del_text
,(select listagg(column_value,', ') within group (order by column_value) from table (pal_text)) pal_text
from (select collect (distinct tx1.text) del_text,
collect (distinct tx2.text) pal_text
from oes_ordtxt tx1
inner join oes_ordtxt tx2
on tx1.key1 = tx2.key1
and tx1.key2 = tx2.key2
and tx1.key3 = tx2.key3
and tx2.doctyp = 'PAL'
where tx1.key1 = '0018104834'
and tx1.key2 = '00001'
and tx1.key3 = '001'
and tx1.doctyp = 'DEL'
group by 1)
Rewrite the select to
1) group both tables on the join key and calculate listagg (possible removing duplicate keys)
2) join the result
The join will be always 1:1, so there will be no duplication caused by it.

Using Count() and Sum() correctly in SQL?

Ok, so I hope I can explain this question well enough, because I feel like this is going to be a tough one.
I have two tables I'm working with today. These look like:
#pset table (PersonID int, SystemID int, EntitlementID int, TargetID int)
#Connector table (TargetName varchar(10), fConnector bit)
The first table stores records that tell me, oh this person has this system, which is composed of these entitlements, whom have these targets. A little complicated, but stay with me. The second stores the TargetName and then whether or not that target has a connector in my not-so-theoretical system.
What I'm trying to do is merge these two tables so that I can see the target flag for each row in #pset. This will help me later as you'll see.
If each entitlement in a system has a connector to the target (the flag is true for all of them), then I'd like to know.
All the others should go into a different table.
This is what I tried to do, but it didn't work. I need to know where I went wrong. Hopefully someone with more experience than me will be able to answer.
-- If the count(123) = 10 (ten rows with SystemID = 123) and the sum = 10, cool.
select pset.*, conn.fConnector from #pset pset
inner join vuTargets vt
on vt.TargetID = pset.TargetID
inner join #conn conn
on conn.TargetName = vt.TargetName
group by ProfileID, SystemRoleID, EntitlementID, TargetID, fConnector
having count(SystemID) = sum(cast(fConnector as int))
order by ProfileID
and
-- If the count(123) = 10 (ten rows with SystemID = 123) and the sum <> 10
select pset.*, conn.fConnector from #pset pset
inner join vuTargets vt
on vt.TargetID = pset.TargetID
inner join #conn conn
on conn.TargetName = vt.TargetName
group by ProfileID, SystemRoleID, EntitlementID, TargetID, fConnector
having count(SystemID) <> sum(cast(fConnector as int))
order by ProfileID
Unfortunately, these do not work :(
Edit
Here is a screenshot showing the problem. Notice ProfileID 1599 has a SystemID of 1126567, but one of the entitlements doesn't have a connector! How can I get both of these rows into the second query? (above)
Your basic problem is that you're trying to roll up to two different record sets.
The initial set (the SELECT and GROUP BY clauses) is saying that you want one record for every difference in the set [ProfileId, SystemId, EntitlementId, TargetId, fConnector].
The second set (the HAVING clause) is saying that you want, for every row in the inital set, to compare it's COUNT of records with the SUM of the connections. However, because you've asked for grouping down to the individual flag, this has the effect of getting a single row for each flag (assuming 1-to-1 relationships). Effectively, you're saying - 'Hey, if this target has a connection? Yeah, I want it'.
What you appear to want is a roll up to the SystemId value. To do that, you will need to change your SELECT and GROUP BY clauses to only include the set [ProfileId, SystemId]. This will return only those rows (keyed from profile and system) who has all targets 'connected'. You will not be able to see the individual entitlements, targets, and whether they are connected (you will be able to infer that they will all be/not be connected, however).
EDIT:
In the interests of full disclosure, here is how you'd get something similar to your original results set, where it lists all EntitlementIds and TargetIds:
WITH all_connections as (SELECT pset.ProfileId, pset.SystemRoleId
FROM #pset pset
INNER JOIN vuTargets vt
ON vt.TargetId = pset.TargetId
INNER JOIN #conn conn
ON conn.TargetName = vt.TargetName
GROUP BY pset.ProfileId, pset.SystemRoleId
HAVING COUNT(pset.SystemRoleId)
= SUM(CAST(fConnector as INT)))
SELECT pset.*
FROM #pset pset
JOIN all_connections conn
ON conn.ProfileId = pset.ProfileId
AND conn.SystemRoleId = pset.SystemRoleId
This should get you a listing, down to the TargetId, of ProfileId/SystemRoleId keys where all EntitlementIds and TargetIds have a connection (or, flip the CTE = to <> for those where not all do).
Edit: fixed my original queries, updated the description as well
You can split this up: first find the TargetIDs that have an fConnector of 0. Then find the PersonID, SystemID pairs that have any target equal to the ones you found. Then select the relevant data: (this finds the PersonID, SystemID pair where at least one entitlement does not have a connector to the target)
with abc as (
select PersonID, SystemID
from pset P
where TargetID in (
select TargetID
from vuTargets V join connector C on V.TargetName = C.TargetName
where C.fConnector = 0
)
)
select P.PersonID, P.SystemID, P.EntitlementID, P.TargetID, C.fConnector
from pset P
join abc on ((P.PersonID = abc.PersonID) and (P.SystemID = abc.SystemID))
join vuTargets V on P.TargetID = V.TargetID
join connector C on V.TargetName = C.TargetName
The query to find the PersonID, SystemID pairs where all entitlements have a connector to the target is similar:
with abc as (
select PersonID, SystemID
from pset P
where TargetID in (
select TargetID
from vuTargets V join connector C on V.TargetName = C.TargetName
where C.fConnector = 0
)
)
select P.PersonID, P.SystemID, P.EntitlementID, P.TargetID, C.fConnector
from
pset P
join abc on ((P.PersonID <> abc.PersonID) or (P.SystemID <> abc.SystemID))
join vuTargets V on P.TargetID = V.TargetID
join connector C on V.TargetName = C.TargetName
The difference is in the join with the temp table (<> vs =). This is very similar to zero's answer, but doesn't use counts or sums.

Apply distinct on single column from multiple return columns

My problem is I want to apply distinct key word on just one column in select statement.
Here is my sql query.
SELECT ARM.AppId,
ARM.AppFirstName,
ARM.AppLastName,
AQD.QualiId,
VacQualiDetail.QualiName,
VacQualiDetail.VacID,
ARM.TotalExpYear,
ARM.TotalExpMonth,
VacQualiDetail.VacTitle,
VacQualiDetail.DeptId,
VacQualiDetail.CompId
FROM tblAppResumeMaster ARM,
tblAppQualificationDetail AQD,
(SELECT VM.VacID,
VM.VacTitle,
VM.CompId,
VM.DeptId,
vcd.QualificationID,
QM.QualiName,
VM.RequiredExperience as Expe
FROM tblVacancyCriteriaDetail VCD,
tblVacancyMaster VM,
tblQualificationMaster QM
WHERE VCD.VacID = VM.VacID
AND VCD.QualificationID = QM.QualificationId) as VacQualiDetail
WHERE AQD.AppId = arm.AppId
AND aqd.QualiId = VacQualiDetail.QualificationID
AND ARM.TotalExpYear >= Expe
In this query, ARM.AppId is repeated and I want to apply distinct keyworld on ARM.AppId
How can I achieve my goal?
example use 2 or more SELECT DISTICT:
SELECT DISTINCT ARM.AppId,
(SELECT DISTINCT ARM.AppFirstName) AS name
FROM ..............
if data database repeated,
according me the relation of each table is wrong.
please show the structure of the table
select distinct ARM.AppId, ARM.AppFirstName .....

Query TFS database to fetch last 10 check-in details

Is there a way to query TFS database to get the last 10 check-in details
The output should be something like
File name | Comment | Changed By | Date
----------------------------------------------------------------------------
Test.cs Added new functionality username 01/08/2010
I am aware that the above result set can be obtained using TFS SDK. But I want to know if
there is a way to query the TFS database to fetch the above data.
Thanks
If I understand your question correctly, this will get you most of the way there in SQL:
SELECT TOP 10
V.ChildItem AS [File name],
CS.Comment,
I.DisplayName AS [Changed By],
CS.CreationDate AS [Date]
FROM tbl_Changeset CS
INNER JOIN tbl_Identity I ON I.IdentityID = CS.OwnerID
INNER JOIN tbl_Version V ON V.VersionFrom = CS.ChangesetID
ORDER BY CS.CreationDate DESC
There are some escaped characters in the file names that showed up while I was testing this on my TFS instance (like underscore characters become ">"). Other than that this should serve you well.
A lot of these queries are no longer relevant with newer instances of TFS. One of the big reasons is that the user identities (tbl_Identity) have been moved. Tfs_DefaultCollection is the DB that stored all information pertinent to a given Collection, but TFS can host multiple Collections AND an Admin can change the name of the default Collection when they are setting up TFS.
As such, all user identities have been moved to the Tfs_Configuration database;
select * from [tfs_Configuration].dbo.tbl_Identity
Gaining Access to the descriptive part of the account name ( AccountName or DisplayName ) from a Collection DB is accomplished by Join from [tfs_DefaultCollection].dbo.tbl_IdentityMap
select *
from [tfs_Configuration].dbo.tbl_Identity I
JOIN [tfs_DefaultCollection].dbo.tbl_IdentityMap IM
ON I.Id = IM.MasterID
With this information in hand, we can now produce the following query to show the last 100 ChangeSets committed to TFS as well as the name of the committer.
select top 100 *
from tbl_changeset as c
JOIN tbl_IdentityMap IM
ON C.OwnerID = IM.localId
JOIN tfs_configuration.dbo.tbl_Identity u
ON IM.MasterID = u.Id
Order by C.ChangeSetID DESC
Looking at this data, we can see A LOT of IDs, Sids, Lookups, etc. What we will NOT see in this data is any information about the File that was committed or information about the Branch the Commit was made to. This information comes from the tbl_Version table.
select top 100 *
from [tfs_DefaultCollection].dbo.tbl_changeset as c
JOIN [tfs_DefaultCollection].dbo.tbl_IdentityMap IM
ON C.OwnerID = IM.localId
JOIN [tfs_configuration].dbo.tbl_Identity u
ON IM.MasterID = u.Id
JOIN [tfs_DefaultCollection].dbo.tbl_Version as v
ON v.Versionfrom = c.ChangeSetId
Order by C.ChangeSetID DESC
[tfs_DefaultCollection].dbo.tbl_Version has a few columns of interest, namely; ParentPath, ChildItem & FullPath. What is missing here is any useful information regarding the Branch that the commit was made to. Branch info is embedded in either of the 2 available Path fields;
$\da3da7cf"80b5"4385"b3dc"ebb3088f3c01\Features\Inpatient\Source\Yadda\Yadda\Yadda\
This begs the next question, where is Branch info stored in TFS? Thanks to StackOverflowUsers answer above, this information was found in [tfs_DefaultCollection].dbo.TreeNodes table;
select * from [tfs_DefaultCollection].dbo.[TreeNodes] where parentID=0 and fDeleted=0
The Column CssNodeID is the ID we are looking for to make sense out of the FullPath
CoolBranchName da3da7cf-80b5-4385-b3dc-ebb3088f3c01
However, this presents us with our next challenge regarding the TFS Databases. A lot of information is encoded and\or embedded so we need to manipulate a few things to get what we are after.
In this instance, the part of the [tfs_DefaultCollection].dbo.tbl_Version.ParentPath or [tfs_DefaultCollection].dbo.tbl_Version.FullPath value that contains the Branch info.
This ugly little diddy right here extracts the ID portion of the ParentPath field value and replaces all the Double-Quotes with Hyphens which gives us an Id value we can use to query [Tfs_DefaultCollection].dbo.TreeNodes
SUBSTRING(
REPLACE( v.ParentPath, '$\', ''),
CHARINDEX( '\', REPLACE( v.ParentPath, '$\', '') ),
( LEN( v.ParentPath ) - CHARINDEX( '\', REPLACE( v.ParentPath, '$\', '') ) )
)
Putting all of this together in pursuit of a Query to Fetch The Last [X] Check-In details results in the following;
select top 10
c.CreationDate,
c.Comment,
u.DisplayName as CommittingUser,
TN.Name as BranchName,
SUBSTRING(
REPLACE( v.ParentPath, '$\', ''),
CHARINDEX( '\', REPLACE( v.ParentPath, '$\', '') ),
( LEN( v.ParentPath ) - CHARINDEX( '\', REPLACE( v.ParentPath, '$\', '') ) )
) as ChangedFile
from tbl_changeset as c
JOIN tbl_IdentityMap IM
ON C.OwnerID = IM.localId
JOIN [Tfs_Configuration].dbo.tbl_Identity u
ON IM.MasterID = u.Id
JOIN dbo.tbl_Version as v
ON v.Versionfrom = c.ChangeSetId
LEFT JOIN dbo.TreeNodes TN with(nolock)
ON TN.CssNodeId = REPLACE(
SUBSTRING(
REPLACE( v.ParentPath, '$\', ''),
0,
CHARINDEX( '\', REPLACE( v.ParentPath, '$\', '') )
),
'"', '-'
)
AND parentID=0
AND fDeleted=0
Order by c.CreationDate desc
Note that I included the DB qualifiers for all the queries leading up to the final query to provide context on where tables of interest are located.
As a workaround how about the below query.. But i think it is returning me the wrong comments.. not sure why.
SELECT top 10
C.ChangeSetId,
V.FullPath,
V.ParentPath,
REPLACE(V.ChildItem,'\','') as [FileName],
C.CreationDate,
I.DisplayName,
C.Comment
FROM tbl_Version(nolock) V
INNER JOIN tbl_File (nolock) F ON V.ItemId = F.ItemId
INNER JOIN tbl_Changeset (nolock) C ON V.VersionTo = C.ChangeSetId
INNER JOIN tbl_Identity (nolock) I ON C.CommitterId = I.IdentityId
where v.ParentPath like '$\' + (select name from [TfsWorkItemTracking].[dbo].[treenodes] where parentid=0 and fdeleted=0 and id=524) + '\%'
order by C.CreationDate desc
Thanks to mark.crockett for posting the above query # http://social.msdn.microsoft.com/Forums/en-US/tfsreporting/thread/32d2c27e-825b-43bb-b156-36048a3e70cb/
If you have access to the SQL server that hosts TFS database the db you have to look is TFSWarehouse, then you can look for the tables Work Item,tbl_Changeset,tbl_Identity,tbl_Version etc. from where you can pull some information.
Thnks.