Sql Query Linking Two Tables - sql

One doubt in MSSQL.
There are two tables in a databases.
Table 1 named Property contain
fields PRPT_Id(int),PRPT_Name(varchar), PRPT_Status(bit)
Table 2 named PropertyImages contain fields PIMG_Id(int),PIMG_ImageName(varchar),PRPT_Id(int),PIMG_Status(bit)
These two tables follow a one-to-many relationship.
That means the each Property can have zero, one or more PropertyImages corresponding to it.
What is required is a query to display
PRPT_Id, PRPT_Name, ImageCount(Count of all images corresponding to a PRPT_Id where PIMG_Status is true. o if there arent any images), FirstImageName(if there are n images, the name of the first image in the image table corresponding to the PRPT_Id with PIMG_Status true. if there aren't any images we fill that with whitespace/blank) . another condition is that PRPT_Status should be true.
Edit Note - Both the tables are having autoincremented integers as primary key.
So first Image name will be the name with MIN(PIMG_Id), isn't that so?
I want the PIMG_ImageName corresponding to the MIN(PIMG_ID) in the resultset

Assuming that FirstImage means the one with the lowest Id, then this should be at least close enough to test to completion:
SELECT
PRPT_Id,
PRPT_Name,
ISNULL(pi1.ImageName, '') AS FirstImageName,
COUNT(1) AS ImageCount
FROM Property AS p
LEFT JOIN PropertyImages AS pi
ON p.PRPT_Id = pi.PRPT_Id
LEFT JOIN PropertyImage AS pi1
ON p.PRPT_Id = pi1.PRPT_Id
LEFT JOIN PropertyImgage AS pi2
ON p.PRPT_Id = pi2.PRPT_Id
AND pi1.PIMG_Id > pi2.PIMG_Id
WHERE PRPT_Status = TRUE
AND pi1.PIMG_Status = TRUE
AND pi2.PIMG_ImageName IS NULL
The double LEFT JOIN assures that you get the first image record in pi1. If the "First" rule is different, then adjust this join accordingly.
This should be about as efficient as possible. It has no subqueries.

It seems that you have to write nested queries to display what you want.
If that's the case (I'm no SQL expert), I'd recommend you to start with the innermost query, then you go out until you reach the outermost (and final) query.
First, you have to retrieve the PIMGs and group them by PRPT.
SELECT PRPT_Id, COUNT(PIMG_Id) AS PRPT_ImageCount, MIN(PIMG_Id) AS PRPT_MinImage
FROM PropertyImages
GROUP BY PRPT_Id
That retrieves the PRPT_Id's of the properties that do have associated images. However, you don't get any results for the properties that don't have any associated images.
After that, we will left join the Properties table with the previous query. The left join ensures that all the Properties will be retrieved, even if they don't appear in the results of the right query (that is, even if they don't have any associated images).
SELECT Properties.*, PRPT_ImageCount, PRPT_MinImage
FROM Properties LEFT JOIN (
SELECT PRPT_Id, COUNT(PIMG_Id) AS PRPT_ImageCount, MIN(PIMG_Id) AS PRPT_MinImage
FROM PropertyImages
GROUP BY PRPT_Id ) Temp ON ( Properties.PRPT_Id = Temp.PRPT_Id )
I hope that my SQL isn't wrong and that this post helps you.
Regards,
Edit:
SELECT Properties.*,
PRPT_ImageCount,
PRPT_MinImage,
PIMG_ImageName
FROM ( Properties LEFT JOIN
( SELECT PRPT_Id,
COUNT(PIMG_Id) AS PRPT_ImageCount,
MIN(PIMG_Id) AS PRPT_MinImage
FROM PropertyImages
GROUP BY PRPT_Id ) Temp1
ON ( Properties.PRPT_Id = Temp1.PRPT_Id ) ) Temp2 LEFT JOIN
PropertyImages ON ( PropertyImages.PIMG_Id = Temp2.PRPT_MinImage )
Now, I'm really unsure of my SQL.

