SQL: SELECT DISTINCT not returning distinct values - sql

The code below is supposed to return unique records in the lp_num field from the subquery to then be used in the outer query, but I am still getting multiples of the lp_num field. A ReferenceNumber can have multiple ApptDate records, but each lp_num can only have 1 rf_num. That's why I tried to retrieve unique lp_num records all the way down in the subquery, but it doesn't work. I am using Report Builder 3.0.
Current Output
Screenshot
The desired output would be to have only unique records in the lp_num field. This is because each value in the lp_num field is a pallet, one single pallet. the info to the right is when it arrived (ApptDate) and what the reference number is for the delivery (ref_num). Therefore, it makes no sense for a pallet to have multiple receipt dates...it can only arrive once...
SELECT DISTINCT
dbo.ISW_LPTrans.item,
dbo.ISW_LPTrans.lot,
dbo.ISW_LPTrans.trans_type,
dbo.ISW_LPTrans.lp_num,
dbo.ISW_LPTrans.ref_num,
(MIN(CONVERT(VARCHAR(10),dbo.CW_CheckInOut.ApptDate,101))) as appt_date_only,
dbo.CW_CheckInOut.ApptTime,
dbo.item.description,
dbo.item.u_m,
dbo.ISW_LPTrans.qty,
(CASE
WHEN dbo.ISW_LPTrans.trans_type = 'F'
THEN 'Produced internally'
ELSE
(CASE
WHEN dbo.ISW_LPTrans.trans_type = 'R'
THEN 'Received from outside'
END)
END
) as original_source
FROM
dbo.ISW_LPTrans
INNER JOIN dbo.CW_Dock_Schedule ON LTRIM(RTRIM(dbo.ISW_LPTrans.ref_num)) = dbo.CW_Dock_Schedule.ReferenceNumber
INNER JOIN dbo.CW_CheckInOut ON dbo.CW_CheckInOut.TruckID = dbo.CW_Dock_Schedule.TruckID
INNER JOIN dbo.item ON dbo.item.item = dbo.ISW_LPTrans.item
WHERE
(dbo.ISW_LPTrans.trans_type = 'R') AND
--CONVERT(VARCHAR(10),dbo.CW_CheckInOut.ApptDate,101) <= CONVERT(VARCHAR(10),dbo.ISW_LPTrans.trans_date,101) AND
dbo.ISW_LPTrans.lp_num IN
(SELECT DISTINCT
dbo.ISW_LPTrans.lp_num
FROM
dbo.ISW_LPTrans
INNER JOIN dbo.item ON dbo.ISW_LPTrans.item = dbo.item.item
INNER JOIN dbo.job ON dbo.ISW_LPTrans.ref_num = dbo.job.job AND dbo.ISW_LPTrans.ref_line_suf = dbo.job.suffix
WHERE
(dbo.ISW_LPTrans.trans_type = 'W' OR dbo.ISW_LPTrans.trans_type = 'I') AND
dbo.ISW_LPTrans.ref_num IN
(SELECT
dbo.ISW_LPTrans.ref_num
FROM
dbo.ISW_LPTrans
--INNER JOIN dbo.ISW_LPTrans on dbo.ISW_LPTrans.
WHERE
dbo.ISW_LPTrans.item LIKE #item AND
dbo.ISW_LPTrans.lot LIKE #lot AND
dbo.ISW_LPTrans.trans_type = 'F'
GROUP BY
dbo.ISW_LPTrans.ref_num
) AND
dbo.ISW_LPTrans.ref_line_suf IN
(SELECT
dbo.ISW_LPTrans.ref_line_suf
FROM
dbo.ISW_LPTrans
--INNER JOIN dbo.ISW_LPTrans on dbo.ISW_LPTrans.
WHERE
dbo.ISW_LPTrans.item LIKE #item AND
dbo.ISW_LPTrans.lot LIKE #lot AND
dbo.ISW_LPTrans.trans_type = 'F'
GROUP BY
dbo.ISW_LPTrans.ref_line_suf
)
GROUP BY
dbo.ISW_LPTrans.lp_num
HAVING
SUM(dbo.ISW_LPTrans.qty) < 0
)
GROUP BY
dbo.ISW_LPTrans.item,
dbo.ISW_LPTrans.lot,
dbo.ISW_LPTrans.trans_type,
dbo.ISW_LPTrans.lp_num,
dbo.ISW_LPTrans.ref_num,
dbo.CW_CheckInOut.ApptDate,
dbo.CW_CheckInOut.ApptTime,
dbo.item.description,
dbo.item.u_m,
dbo.ISW_LPTrans.qty
ORDER BY
dbo.ISW_LPTrans.lp_num

