Continue to get duplicate results with SQL Query - sql

I was thinking it might be an issue with the joins? I've tried Group by to no avail...
Any advice would be appreicated! I've placed the query below:
*Sorry for the lack of details- Med_Prof_Record_No is the unique value, and I know the code is messy- but this is what was here when I got here ;-) Also this is a sql 2000 box, so new syntax won't work... I've cleaned up the joins abit but don't want to stray too far from the original queries-
SELECT DISTINCT
P.Last_name + ', ' + P.First_name AS Full_name,
P.Degree,
F.Med_Prof_Record_No,
F.Current_status,
F.Status_category,
F.Department_name,
F.Section,
F.SPHAffiliatedPhysiciansSurgeons AS Affiliated,
S.Board_Name,
S.Specialty_Name,
O.Office_name,
O.Address_1,
O.Address_2,
O.City,
O.State,
O.Zip_Code,
O.Phone_number_1,
O.Fax_number
FROM
Med_Prof P, Med_Prof_Facilities F, Med_Prof_Specialties S, Med_Prof_Offices O
WHERE
(F.Med_Prof_Record_No = P.Med_Prof_Record_No) AND
(F.Med_Prof_Record_No = S.Med_Prof_Record_No) AND
(F.Med_Prof_Record_No = O.Med_Prof_Record_No) AND
<cfif URL.LastName is NOT "">(P.Last_name LIKE '#URL.LastName#%') AND</cfif>
<cfif URL.Specialty is NOT "">(F.Section = '#URL.Specialty#') AND</cfif>
<cfif URL.Group is NOT "">(O.Office_name LIKE '#URL.Group#%') AND</cfif>
(F.Status_category = 'active')
ORDER by Full_name

Here is a stab. This makes assumptions about how you want to decide which office etc. to show (in this case it is as good as arbitrary), that P.Med_Prof_Record_No is unique and only represents one person (at first I thought Last_name + First_name is unique, but that seems a very dangerous assumption), and also that you are using SQL Server 2005 or better. Finally, please use properly qualified object names and please, please, please stop using lazy implicit joins of the FROM foo, bar, blat, splunge variety.
;WITH x AS
(
SELECT
P.Last_name + ', ' + P.First_name AS Full_name,
P.Degree,
F.Med_Prof_Record_No,
-- other columns from F,
S.Board_Name,
S.Specialty_Name,
O.Office_name,
-- other columns from O,
rn = ROW_NUMBER() OVER (PARTITION BY P.Med_Prof_Record_No
ORDER BY F.Current_status, S.Board_name, O.Office_name)
FROM
dbo.Med_Prof AS P
INNER JOIN
dbo.Med_Prof_Facilities AS F
ON P.Med_Prof_Record_No = F.Med_Prof_Record_No
INNER JOIN
dbo.Med_Prof_Specialties AS S
ON F.Med_Prof_Record_No = S.Med_Prof_Record_No
INNER JOIN
dbo.Med_Prof_Offices AS O
ON F.Med_Prof_Record_No = O.Med_Prof_Record_No
WHERE
<cfif ... AND</cfif>
-- other <cfif> clauses
(F.Status_category = 'active')
)
SELECT * FROM x WHERE rn = 1
ORDER BY Full_name;

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

Apply default filter if query returns 0 rows - Oracle Plsql

