SQL join two tables and the elements that satisfies one condition - sql

Good afternoon,
I'm having an issue with two tables that I'm trying to join.
What I am trying to do is, I have to print a table with all products that is registered in some agenda (codControl), so the person can put his price.
But first I have to look into lctocotacao to see if he had already given a price to some product. But when I do this, I just get the products that has some price, and the other ones I dont see.
Here is an example of my table cadprodutoscotacao
codProduct desc codControl
1 abc 197
2 cde 197
3 fgh 197
1 abc 198
And my table lctocotacao
codProduct price codControl codPerson
1 2.5000 197 19
2 3.0000 197 37
3 4.5000 198 37
I have this SQL statement at the moment:
SELECT cadc.cod, cadc.desc, lcto.codEnt, lcto.price
FROM cadprodutoscotacao cadc JOIN lctocotacao lcto
ON cadc.codControl = lcto.codControl
AND cadc.codProduct = lcto.codProduct
AND cadc.codControl = '197'
AND lcto.codPerson = '19'
ORDER BY cadc.codControl;
What I'm getting:
cod desc price codPerson codControl
1 abc 2.5000 19 197
And the table I expect
cod desc price codPerson codControl
1 abc 2.5000 19 197
2 cde 197
3 fgh 197
197 and 19 will be parameters to my query.
Any ideas on how to proceed?
E D I T
Basically, I have two queries:
SELECT *
FROM cadprodutoscotacao
WHERE cadc_codControl = '197'
This first, to return all products registered in the agenda '197'.
And the second one:
SELECT *
FROM lctocotacao
WHERE codPerson = 19
AND codControl = '197'
This second one to return products that already has some price added by the Person 19 in the agenda 197.
I have to return one table, including all records from the first query, and, if there is some price in the second one, I have to "concatenate" them.
Thanks in advance.

You need a LEFT JOIN, but you also need to be careful about the filtering conditions:
SELECT cadc.cod, cadc.desc, lcto.codEnt, lcto.price
FROM cadprodutoscotacao cadc LEFT JOIN
lctocotacao lcto
ON cadc.codControl = lcto.codControl AND
cadc.cod = lcto.cod AND
lcto.codEnt = '19'
WHERE cadc.codControl = '197'
ORDER BY cadc_codigo;
A LEFT JOIN keeps all rows in the first table, regardless of whether a match is found in the ON conditions. This applies to conditions on the first table as well as the second. Hence, you don't want to put filters on the first table in the ON clause.
The rule is: When using LEFT JOIN put filters on the first table in the WHERE clause. Filters on the second table go in the ON clause (otherwise the outer join is generally turned into an inner join).

Your rows are filtered because you specified JOIN, which is a shortcut for INNER JOIN
If you want all the records from the left table, even if they don't have correlated records in the right table, you should do a LEFT JOIN:
SELECT cadc.cod, cadc.desc, lcto.codEnt, lcto.price
FROM cadprodutoscotacao cadc
LEFT JOIN lctocotacao lcto
ON cadc.codControl = lcto.codControl
AND cadc.cod = lcto.cod
AND cadc.codControl = '197'
AND lcto.codEnt = '19'
ORDER BY cadc_codigo;

I don't understand your example. What are the primary keys? "cod" and "codentry" appear in both tables. Your schema seems to be very redundant.
But whenever someone JOINs and is missing some entries, it might be solved by using a LEFT OUTER JOIN.

Related

SQL Query with 2 joins and different values