In a nutshell - the way you use DISTINCT is logically wrong from SQL perspective.
Your DISTINCT is in an IN subquery in the WHERE clause - and at that point of code it has absolutely no effect (except from the performance penalty). Think on it - if the outer query returns non-unique values of dbo.ISW_LPTrans.lp_num (which obvioulsy happens) those values can still be within the distinct values of the IN subquery - the IN does not enforce a 1-to-1 match, it only enforces the fact that the outer query values are within the inner values, but they can match multiple times. So it is definitely not DISTINCT's fault.
I would go through the following check steps:
See if there is insufficient JOIN ON condition(s) in the outer FROM section that leads to data multiplication (e.g. if a table has primary-to-foreign key relation on several columns, but you join on one of them only etc.).
Check which of the sources contains non-distinct records in the outer FROM section - then either cleanse your source, or adjust the JOIN condition and / or the WHERE clause so that you only pick distinct & correct records. In fact you might need to SELECT DISTINCT in the FROM sections - there it would make much more sense.

Related

Removing Duplicate Rows in SQL

I have a query that returns a list of devices that have multiple "moved" dates. I only want the oldest date entry. I used the MIN function to give me the oldest date, but I'm still getting multiple entries (I am, however, getting less than before). I tried to get a more precise JOIN, but I couldn't narrow the fields down any more.
If you'll look at the screenshot, the first three rows have the same "wonum" but three different "Moved Dates." I am thinking that if I can somehow take the oldest "Moved Date" out of those three and remove the other rows, that would give me the result I'm looking for. However, I'm not skilled enough to do that (I've only been working in SQL for a few months now). Would that work, or is there a better way to narrow down my results? I'm wondering if I need to perform some kind of sub-query to get what I need.
I've looked around but can't find anything that allows me to remove a row of data the way I'm looking to. Nor can I seem to find a reason my MIN function isn't paring down the data anymore than it is. Below is the code I'm currently using. Thanks for any help that can be given.
SELECT wo.wonum, wo.location, wo.statusdate, wo.status, l.subcontractor,
wo.description, MIN(ast.datemoved) AS 'Moved Date'
FROM workorder wo
JOIN locations l ON wo.location = l.location
JOIN asset a ON wo.location = a.location
-- AND wo.assetnum = a.assetnum
JOIN assettrans ast ON a.assetnum = ast.assetnum
-- AND a.assetid = ast.assetid
WHERE wo.description LIKE '%deteriorating%'
AND wo.status != 'close'
GROUP BY wo.wonum, wo.location, wo.statusdate,
wo.status, l.subcontractor, wo.description
ORDER BY wo.wonum;
DBV SQL Query Result
Update: Table Data
You need to do the grouping in your join statement inside a subquery(not tested, but you'll get the idea):
Replace
JOIN assettrans ast ON a.assetnum = ast.assetnum
With
inner join
(
select ast.assetnum,MIN(ast.datemoved) AS 'Moved Date'
from assettrans ast
group by ast.assetnum
) grouped
on a.assetnum = grouped.assetnum
So the full query looks like:
SELECT wo.wonum, wo.location, wo.statusdate, wo.status, l.subcontractor,
wo.description, grouped.MovedDate
FROM workorder wo
JOIN locations l ON wo.location = l.location
JOIN asset a ON wo.location = a.location
INNER JOIN
(
select ast.assetnum,MIN(ast.datemoved) AS MovedDate
from assettrans ast
group by ast.assetnum
) grouped
on a.assetnum = grouped.assetnum
WHERE wo.description LIKE '%deteriorating%'
AND wo.status != 'close'
ORDER BY wo.wonum;
Please test before using in production
--if you have id column and leave the oldest record
delete from T1 from MyTable T1, MyTable T2
where T1.dupField = T2.dupField (and add more filters if applies)
and
T1.uniqueField > T2.uniqueField
--if you want to delete the new "Moved Dates" and leave the oldest one
delete from T1 from MyTable T1, MyTable T2
where T1.dupField = T2.dupField (and add more filters if applies)
and
T1.Moved Dates > T2.Moved Dates

Matching values from two columns with the values of two columns in a sub-query

I have a problem I can't really figure out, even though I thought I had the solution.
I think this is DB2 SQL by the way.
I have a customer number and a country code (extracted from a string using SUBSTR) which I don't want to find in combination in a subquery, like so:
SELECT ku.orgnr AS customer ,
Substr(bu.bank_account_swiftadr,5,2) AS country
FROM db811.bet_utl bu
LEFT JOIN db811.henv_utl bh
ON bu.betaling_urn = bh.betaling_urn
LEFT JOIN db811.betaling_status bs
ON bu.betaling_status = bs.betaling_status
LEFT JOIN db811.kunde_orgnr ku
ON bu.kundenr = ku.kundenr
WHERE bu.kanal = 'N'
AND (ku.orgnr, Substr(bu.bank_account_swiftadr,5,2)) ;
Not in the results below
SELECT ku.orgnr AS customer ,
Substr(bu.bank_account_swiftadr,5,2) AS country ,
COUNT(*) AS numberof
FROM db811.bet_utl_hist bu
LEFT JOIN db811.kunde_orgnr ku
ON bu.kundenr = ku.kundenr
WHERE
and bu.kanal = 'N'
AND bu.betalingsdato > '2016-01-01'
GROUP BY ku.orgnr ,
substr(bu.bank_account_swiftadr,5,2);
This should work I though, but it seems to match on just one of them, and I need both to be true in order for me to exclude it with the NOT IN.
I assume I am missing something basic since I am quite new at this.

Where is the signature value read from in this query?

I have the following SQL query, and need to figure out where the "signatures" data is actually being read from. It's not from the 'claims' table, and doesn't seem to be from the 'questionnaire_answers' table. I believe it will be a boolean value, if that helps at all.
I'm reasonably proficient at SQL, but the joins have left me a bit confused.
(There's some PHP, but it's not relevant to the question).
$SQL="SELECT surveyor, COUNT(signed_total) AS 'total', SUM(signed_total) AS 'signed_total' FROM (
SELECT DISTINCT claims.claim_id, CONCAT(surveyors.user_first_name, CONCAT(' ', surveyors.user_surname)) AS 'surveyor', CASE WHEN signatures.claim_id IS NOT NULL THEN 1 ELSE 0 END AS 'signed_total' FROM claims
INNER JOIN users surveyors ON claims.surveyor_id = surveyors.user_id
LEFT OUTER JOIN signatures ON claims.claim_id = signatures.claim_id
INNER JOIN questionnaire_answers ON questionnaire_answers.claim_id = claims.claim_id
WHERE (claims.claim_type <> ".$conn->qstr(TYPE_DESKTOP).")
AND (claims.claim_type <> ".$conn->qstr(TYPE_AUDIT).")
AND (claims.claim_cancelled_id <= 0)
AND (claims.date_completed BETWEEN '".UK2USDate($start_date)." 00:00:00' AND '".UK2USDate($end_date)." 23:59:59')
) AS tmp
GROUP BY surveyor
ORDER BY surveyor ASC
";
Thank you!
signatures is a table (see LEFT OUTER JOIN signatures in your query).
As written in FROM clause :
FROM claims
INNER JOIN users surveyors ON claims.surveyor_id = surveyors.user_id
LEFT OUTER JOIN signatures ON claims.claim_id = signatures.claim_id
The LEFT keyword means that the rows of the left table are preserved; So all rows from claims table are considered and NULL marks are added as placeholders for the attributes from the nonpreserved side of the join which is signatures table here.
So CASE WHEN signatures.claim_id IS NOT NULL THEN 1 ELSE 0 END AS 'signed_total' basically checks that if a match between these two tables exists based on claim_id then signed_total column should have value 1 else 0.
Hope that helps!!