the name of the first image in the image table corresponding to the PRPT_Id with PIMG_Status true
You may want to define "first" in this context. Tables aren't really ordered so, unless you keep your own ordering, the term first needs to mean "first one found".
Assuming the above is true (you want first found image), then that's the only real hard part of the query (and the type of thing that's tripped me up before). The rest of it seems fairly straight forward. If I can find the time tomorrow, I'll try to put something together for you... but it seems likely someone else will be able to provide the answer before then.

Related

Complex SQL View with Joins & Where clause

My SQL skill level is pretty basic. I have certainly written some general queries and done some very generic views. But once we get into joins, I am choking to get the results that I want, in the view I am creating.
I feel like I am almost there. Just can't get the final piece
SELECT dbo.ics_supplies.supplies_id,
dbo.ics_supplies.old_itemid,
dbo.ics_supplies.itemdescription,
dbo.ics_supplies.onhand,
dbo.ics_supplies.reorderlevel,
dbo.ics_supplies.reorderamt,
dbo.ics_supplies.unitmeasure,
dbo.ics_supplies.supplylocation,
dbo.ics_supplies.invtype,
dbo.ics_supplies.discontinued,
dbo.ics_supplies.supply,
dbo.ics_transactions.requsitionnumber,
dbo.ics_transactions.openclosed,
dbo.ics_transactions.transtype,
dbo.ics_transactions.originaldate
FROM dbo.ics_supplies
LEFT OUTER JOIN dbo.ics_orders
ON dbo.ics_supplies.supplies_id = dbo.ics_orders.suppliesid
LEFT OUTER JOIN dbo.ics_transactions
ON dbo.ics_orders.requisitionnumber =
dbo.ics_transactions.requsitionnumber
WHERE ( dbo.ics_transactions.transtype = 'PO' )
When I don't include the WHERE clause, I get 17,000+ records in my view. That is not correct. It's doing this because we are matching on a 1 to many table. Supplies table is 12,000 records. There should always be 12,000 records. Never more. Never less.
The pieces that I am missing are:
I only need ONE matching record from the ICS_Transactions Table. Ideally, the one that I want is the most current 'ICS_Transactions.OriginalDate'.
I only want the ICS_Transactions Table fields to populate IF ICS_Transacions.Type = 'PO'. Otherwise, these fields should remain null.
Sample code or anything would help a lot. I have done a lot of research on joins and it's still very confusing to get what I need for results.
EDIT/Update
I feel as if I asked my question in the wrong way, or didn't give a good overall view of what I am asking. For that, I apologize. I am still very new to SQL, but trying hard.
ICS_Supplies Table has 12,810 records
ICS_Orders Table has 3,666 records
ICS_Transaction Table has 4,701 records
In short, I expect to see a result of 12,810 records. No more and no less. I am trying to create a View of ALL records from the ICS_Supplies table.
Not all records in Supply Table are in Orders and or Transaction Table. But still, I want to see all 12,810 records, regardless.
My users have requested that IF any of these supplies have an open PO (ICS_Transactions.OpenClosed = 'Open' and ICS_Transactions.InvType = 'PO') Then, I also want to see additional fields from ICS_Transactions (ICS_Transactions.OpenClosed, ICS_Transactions.InvType, ICS_Transactions.OriginalDate, ICS_Transactions.RequsitionNumber).
If there are no open PO's for supply record, then these additional fields should be blank/null (regardless to what data is in these added fields, they should display null if they don't meet the criteria).
The ICS_Orders Table is nly needed to hop from the ICS_Supplies to the ICS_Transactions (I first, need to obtain the Requisition Number from the Orders field, if there is one).
I am sorry if I am not doing a good job to explain this. Please ask if you need clarification.
Here's a simplified version of Ross Bush's answer (It removes a join from the CTE to keep things more focussed, speed things up, and cut down the code).
;WITH
ordered_ics_transactions AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY requisitionnumber
ORDER BY originaldate DESC
)
AS seq_id
FROM
dbo.ics_transactions
)
SELECT
s.supplies_id, s.old_itemid,
s.itemdescription, s.onhand,
s.reorderlevel, s.reorderamt,
s.unitmeasure, s.supplylocation,
s.invtype, s.discontinued,
s.supply,
t.requsitionnumber, t.openclosed,
t.transtype, t.originaldate
FROM
dbo.ics_supplies AS s
LEFT OUTER JOIN
dbo.ics_orders AS o
ON o.supplies_id = s.suppliesid
LEFT OUTER JOIN
ordered_ics_transactions AS t
ON t.requisitionnumber = o.requisitionnumber
AND t.transtype = 'PO'
AND t.seq_id = 1
This will only join the most recent transaction record for each requisitionnumber, and only if it has transtype = 'PO'
IF you want to reverse that (joining only transaction records that have transtype = 'PO', and of those only the most recent one), then move the transtype = 'PO' filter to be a WHERE clause inside the ordered_ics_transactions CTE.
You can possibly work with the query below to get what you need.
1. I only need ONE matching record from the ICS_Transactions Table. Ideally, the one that I want is the most current 'ICS_Transactions.OriginalDate'.
I would solve this by creating a CTE with all the ICS_Transaction fields needed in the query, rank-ordered by OPriginalDate, partitioned by suppliesid.
2. I only want the ICS_Transactions Table fields to populate IF ICS_Transacions.Type = 'PO'. Otherwise, these fields should remain null.
If you move the condition from the WHERE clause to the LEFT JOIN then ICS_Transactions not matching the criteria will be peeled and replaced with null values with the rest of the query records.
;
WITH ReqNumberRanked AS
(
SELECT
dbo.ICS_Orders.SuppliesID,
dbo.ICS_Transactions.RequisitionNumber,
dbo.ICS_Transactions.TransType,
dbo.ICS_Transactions.OriginalDate,
dbo.ICS_Transactions.OpenClosed,
RequisitionNumberRankReversed = RANK() OVER(PARTITION BY dbo.ICS_Orders.SuppliesID, dbo.ICS_Transactions.RequisitionNumber ORDER BY dbo.ICS_Transactions.OriginalDate DESC)
FROM
dbo.ICS_Orders
LEFT OUTER JOIN dbo.ICS_Transactions ON dbo.ICS_Orders.RequisitionNumber = dbo.ICS_Transactions.RequsitionNumber
)
SELECT
dbo.ICS_Supplies.Supplies_ID, dbo.ICS_Supplies.Old_ItemID,
dbo.ICS_Supplies.ItemDescription, dbo.ICS_Supplies.OnHand,
dbo.ICS_Supplies.ReorderLevel, dbo.ICS_Supplies.ReorderAmt,
dbo.ICS_Supplies.UnitMeasure,
dbo.ICS_Supplies.SupplyLocation, dbo.ICS_Supplies.InvType,
dbo.ICS_Supplies.Discontinued, dbo.ICS_Supplies.Supply,
ReqNumberRanked.RequsitionNumber,
ReqNumberRanked.OpenClosed,
ReqNumberRanked.TransType,
ReqNumberRanked.OriginalDate
FROM
dbo.ICS_Supplies
LEFT OUTER JOIN dbo.ICS_Orders ON dbo.ICS_Supplies.Supplies_ID = dbo.ICS_Orders.SuppliesID
LEFT OUTER JOIN ReqNumberRanked ON ReqNumberRanked.RequisitionNumber = dbo.ICS_Transactions.RequsitionNumber
AND (ReqNumberRanked.TransType = 'PO')
AND ReqNumberRanked.RequisitionNumberRankReversed = 1