I'm quite the beginner so I suppose some of you would have an easy time on my task but I need some help:
I have 3 DBs. dbo_A_Personal, dbo_Z_Ferien and dbo_Z_ERFASSUNG
A_Pers has a Pers_ID (LPE_ID) that I can use to join Z_Ferien and Z_ERFASSUNG on.
In Z_Ferien I have 4 rows with that pers_ID and in Z_ERFASSUNG 96.
What I need is a result that has columns that are basically like that:
PersID
Erf
Fer
1224
5
0
1234
4
0
1234
6
0
1234
0
6
so far I have this:
SELECT dbo_A_PERSONAL.LPE_ID, dbo_Z_Ferien.ZFE_TAGE, dbo_Z_ERFASSUNG.ZER_Std100
FROM dbo_A_PERSONAL
INNER JOIN dbo_Z_Ferien ON dbo_A_PERSONAL.LPE_ID = dbo_Z_Ferien.ZFE_LPE_ID
INNER JOIN dbo_Z_ERFASSUNG ON dbo_A_PERSONAL.LPE_ID = dbo_Z_ERFASSUNG.ZER_LPE
WHERE dbo_A_PERSONAL.LPE_ID=804 AND dbo_Z_ERFASSUNG.ZER_EIGENSCH = 3;
I need that so I can sum up the value I need from Z_ERFASSUNG and Z_Ferien but I don't know how to make it so each value is only "printed" once.
I hope I explained it well enough so you guys can help me out.
If I understand correctly an aggerate function is what you need here.
I added a sum function of both dbo_Z_Ferien & dbo_Z_ERFASSUNG, as well as adding a group by statement for LPE_ID. Which tells SQL to partition the sum only on LPE_ID
SELECT dbo_A_PERSONAL.LPE_ID, sum(dbo_Z_Ferien.ZFE_TAGE), sum(dbo_Z_ERFASSUNG.ZER_Std100)
FROM dbo_A_PERSONAL
INNER JOIN dbo_Z_Ferien ON dbo_A_PERSONAL.LPE_ID = dbo_Z_Ferien.ZFE_LPE_ID
INNER JOIN dbo_Z_ERFASSUNG ON dbo_A_PERSONAL.LPE_ID = dbo_Z_ERFASSUNG.ZER_LPE
WHERE dbo_A_PERSONAL.LPE_ID=804 AND dbo_Z_ERFASSUNG.ZER_EIGENSCH = 3
GROUP BY dbo_A_PERSONAL

SQL Server: Two COUNTs in one query multiplying with one another in output