Im using Oracle PLSQL. Im trying to make a join beetween two tables and make a filter from one parameter that can be 'W' (Woman) or 'M' (Man).
I have this table with a list of Jobs:
And this other one with every job translated to English or Spanish for each gender:
I want to get the job translated to both languages for the gender specified in the parameter. If there is no translation for Woman, then get translation for Man.
I tried to do this with NVL but it's not working:
select j.*, l.long_name, l.language, l.gender
from job j
join job_lang l
on j.id = l.id_job
where j.short_name = 'Firefighter'
and nvl(l.gender, 'M') = 'M'; -- parameter
It works perfect when the parameter is 'M' but returns nothing when is 'W' because there is no translation for Woman.
I'm not sure if I can do this with a case, I tried but I couldn't find anything. Could you help me?
Thanks.
I would aggregate it before join:
select j.*, l.language, l.long_name_M, l.long_name_W
from job j
join (
select
jl.id_job,jl.language
,max(decode(jl.gender,'M',long_name) long_name_M
,max(long_name)keep(dense_rank first order by decode(jl.gender,'W',1,'M',2)) long_name_W
from job_lang jl
group by jl.id_job,jl.language
) l
on j.id = l.id_job
where j.short_name = 'Firefighter';
PS. Oracle can push predicates into group-by inline view
You could use the fetch clause with window functions for prioritization:
select j.*, l.long_name, l.language, l.gender,
from job j
join job_lang l on j.id = l.id_job
where j.short_name = 'Firefighter'
order by row_number() over(
partition by j.id, l.language
order by case when l.gender = 'M' then 0 else 1 end
) ^----- parameter
fetch first row with ties

Adding WHERE clause to outer query causes inner query to error

The below query worked until I added a WHERE clause.
SELECT
PersonTable.FullName,
View_PersonToHead.DirectorId
FROM PersonTable
LEFT JOIN View_PersonToDirector ON PersonTable.PersonId = View_PersonToDirector.PersonId
WHERE View_PersonToDirector.DirectorId = 12345 --No error if this line is removed
The error message is:
Invalid length parameter passed to the RIGHT function.
This leads me to believe that there was an error with how I wrote the View_PersonToDirector view. Something about adding the WHERE clause causes the query to be evaluated/optimized in a different way, exposing some issue.
Internals of View_PersonToDirector:
WITH items AS (
SELECT
PersonId,
0 AS [Level],
CAST(PersonId AS VARCHAR(255)) AS [Path]
FROM PersonTable
UNION ALL
SELECT
e.PersonId,
[Level] + 1,
CAST([Path] + ' < ' + CAST(e.PersonId AS VARCHAR(255)) AS VARCHAR(255))
FROM PersonTable e
INNER JOIN items itms ON itms.PersonId = e.ManagerId
)
SELECT
A.PersonId,
CASE
WHEN A.[Level] = 1
THEN A.PersonId
ELSE CAST(LEFT(A.PathToDirector, CHARINDEX(' ', A.PathToDirector)) AS INT)
END AS DirectorId
FROM (
SELECT
items.PersonId,
items.[Level],
RIGHT(items.[Path], LEN(items.[Path])-7) AS PathToDirector
FROM items
WHERE Path LIKE '1111 < %' --Id of director
) A
I suspect that having a WITH cte in the view causes the query optimizer to work differently, applying the WHERE filtering in a different order. Is this bad practice?
One of your problems is definitely in this code:
RIGHT(items.[Path], LEN(items.[Path])-7) AS PathToDirector
I appreciate that you think that this WHERE clause fixes the problem:
WHERE Path LIKE '1111 < %'
But it does not. The problem is that SQL Server does not guarantee the order of evaluation of expressions. This true even for subqueries, CTEs, and views. These can be evaluated in any order. In fact, SQL Server sometimes pushes the evaluation to the node that reads from the table -- which is why you get an error.
Personally, I think this is a bug in SQL Server. Those powers that be do not agree. Here are two solutions:
(CASE WHEN Path LIKE '1111 < %' THEN RIGHT(items.[Path], LEN(items.[Path])-7) END) AS PathToDirector
Or:
(CASE WHEN Path LIKE '1111 < %' THEN RIGHT(items.[Path], LEN(items.[Path] + '1234567')-7) END AS PathToDirector
You may have the same problem with the CHARINDEX().
The use of a where condition related to column from left join table generate and implicit inner join
In this case add the condition to the ON clause for let the left join work
SELECT
PersonTable.FullName,
View_PersonToHead.DirectorId
FROM PersonTable
LEFT JOIN View_PersonToDirector ON PersonTable.PersonId = View_PersonToDirector.PersonId
AND View_PersonToDirector.DirectorId = 12345
for the length problem try use conditional eg using case
SELECT
items.PersonId,
items.[Level],
case when LEN(items.[Path]) > 7 then RIGHT(items.[Path], LEN(items.[Path])-7)
ELSE items.[Path] END AS PathToDirector
FROM items
The base part of your rCTE has:
CAST(PersonId AS VARCHAR(255)) AS [Path]
Which means base rows of the rCTE would contain values such as 1234 which are shorter than 7 characters and cause RIGHT(x, LEN(x) - 7) to fail. The condition:
RIGHT(items.[Path], LEN(items.[Path])-7) AS PathToDirector
Could be safely written as:
SUBSTRING(items.[Path], NULLIF(CHARINDEX(' < ', items.[Path]), 0) + 3, LEN(items.[Path]))

Select statement to show the corresponding user with the lowest/highest amount?

I want to write a select statement output that, among other things, has both a lowest_bid and highest_bid column. I know how to do that bit, but want I also want is to show the user (user_firstname and user_lastname combined into their own column) as lowest_bidder and highest_bidder. What I have so far is:
select item_name, item_reserve, count(bid_id) as number_of_bids,
min(bid_amount) as lowest_bid, ???, max(big_amount) as highest_bid,
???
from vb_items
join vb_bids on item_id=bid_item_id
join vb_users on item_seller_user_id=user_id
where bid_status = ‘ok’ and
item_sold = ‘no’
sort by item_reserve
(The ???'s are where the columns should go, once I figure out what to put there!)
This seems like good use of window functions. I've assumed a column vb_bids.bid_user_id. If there's no link between a bid and a user, you can't answer this question
With x as (
Select
b.bid_item_id,
count(*) over (partition by b.bid_item_id) as number_of_bids,
row_number() over (
partition by b.bid_item_id
order by b.bid_amount desc
) as high_row,
row_number() over (
partition by b.bid_item_id
order by b.bid_amount
) as low_row,
b.bid_amount,
u.user_firstname + ' ' + u.user_lastname username
From
vb_bids b
inner join
vb_users u
on b.bid_user_id = u.user_id
Where
b.bid_status = 'ok'
)
Select
i.item_name,
i.item_reserve,
min(x.number_of_bids) number_of_bids,
min(case when x.low_row = 1 then x.bid_amount end) lowest_bid,
min(case when x.low_row = 1 then x.username end) low_bidder,
min(case when x.high_row = 1 then x.bid_amount end) highest_bid,
min(case when x.high_row = 1 then x.username end) high_bidder
From
vb_items i
inner join
x
on i.item_id = x.bid_item_id
Where
i.item_sold = 'no'
Group By
i.item_name,
i.item_reserve
Order By
i.item_reserve
Example Fiddle
In order to get the users, I broke out the aggregates into their own tables, joined them by the item_id and filtered them by a derived value that is either the min or max of bid_amount. I could have joined to vb_bids for a third time, and kept the aggregate functions, but that would've been redundant.
This will fail if you have two low bids of the exact same amount for the same item, since the join is on bid_amount. If you use this, then you'd want to created an index on vb_bids covering bid_amount.
select item_name, item_reserve, count(bid_id) as number_of_bids,
low_bid.bid_amount as lowest_bid, low_user.first_name + ' ' + low_user.last_name,
high_bid.bid_amount as highest_bid, high_user.first_name + ' ' + high_user.last_name
from vb_items
join vb_bids AS low_bid on item_id = low_bid.bid_item_id
AND low_bid.bid_amount = (
SELECT MIN(bid_amount)
FROM vb_bids
WHERE bid_item_id = low_bid.bid_item_id)
join vb_bids AS high_bid on item_id = high_bid.bid_item_id
AND high_bid.bid_amount = (
SELECT MAX(bid_amount)
FROM vb_bids
WHERE bid_item_id = high_bid.bid_item_id)
join vb_users AS low_user on low_bid.user_id=user_id
join vb_users AS high_user on high_bid.user_id=user_id
where bid_status = ‘ok’ and
item_sold = ‘no’
group by item_name, item_reserve,
low_bid.bid_amount, low_user.first_name, low_user.last_name,
high_bid.bid_amount, high_user.first_name, high_user.last_name
order by item_reserve
I am a big fan of using Common Table Expressions (CTEs) for situations like this, because of the following advantages:
Separating different parts of the logic, adding to readability, and
Reducing complexity (for example, the need to GROUP BY a large number of fields, or to repeat the same join multiple times.)
So, my suggested approach would be something like this:
-- semi-colon must precede CTE
;
-- collect bid info
WITH item_bids AS (
SELECT
i.item_id, i.item_name, i.item_reserve, b.bid_id, b.bid_amount,
(u.first_name + ' ' + u.last_name) AS bid_user_name
FROM vb_items i
JOIN vb_bids b ON i.item_id = b.bid_item_id
JOIN vb_users u ON b.user_id = u.user_id
WHERE b.bid_status = 'ok'
AND i.item_sold = 'no'
),
-- group bid info
item_bid_info AS (
SELECT item_id, item_name, item_reserve
COUNT(bid_id) AS number_of_bids, MIN(bid_amount) AS lowest_bid, MAX(bid_amount) AS highest_bid
FROM item_bids
GROUP BY item_id, item_name, item_reserve
)
-- assemble final result
SELECT
bi.item_name, bi.item_reserve, bi.number_of_bids,
bi.low_bid, low_bid.bid_user_name AS low_bid_user,
bi.high_bid, high_bid.bid_user_name AS high_bid_user
FROM item_bid_info bi
JOIN item_bids AS low_bid ON bi.lowest_bid = low_bid.bid_amount AND bi.item_id = low_bid.bid_item_id
JOIN item_bids AS high_bid ON bi.lowest_bid = high_bid.bid_amount AND bi.item_id = high_bid.bid_item_id
ORDER BY bi.item_reserve;
Note that the entire SQL statement (from the starting WITH all the way down to the final semi-colon after the ORDER BY) is a single statement, and is evaluated by the optimizer as such. (Some people think each part is evaluated separately, like temp tables, and then all the rows are joined together at the end in a final step. That's not how it works. CTEs are just as efficient as sub-queries.)
Also note that this approach does a JOIN on the bid amount, so if there are identical bids for a single item, it will fail. (Seems like that should be an invalid state anyway, though, right?) Also you may have efficiency concerns depending on:
The size of your table
Whether the lookup can use an index
You could address both issues by including a unique constraint (which has the added advantage of indexing the foreign key bid_item_id as well; always a good practice):
ALTER TABLE [dbo].[vb_bids] ADD CONSTRAINT [UK_vbBids_item_amount]
UNIQUE NONCLUSTERED (bid_item_id, bid_amount)
GO
Hope that helps!

SQL Join then combine columns from the result

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