Oracle left outer join, only want the null values

I'm working on a problem with two tables. Charge and ChargeHistory. I want to display a selection of columns from both tables where either the matching row in ChargeHistory has a different value and/or date from Charge or if there is no matching entry in ChargeHistory at all.
I'm using a left outer join declared using the ansi standard and while it does show the rows correctly where there is a difference, it isn't showing the null entries.
I've read that there can sometimes be issues if you are using the WHERE clause as well as the ON clause. However when I try and put all the conditons in the ON clause the query takes too long > 15 minutes (so long I have just cancelled the runs).
To make things worse both tables use a three part compound key.
Does anyone have any ideas as to why the null values are being left out?
SELECT values...
FROM bcharge charge
LEFT OUTER JOIN chgHist history
ON charge.key1 = history.key1 AND charge.key2 = history.key2 AND charge.key3 = history.key3 AND charge.chargeType = history.chargeType
WHERE charge.chargeType = '2'
AND (charge.value <> history.value OR charge.date <> history.date)
ORDER BY key1, key2, key
You probably want to explicitly select the null values:
SELECT values...
FROM bcharge charge
LEFT OUTER JOIN chgHist history
ON charge.key1 = history.key1 AND charge.key2 = history.key2 AND charge.key3 = history.key3 AND charge.chargeType = history.chargeType
WHERE charge.chargeType = '2'
AND ((charge.value <> history.value or history.value is null) OR (charge.date <> history.date or history.date is null))
ORDER BY key1, key2, key
You can explicitly look for a match in the where. I would recommend looking at one of the keys used for the join:
SELECT . . .
FROM bcharge charge LEFT OUTER JOIN
chgHist history
ON charge.key1 = history.key1 AND charge.key2 = history.key2 AND
charge.key3 = history.key3 AND charge.chargeType = history.chargeType
WHERE charge.chargeType = '2' AND
(charge.value <> history.value OR charge.date <> history.date OR history.key1 is null)
ORDER BY key1, key2, key;
The expressions charge.value <> history.value change the left outer join to an inner join because NULL results will be filtered out.
A WHERE clause filters the data returned by a join. Therefore when your inner table has null data for a particular column, the corresponding rows get filtered out based on your specified condition. That is why you should move that logic to the ON clause instead.
For the performance issues, you could consider adding indexes on the columns used for joining and filtering.
Have a look at this site, it will be very helpful for you, visual illustration of all the join statements with code samples
blog.codinghorror.com
Quoted of the relevant info in the above link:
SELECT * FROM TableA
LEFT OUTER JOIN TableB
ON TableA.name = TableB.name
Sample output:
id name id name
-- ---- -- ----
1 Pirate 2 Pirate
2 Monkey null null
3 Ninja 4 Ninja
4 Spaghetti null null
Left outer join
produces a complete set of records from Table A, with the matching records (where available) in Table B. If there is no match, the right side will contain null
For any field from an outer joined table used in the where clause you must also permit an IS NULL option for that same field, otherwise you negate the effect of the outer join and the result is the same as if you had used an inner join.
SELECT
*
FROM bcharge charge
LEFT OUTER JOIN chgHist history
ON charge.key1 = history.key1
AND charge.key2 = history.key2
AND charge.key3 = history.key3
AND charge.chargeType = history.chargeType
WHERE charge.chargeType = '2'
AND (
(charge.value <> history.value OR history.value IS NULL)
OR
(charge.date <> history.date OR history.date IS NULL)
)
ORDER BY
key1, key2, key3
Edit: Appears that this is the same query structure used by Rene above, so treat this one as in support of that please.