I have a query is used to display information in a queue and part of that information is showing the amount of child entities (packages and labs) that belong to the parent entity (change). However instead of showing the individual counts of each type of child, they multiply with one another.
In the below case, there are supposed to be 3 labs and 18 packages, however the the multiply with one another and the output is 54 of each.
Below is the offending portion of the query.
SELECT cef.ChangeId, COUNT(pac.PackageId) AS 'Packages', COUNT(lab.LabRequestId) AS 'Labs'
FROM dbo.ChangeEvaluationForm cef
LEFT JOIN dbo.Lab
ON cef.ChangeId = Lab.ChangeId
LEFT JOIN dbo.Package pac
ON (cef.ChangeId = pac.ChangeId AND pac.PackageStatus != 6 AND pac.PackageStatus !=7)
WHERE cef.ChangeId = 255
GROUP BY cef.ChangeId
I feel like this is obvious but it's not occurring to me how to fix it so the two counts are independent of one another like to me they should be. There doesn't seem to be a scenario like this in any of my research either. Can anyone guide me in the right direction?
Because you do multiply source rows by each left join. So sometimes you have more likely cross join here.
SELECT cef.ChangeId, p.Packages, l.Labs
FROM dbo.ChangeEvaluationForm cef
OUTER APPLY(
SELECT COUNT(*) as Labs
FROM dbo.Lab
WHERE cef.ChangeId = Lab.ChangeId
) l
OUTER APPLY(
SELECT COUNT(*) AS Packages
FROM dbo.Package pac
WHERE (cef.ChangeId = pac.ChangeId AND pac.PackageStatus != 6 AND pac.PackageStatus !=7)
) p
WHERE cef.ChangeId = 255
GROUP BY cef.ChangeId
perhaps GROUP BY is not needed now.
From you question its difficult to derive what result do you expect from your query. So I presume you want following result:
+----------+----------+------+
| ChangeId | Packages | Labs |
+----------+----------+------+
| 255 | 18 | 3 |
+----------+----------+------+
Try below query if you are looking for above mentioned result.
SELECT cef.ChangeId, ISNULL(pac.PacCount, 0) AS 'Packages', ISNULL(Lab.LabCount, 0) AS 'Labs'
FROM dbo.ChangeEvaluationForm cef
LEFT JOIN (SELECT Lab.ChangeId, COUNT(*) LabCount FROM dbo.Lab GROUP BY) Lab
ON cef.ChangeId = Lab.ChangeId
LEFT JOIN (SELECT pac.ChangeId, COUNT(*) PacCount FROM dbo.Package pac WHERE pac.PackageStatus != 6 AND pac.PackageStatus !=7 GROUP BY pac.ChangeId) pac
ON cef.ChangeId = pac.ChangeId
WHERE cef.ChangeId = 255
Query Explanation:
In your query you didn't use group by, so it ended up giving you 54 as count which is Cartesian product.
In this query I tried to group by 'ChangeId' and find aggregate before joining tables. So 3 labs and 18 packages will be counted before join.
Your will also notice that I have moved PackageStatus filter before group by in pac table. So unwanted record won't mess with our count.
You start with a particular ChangeId from the dbo.ChangeEvaluationForm table (ChangeId = 255 from your example), then join to the dbo.Lab table. This join makes your result go from 1 row to 3, considering there are 3 Labs with ChangeId = 255. Your problem is on the next join, you are joining all 3 resulting rows from the previous join with the dbo.Package table, which has 18 rows for ChangeId = 255. The resulting count for columns pac.PackageId and lab.LabRequestId will then be 3 x 18 = 54.
To get what you want, there are 2 easy solutions:
Use COUNT DISTINCT instead of COUNT. This will just count the different values of pac.PackageId and lab.LabRequestId and not the repeated ones.
Split the joins into 2 subqueries and join their result (by ChangeId)

How do you retrieve the top two records within each grouping