Beginner SQL trouble with simple Join/Subquery

I know this is a quite a basic query, but I can't find exactly the solution for my needs, so here goes...
I have a table of customers INT_AUX_LISTING, a table of folders in which they're contained INT_AUX_DIRECTORY and a joining table INT_AUX_DIR_LIST.
I need to return a list of all customers who are in folder 40017, and also not in folder 2. Any other folder in which they're contained is irrelevant.
So far I've come up with
SELECT *
FROM INT_AUX_LISTING l
LEFT JOIN INT_AUX_DIR_LIST dl
ON l.LISTING_ID=dl.LISTING_ID
WHERE dl.CONTAIN_DIR_ID=40017
AND dl.CONTAIN_DIR_ID <> 2
However this (obviously) isn't correct, and is returning far too many values. I think I need to introduce a subquery but things go awry when I try.
Apologies for the entry level nature of the question, but it's only my 3rd day working with SQL!
First, clarification on left-join and where. If you have a query that has a left-join to a table (per your example where "dl" was the right-side of the join), and then add the "dl" alias to a where clause, that in essence converts it to an INNER JOIN (unless you explicitly tested with an OR dl.listing_ID IS NULL).
Now, back to your primary objective. Without the table structures, and not exactly clear on your column intent, it APPEARS that the "listing_id" column is your client as found in the int_aux_listing table. the "contain_dir_id" is the directory (one of many that may be possible) as found in the int_aux_dir_list.
That said, you want a list of all people who are in 40017 AND NOT in 2. For this, you can do a left-join but using the int_aux_listing table twice but with different aliases. Once you get the list of clients, you can then join that to your client table. Since you are new, take the sample one step at a time.
Just get clients within 40017
select
dl.listing_id
from
int_aux_dir_list dl
where
dl.contain_dir_id = 40017
Now, exclude those if they are found in directory 2
select
dl.listing_id
from
int_aux_dir_list dl
left join int_aux_dir_list dl2
on dl.listing_id = dl2.listing_id
AND dl2.contain_dir_id = 2
where
dl.contain_dir_id = 40017
AND dl2.listing_id IS NULL
By doing a left-join to the directory listing table a second time (via alias "dl2"), but specifically for its directory id = 2 (via AND dl2.contain_dir_id = 2), you are getting all clients from dl that are within directory 40017 and looking for that same client ID in the second instance ("dl2").
Here's the kicker. Notice the where clause is stating "AND dl2.listing_id IS NULL".
This is basically saying WHEN Looking at the DL2 instance, I only want records that DO NOT have a corresponding listing ID matched.
Now, wrap this up to get the client information you need. In this case, getting all columns from the directory listing (in case other columns of importance), AND getting all columns from the "l"isting table.
select
dl.*,
l.*
from
int_aux_dir_list dl
left join int_aux_dir_list dl2
on dl.listing_id = dl2.listing_id
AND dl2.contain_dir_id = 2
join INT_AUX_LISTING l
on dl.listing_id = l.listing_id
where
dl.contain_dir_id = 40017
AND dl2.listing_id IS NULL
Hopefully this clears things up for you some.
Your WHERE clause is the problem; it brings back ALL rows except the one where that id is not 2, including the one where id is 40017 (since it's not equal to 2). I think you should revisit it.
If you use a LEFT JOIN[1] on INT_AUX_LISTING, you will get all customers even if they don't have a corresponding folder. In your case it's best to use INNER JOIN[2] instead.
In the where clause, you're first statement selects customers who are in folder 40017 (dl.CONTAIN_DIR_ID=40017), because of this your second statement (dl.CONTAIN_DIR_ID <> 2) will always be true (40017 will never be equal to 2). So basically your selecting all customers in folder 40017.
Your notion of a subquery was correct. This is one way of getting the desired result:
SELECT *
FROM INT_AUX_LISTING l
INNER JOIN INT_AUX_DIR_LIST dl1
ON l.LISTING_ID = dl1.LISTING_ID
WHERE dl1.CONTAIN_DIR_ID = 40017
AND l.LISTING_ID NOT IN (
SELECT dl2.LISTING_ID FROM INT_AUX_DIR_LIST dl2 WHERE dl2.CONTAIN_DIR_ID = 2
)

In an EXISTS can my JOIN ON use a value from the original select

I have an order system. Users with can be attached to different orders as a type of different user. They can download documents associated with an order. Documents are only given to certain types of users on the order. I'm having trouble writing the query to check a user's permission to view a document and select the info about the document.
I have the following tables and (applicable) fields:
Docs: DocNo, FileNo
DocAccess: DocNo, UserTypeWithAccess
FileUsers: FileNo, UserType, UserNo
I have the following query:
SELECT Docs.*
FROM Docs
WHERE DocNo = 1000
AND EXISTS (
SELECT * FROM DocAccess
LEFT JOIN FileUsers
ON FileUsers.UserType = DocAccess.UserTypeWithAccess
AND FileUsers.FileNo = Docs.FileNo /* Errors here */
WHERE DocAccess.UserNo = 2000 )
The trouble is that in the Exists Select, it does not recognize Docs (at Docs.FileNo) as a valid table. If I move the second on argument to the where clause it works, but I would rather limit the initial join rather than filter them out after the fact.
I can get around this a couple ways, but this seems like it would be best. Anything I'm missing here? Or is it simply not allowed?
I think this is a limitation of your database engine. In most databases, docs would be in scope for the entire subquery -- including both the where and in clauses.
However, you do not need to worry about where you put the particular clause. SQL is a descriptive language, not a procedural language. The purpose of SQL is to describe the output. The SQL engine, parser, and compiler should be choosing the most optimal execution path. Not always true. But, move the condition to the where clause and don't worry about it.
I am not clear why do you need to join with FileUsers at all in your subquery?
What is the purpose and idea of the query (in plain English)?
In any case, if you do need to join with FileUsers then I suggest to use the inner join and move second filter to the WHERE condition. I don't think you can use it in JOIN condition in subquery - at least I've never seen it used this way before. I believe you can only correlate through WHERE clause.
You have to use aliases to get this working:
SELECT
doc.*
FROM
Docs doc
WHERE
doc.DocNo = 1000
AND EXISTS (
SELECT
*
FROM
DocAccess acc
LEFT OUTER JOIN
FileUsers usr
ON
usr.UserType = acc.UserTypeWithAccess
AND usr.FileNo = doc.FileNo
WHERE
acc.UserNo = 2000
)
This also makes it more clear which table each field belongs to (think about using the same table twice or more in the same query with different aliases).
If you would only like to limit the output to one row you can use TOP 1:
SELECT TOP 1
doc.*
FROM
Docs doc
INNER JOIN
FileUsers usr
ON
usr.FileNo = doc.FileNo
INNER JOIN
DocAccess acc
ON
acc.UserTypeWithAccess = usr.UserType
WHERE
doc.DocNo = 1000
AND acc.UserNo = 2000
Of course the second query works a bit different than the first one (both JOINS are INNER). Depeding on your data model you might even leave the TOP 1 out of that query.

Why does my left join in Access have fewer rows than the left table?

I have two tables in an MS Access 2010 database: TBLIndividuals and TblIndividualsUpdates. They have a lot of the same data, but the primary key may not be the same for a given person's record in both tables. So I'm doing a join between the two tables on names and birthdates to see which records correspond. I'm using a left join so that I also get rows for the people who are in TblIndividualsUpdates but not in TBLIndividuals. That way I know which records need to be added to TBLIndividuals to get it up to date.
SELECT TblIndividuals.PersonID AS OldID,
TblIndividualsUpdates.PersonID AS UpdateID
FROM TblIndividualsUpdates LEFT JOIN TblIndividuals
ON ( (TblIndividuals.FirstName = TblIndividualsUpdates.FirstName)
and (TblIndividuals.LastName = TblIndividualsUpdates.LastName)
AND (TblIndividuals.DateBorn = TblIndividualsUpdates.DateBorn
or (TblIndividuals.DateBorn is null
and (TblIndividuals.MidName is null and TblIndividualsUpdates.MidName is null
or TblIndividuals.MidName = TblIndividualsUpdates.MidName))));
TblIndividualsUpdates has 4149 rows, but the query returns only 4103 rows. There are about 50 new records in TblIndividualsUpdates, but only 4 rows in the query result where OldID is null.
If I export the data from Access to PostgreSQL and run the same query there, I get all 4149 rows.
Is this a bug in Access? Is there a difference between Access's left join semantics and PostgreSQL's? Is my database corrupted (Compact and Repair doesn't help)?
ON (
TblIndividuals.FirstName = TblIndividualsUpdates.FirstName
and
TblIndividuals.LastName = TblIndividualsUpdates.LastName
AND (
TblIndividuals.DateBorn = TblIndividualsUpdates.DateBorn
or
(
TblIndividuals.DateBorn is null
and
(
TblIndividuals.MidName is null
and TblIndividualsUpdates.MidName is null
or TblIndividuals.MidName = TblIndividualsUpdates.MidName
)
)
)
);
What I would do is systematically remove all the join conditions except the first two until you find the records drop off. Then you will know where your problem is.
This should never happen. Unless rows are being inserted/deleted in the meantime,
the query:
SELECT *
FROM a LEFT JOIN b
ON whatever ;
should never return less rows than:
SELECT *
FROM a ;
If it happens, it's a bug. Are you sure the queries are exactly like this (and you have't omitted some detail, like a WHERE clause)? Are you sure that the first returns 4149 rows and the second one 4103 rows? You could make another check by changing the * above to COUNT(*).
Drop any indexes from both tables which include those JOIN fields (FirstName, LastName, and DateBorn). Then see whether you get the expected
4,149 rows with this simplified query.
SELECT
i.PersonID AS OldID,
u.PersonID AS UpdateID
FROM
TblIndividualsUpdates AS u
LEFT JOIN TblIndividuals AS i
ON
(
(i.FirstName = u.FirstName)
AND (i.LastName = u.LastName)
AND (i.DateBorn = u.DateBorn)
);
For whatever it is worth, since this seems to be a deceitful bug and any additional information could help resolving it, I have had the same problem.
The query is too big to post here and I don't have the time to reduce it now to something suitable, but I can report what I found. In the below, all joins are left joins.
I was gradually refining and changing my query. It had a derived table in it (D). And the whole thing was made into a derived table (T) and then joined to a last table (L). In any case, at one point in its development, no field in T that originated in D participated in the join to L. It was then the problem occurred, the total number of rows mysteriously became less than the main table, which should be impossible. As soon as I again let a field from D participate (via T) in the join to L, the number increased to normal again.
It was as if the join condition to D was moved to a WHERE clause when no field in it was participating (via T) in the join to L. But I don't really know what the explanation is.

SQL Query Help (Postgresql)

I'm having trouble wrapping my head around a query. I have the following 3 tables:
documents (
id,
title
);
positions (
id,
title
);
documents_positions (
document_id,
position_id
);
What I am trying to get (my requested outcome) is a matrix of documents, and what positions they apply to. So each row would have document title, and then have a column for every position and a column next to it with True or False if the positions applies to the document. I suspect some kind of LEFT JOIN is required, because on each row after document, I want to list every position from the positions table and whether or not the document applies to it. Make sense?
You could use a cross join to build the matrix, and then left join to find the positions in the matrix that are filled:
select d.title
, p.title
, case when dp.document_id is null then 'hrmm' else 'HALLELUJAH' end
from documents d
cross join
positions p
left join
documents_positions dp
on dp.document_id = d.id
and dp.position_id = p.id
Since you want to turn positions rows into columns you have to "pivot" them. In PostgreSQL this is done with crosstab function. However, crosstab seems to require that you define the number of output columns in the query which you can't do as the number of rows in the positions is subject to change?
I'm not a PostgreSQL user myself so perhaps there is some trick to build the dynamic query I don't know of but it seems to be easier to use query like Andomar posted and then pivot the rows in your client code...