Select Case value not sorting correctly in order by

I have a result set that has a defined grouping (for a report). There is a possibility that the position is not assigned and therefore does not have a "Grid_Group". In this case I assign a value of 99. This is working correctly except the order by, the 99 is always first and it should be last (If I do desc it is at bottom). I've tried a cast on the select side as well as within the order by on the Grid_Group, but both have same results with 99 at the top. (Sql server 2008)
Here is the snippet, I remove all other unneeded columns.
SELECT s.SessionNumber,Position.PositionName,(Select CASE when dbo.Position.Grid_Group is null THEN 99 ELSE dbo.Position.Grid_Group END) as Grid_Group
FROM dbo.USession AS us Left Outer JOIN
dbo.Position ON us.PositionId = dbo.Position.PositionId FULL OUTER JOIN
dbo.Sessions AS s ON us.SessionId = s.SessionId
ORDER BY S.SessionNumber, dbo.Position.Grid_Group
Thoughts?
You need to apply the CASE on your order by as well (bear in mind this will ruin index utilization on the sort operation). Your ORDER BY is referencing the original table's column, not your alias result column. Something like this should do the trick:
SELECT
s.SessionNumber,Position.PositionName,
(CASE
WHEN dbo.Position.Grid_Group IS NULL THEN 99
ELSE dbo.Position.Grid_Group
END) AS Grid_Group
FROM dbo.USession AS us
LEFT OUTER JOIN dbo.Position ON us.PositionId = dbo.Position.PositionId
FULL OUTER JOIN dbo.Sessions AS s ON us.SessionId = s.SessionId
ORDER BY
S.SessionNumber,
(CASE
WHEN dbo.Position.Grid_Group IS NULL THEN 99
ELSE dbo.Position.Grid_Group
END)
I allowed myself to apply some minor formatting of your SQL.
An ORDER BY will in this case not see your calculated columns. To get the effect you're asking for, you'll have to ORDER BY the same expression (which kastermester's answer is demonstrating), or wrap the query in a common table expression and ORDER BY while selecting from that, something like;
WITH cte AS (
SELECT s.SessionNumber, p.PositionName, COALESCE(p.Grid_Group, 99) Grid_Group
FROM dbo.USession AS us
LEFT OUTER JOIN dbo.Position p ON us.PositionId = p.PositionId
FULL OUTER JOIN dbo.Sessions s ON us.SessionId = s.SessionId
)
SELECT * FROM cte ORDER BY SessionNumber, Grid_Group;