In my table, I have data that looks like this:
CODE DATE PRICE
100 1/1/13 $500
100 2/1/13 $521
100 3/3/13 $530
100 5/9/13 $542
222 3/3/13 $20
350 1/1/13 $200
350 3/1/13 $225
Is it possible to create query to pull out the TWO most recent records by DATE? AND only if there are 2+ dates for a specific code. So the result would be:
CODE DATE PRICE
100 5/9/13 $542
100 3/3/13 $530
350 3/1/13 $225
350 1/1/13 $200
Bonus points if you can put both prices/dates on the same line, like this:
CODE OLD_DATE OLD_PRICE NEW_DATE NEW_PRICE
100 3/3/13 $530 5/9/13 $542
350 1/1/13 $200 3/1/13 $225
Thank you!!!
I managed to solve it with 5 sub-queries and 1 rollup query.
First we have a subquery that gives us the MAX date for each code.
Next, we do the same subquery, except we exclude our previous results.
We assume that your data is already rolled up and you won't have duplicate dates for the same code.
Next we bring in the appropriate Code / Price for the latest and 2nd latest date. If a code doesn't exist in the 2nd Max query - then we don't include it at all.
In the union query we're combining the results of both. In the Rollup Query, we're sorting and removing null values generated in the union.
Results:
CODE MaxOfOLDDATE MaxOfOLDPRICE MaxOfNEWDATE MaxOfNEWPRICE
100 2013-03-03 $530.00 2013-05-09 542
350 2013-01-01 $200.00 2013-03-01 225
Using your Data in a table called "Table", create the following queries:
SUB_2ndMaxDatesPerCode:
SELECT Table.CODE, Max(Table.Date) AS MaxOfDATE1
FROM SUB_MaxDatesPerCode RIGHT JOIN [Table] ON (SUB_MaxDatesPerCode.MaxOfDATE = Table.DATE) AND (SUB_MaxDatesPerCode.CODE = Table.CODE)
GROUP BY Table.CODE, SUB_MaxDatesPerCode.CODE
HAVING (((SUB_MaxDatesPerCode.CODE) Is Null));
SUB_MaxDatesPerCode:
SELECT Table.CODE, Max(Table.Date) AS MaxOfDATE
FROM [Table]
GROUP BY Table.CODE;
SUB_2ndMaxData:
SELECT Table.CODE, Table.Date, Table.PRICE
FROM [Table] INNER JOIN SUB_2ndMaxDatesPerCode ON (Table.DATE = SUB_2ndMaxDatesPerCode.MaxOfDATE1) AND (Table.CODE = SUB_2ndMaxDatesPerCode.Table.CODE);
SUB_MaxData:
SELECT Table.CODE, Table.Date, Table.PRICE
FROM ([Table] INNER JOIN SUB_MaxDatesPerCode ON (Table.DATE = SUB_MaxDatesPerCode.MaxOfDATE) AND (Table.CODE = SUB_MaxDatesPerCode.CODE)) INNER JOIN SUB_2ndMaxDatesPerCode ON Table.CODE = SUB_2ndMaxDatesPerCode.Table.CODE;
SUB_Data:
SELECT CODE, DATE AS OLDDATE, PRICE AS OLDPRICE, NULL AS NEWDATE, NULL AS NEWPRICE FROM SUB_2ndMaxData;
UNION ALL SELECT CODE, NULL AS OLDDATE, NULL AS OLDPRICE, DATE AS NEWDATE, PRICE AS NEWPRICE FROM SUB_MaxData;
Data (Rollup):
SELECT SUB_Data.CODE, Max(SUB_Data.OLDDATE) AS MaxOfOLDDATE, Max(SUB_Data.OLDPRICE) AS MaxOfOLDPRICE, Max(SUB_Data.NEWDATE) AS MaxOfNEWDATE, Max(SUB_Data.NEWPRICE) AS MaxOfNEWPRICE
FROM SUB_Data
GROUP BY SUB_Data.CODE
ORDER BY SUB_Data.CODE;
There you go - thanks for the challenge.
Accessing the recent data
To access the recent data, you use TOP 2. Such as you inverse the data from the table, then select the top 2. Just as you start ABC from ZYX and select the TOP 2 which would provide you with ZY.
SELECT TOP 2 * FROM table_name ORDER BY column_time DESC;
This way, you reverse the table, and then select the most recent two from the top.
Joining the Tables
To join the two columns and create a result from there quest you can use JOIN (INNER JOIN; I prefer this) such as:
SELECT TOP 2 * FROM table_name INNER JOIN table_name.column_name ON
table_name.column_name2
This way, you will join both the tables where a value in one column matches the value from the other column in both tables.
You can use a for loop for this to select the value for them, or you can use this inside the foreach loop to take out the values for them.
My suggestion
My best method would be to, first just select the data that was ordered using the date.
Then inside the foreach() loop where you will write the data for that select the remaining data for that time. And write it inside that loop.
Code (column_name) won't bother you
And when you will reference the query using ORDER By Time Desc you won't be using the CODE anymore such as WHERE Code = value. And you will get the code for the most recent ones. If you really need the code column, you can filter it out using and if else block.
Reference:
http://technet.microsoft.com/en-us/library/ms190014(v=sql.105).aspx (Inner join)
http://www.w3schools.com/sql/sql_func_first.asp (top; check the Sql Server query)

Sorting by date across two separate columns in a Full Outer Join

I have two columns of data I am lining up using a Full Outer Join but it includes two separate date columns which make it challenging to sort by.
Table 1 has sales rank data for a product.
Table 2 has actual sales data for the same product.
Each table may have entries for dates on which the other does not.
So envision after the full join, we end up with something like this simplified example:
ProdID L.Date P.Date Rank Units
101 null 2011-10-01 null 740
101 2011-10-02 2011-10-02 23 652
101 2011-10-03 null 32 null
Here is the query I am using to pull this data:
select L.ListID, L.ASIN, L.date, L.ranking, P.ASIN, P.POSdate, P.units from ListItem L
full outer join POSdata P on
L.ASIN = P.ASIN and
L.date = P.POSdate and
(L.ListID = 1 OR L.ASIN is null)
where (L.ASIN = 'xxxxxxxxxx' and L.ListID = 1) or
(P.ASIN = 'xxxxxxxxxx' and L.BookID is null)
order by POSdate, date
It's a bit more complex because products may appear on multiple lists so I have to account for that as well, but it returns the data I need. I am open to suggestions on improving it of course should someone have one.
The problem is, how can I sort this properly when both date columns are likely to have at least some NULLs in them. The way I am Ordering By now will not work when both columns have at one NULL.
Thanks.
ORDER BY ISNULL(P.POSdate,L.date) should do what you need I think?

Problem with SQL Join

I have two tables, tblEntities and tblScheduling.
tblEntities:
EntityID ShortName Active
1 Dirtville 1
2 Goldtown 1
3 Blackston 0
4 Cornfelt 1
5 Vick 1
tblScheduling:
ScheduleID EntityID SchedulingYearID
1 1 20
2 1 21
3 2 20
4 3 19
5 5 20
I need a query that will show ALL ACTIVE Entities and their schedule information for a particular ScheduleYearID.
Output should look like (the desired SchedulingYearID in this case is 20):
EntityID ScheduleID
1 1
2 3
4 NULL
5 5
The query that I have written so far is:
SELECT tblEntities.EntityID, tblEntities.ShortName, tblScheduling.ScheduleID
FROM tblScheduling RIGHT OUTER JOIN
tblEntities ON tblScheduling.EntityID = tblEntities.EntityID
WHERE (tblScheduling.SchedulingYearID = #SchedulingYearID)
AND (tblEntities.Active = 1)
ORDER BY tblEntities.EntityID
My problem is that using this query it will not include active entities without schedule information (such as EntityID 4 in the example above). I can write the query to display all active entities and their schedule status fine, but once I start limiting it via the SchedulingYearID I lose those particular entities.
Are there any solutions that I am obviously missing without having to resort to subqueries, cursors, etc.? If not it's not a big deal, I just feel like I am missing something simple here.
Try this... Join conditions are evaluated to produce the intermediate Join result set, and then, (for an outer join), all the rows from the "Outer" side are added back in before moving on... Where conditions are evaluated after all joins are done...
SELECT E.EntityID, E.ShortName, S.ScheduleID
FROM tblEntities E
Left Join tblScheduling S
ON S.EntityID = E.EntityID
And S.SchedulingYearID = #SchedulingYearID
WHERE E.Active = 1
ORDER BY E.EntityID
I change your join order cause I prefer left joins... but it doesn't matter
It's your conditions in the where clause:
(tblScheduling.SchedulingYearID = #SchedulingYearID)
when there is no tblScheduling info this wil always fail. Add
(((tblScheduling.SchedulingYearID = #SchedulingYearID) OR (tblScheduling.SchedulingYearID is null) )
or wathever null condition checking your DB uses.
I think the trouble is that the WHERE clause is filtering out the rows where SchedulingYearID is null. So don't.
SELECT tblEntities.EntityID, tblEntities.ShortName, tblScheduling.ScheduleID
FROM tblScheduling RIGHT OUTER JOIN
tblEntities ON tblScheduling.EntityID = tblEntities.EntityID
WHERE (tblScheduling.SchedulingYearID = #SchedulingYearID OR
tblScheduling.SchedulingYearID IS NULL)
AND (tblEntities.Active = 1)
ORDER BY tblEntities.EntityID;