I am new to Propel and have been reading the documentation. But, I have not found a clear equivalent to the EXISTS and NOT EXISTS constructs from SQL. Linq in .NET, for instance, has Any(). Is there an equivalent to the following in "idiomatic" Propel?
SELECT a.column1, a column2, a.etc
FROM TableA a
WHERE NOT EXISTS (SELECT 1
FROM TableB b
WHERE b.someIdColumn = a.someIdColumn
AND b.aNullableDateColumn IS NULL)
After doing some more digging, I believe I have an answer to my question, or at least as good an answer as is currently available.
What comes after EXISTS or NOT EXISTS is a subquery. While that fact seems obvious, it did not originally occur to me to focus my search for help on subqueries. I found a few resources on the topic. Essentially, the options are to rewrite the query using JOINs (as is the heart of the answer by #Kaltas) or to use Criteria::CUSTOM. I decided I would likely prefer the second option, since it allows me to keep the subquery, potentially helping my database performance.
I did a lot of reading, then, about Criteria::CUSTOM, but the only reading that really helped me was reading the Propel 1.5 source. It's very simple, really. Just put the subquery, verbatim (using the database's table and column names, not Propel's object names) along with EXISTS or NOT EXISTS in the where call, like:
TableAQuery::create()
->where('NOT EXISTS (SELECT 1 FROM TableB WHERE TableA.someIdColumn = TableB.someIdColumn AND TableB.aNullableDateColumn IS NULL)')
->find();
It's that simple. Internally, the where method goes through a few possibilities for interpreting the clause, and finding no matches, it treats the clause as being of Criteria::CUSTOM and inserts it into the SQL query as-is. So, I could not use table aliases, for example.
If I ever have time, maybe I'll work on a more "ORM-ish" way to do this and submit a patch. Someone will probably beat me to it, though.
As in propel 1.6 u now can use Criteria::IN and Criteria::NOT_IN
Example : Select all users that are not in an UserGroup
$users = UserQuery::create()->filterById(UserPerUserGroupQuery::create()->select('user_id')->find(), CRITERIA::NOT_IN)
->orderByUserName()
->find();
I think you could rewrite the query as:
SELECT
a.column1,
a.column2,
a.etc
FROM
TableA a
WHERE
(SELECT
COUNT(*)
FROM
TableB b
WHERE
b.someIdColumn = a.someIdColumn
AND
b.aNullableDateColumn IS NULL
) > 0
which is easily doable in Propel.
Or even cleaner and easier to accomplish in Propel:
SELECT
a.column1,
a.column2,
a.etc
FROM
TableA a
LEFT JOIN
TableB b ON (b.someIdColumn = a.someIdColumn)
WHERE
b.aNullableDateColumn IS NULL
AND
b.primaryKeyColumn IS NOT NULL
Propel 2 can do:
TableAQuery::create()
->useTableBNotExistsQuery()
->filterByNullableDateColumn(null)
->endUse()
->find();
or
$nestedB = TableBQuery::create()
->filterByNullableDateColumn(null)
->where('TableB.someIdColumn = TableA.someIdColumn');
TableAQuery::create()->whereExists(nestedB)->find();
Related
Last time you guys were super helpful, let's hope we can also resolve the following challenge that I'm having.
I have 2 tables in the same database and trying to match fields. Here is the query I'm looking for with the table name however I cannot get it to work:
I want to MATCH table "projects" and Column "categorysecond" table "users_profile" and Column "category".
projects.categorysecond might include example; "Roofing,Windows,Landscaping" and users_profile.category might only have EITHER "Roofing" OR "Windows" OR "Landscaping", so I want to pull the results from "projects" if a result from users_profile.category FITS in projects.categorysecond
***Important - I do not have unique identifiers to do a inner join on example ID. The only match should be on the tables listed above, if words(%Wildcard) from one fits the other.
SAMPLE OF MY ATTEMPT
$sql = "SELECT DISTINCT users_profiles.username, projects.id FROM users_profiles, projects WHERE users_profiles.category like '%' + projects.categorysecond + '%'";
Does this make sense? :)
Thank you in advance.
select distinct
up.username
,p.id
from users_profiles as up
join projects as p
on concat(',',p.categorysecond,',') like concat('%,',up.category,',%')
You have a really bad data format. You should not be storing lists of things in a comma-delimited field. The trouble you are having with this query is just one example of the issues. You cannot get such queries to take advantage of indexes or other optimization techniques.
Sometimes, we are stuck with other people's really bad design decisions. MySQL has the function find_in_set() with works in this case:
SELECT DISTINCT up.username, p.id
FROM users_profiles up JOIN
projects p
ON find_in_set(up.category, p.categorysecond) > 0;
Note: If you don't need the DISTINCT, then don't include it. It just incurs a performance penalty.
I'm currently working on a project that requires me look at someone elses code, i'm still new to some of this stuff and there is something that I dont quite understand so was hoping someone could shed some light on what the code is actually doing or what it means?
Here is the statement in question:
select s.* from $tableA n, $tableB s where
n.id='$send' and
n.status='$status' and
n.field=s.id";
I understand that down to basics this statement is getting all of the fields from tableA and tableB im just unsure what the s. does or what the n does in this statement? are they simply there as identifiers or am I completely wrong in this manner? I am happy to provide more information if it is necessary.
They are called SQL Table Aliases and are basically temporary names which you give to the tables in order to have better readability when you use the table names to specify a column.
In your example
SELECT s.* FROM $tableA n, $tableB s
WHERE n.id='$send'
AND n.status='$status'
AND n.field=s.id ;
is the same as
SELECT $tableB.* FROM $tableA, $tableB
WHERE $tableA.id='$send'
AND $tableA.status = '$status'
AND $tableA.field = $tableB.id ;
but obviously it's easier to read.
The table aliases are even more useful when you join more tables and are absolutely a must when you make self joins.
Syntax note:
You may or you may not use the AS keyword when alias a table.
SELECT table_name AS alias
is the same as
SELECT table_name alias
and although it's longer sometimes it leads to a better readability (for example in a large and messy query the big AS is easier to spot :)
I am in need of a sql construct which gives the following functionality.
select if-expression if status='regular' else else-expression
from table-name ;
This operation will be used very frequently. So, I am considering building an index for this operation.
But, I heard that indexes are not rebuilt after table is being updated. Is there a way we can have automatically rebuild indexes?
Thanks in advance
The translation in SQL of your statement is:
select (case when status = 'regular' then <if-expression> else <else-expression> end)
from tablename;
An index will not help with this query, because you are not limiting the rows in any way. An index can help when you have filters in a where clause, joins, and correlated subqueries (and sometimes I think with group by).
And as MarcB points out in a comment, MySQL (and all other databases) keep indexes up to date for insert, update, and delete operations.
You did not provide a more detailed example.
There are several alternative solutions to your idea, not just the "use-the-index-luke-solution". And depends a lot on your database.
Another alternative could be using "union". In some circumstances could use too much resources, in others, may be the optimal solution, even if using too many records.
SELECT
<if-expression>
FROM
MyTable
WHERE
(MyTable.status = 'regular')
UNION
SELECT
<else-expression>
FROM
MyTable
WHERE
(MyTable.status <> 'regular')
And, in some circumstances, you may also add
SELECT
<if-expression>
FROM
MyTable
WHERE
(MyTable.status = 'regular')
ORDER BY <indexed-fields-used-in-expression>
UNION
SELECT
<else-expression>
FROM
MyTable
WHERE
(MyTable.status <> 'regular')
ORDER BY <indexed-fields-used-in-expression>
Cheers.
While working with following query on mysql, Its taking to execute around 10sec.
SELECT SQL_CALC_FOUND_ROWS DISTINCT
b.appearance_id,
b.photo_album_id,
b.eventcmmnt_id,
b.id,
b.mem_id,
b.subj,
b.body,
b.image_link as photo_image_uploaded,
b.bottle_id,
b.date,
b.parentid,
b.from_id,
b.visible_to,
pa.photo_big as image_link,
b.post_via,
b.youtubeLink,
b.link_image,
b.link_url,
b.auto_genrate_text,
badges.badge_img,
badges.badge_bottle_img,
b.type,b.share_url_title
FROM bulletin b
INNER JOIN network n
ON (n.mem_id = b.mem_id)
LEFT JOIN badges
ON (b.bottle_id=badges.badge_id)
LEFT JOIN photo_album as pa
ON (pa.photo_id=b.photo_album_id)
JOIN members mem
ON (b.mem_id=mem.mem_id and mem.deleted<>'Y')
WHERE b.parentid = '0'
AND ('$userid' IN (n.frd_id, b.mem_id,b.from_id))
GROUP BY b.id
ORDER BY b.id DESC
LIMIT 0,10
The Inner query inside IN constraint has many frd_id,mem_id,from_id So I think because of that above query is executing slowly... So please help to optimize above query
Thanks
Check, that you have indexes on fields, which are used for joins.
As you have already been hinted, post the EXPLAIN SELECT... plan for the query. Why guess when you can check? (And a SQL Fiddle would be great too).
That will tell whether you need indexes (very, very likely!) and which indexes and how structured (do not go and index everything, since this may even harm performances!).
One suggestion I can already give: if you have an index field on members of the form
(mem_id, deleted, ...)
as you should, verify that deleted is indeed a boolean (if it is not, convert it to an enum of 'Y' and 'N'), and specify the condition like this:
on(b.mem_id=mem.mem_id AND NOT mem.deleted)
since this will go much easier on the indexing. If the field is not boolean, for example it may have the values 'Y', 'N' and 'P' for 'Pending', and you used a varchar field, then the <> has a considerable performance impact on the index - I'd even go out on a limb and say that the index is effectively neutralized. Indexes work best with matches, not with non-matches as <>.
I suspect you can also re-engineer this expression
AND ('$userid' IN (n.frd_id, b.mem_id,b.from_id))
by moving it, albeit partially, in the b JOIN. Or you could consider, depending on your tables' cardinality, using a UNION instead.
I am attempting to get the latest result from two different tables (forum_posts and forum_replies)...I'm not sure of the best way of doing this, so I am attempting to use a UNION ALL to do this...
I did a test result to try to make sure the code worked; however, it doesn't appear to be working correctly. Even though there is data matching the requirements in the database, it is echoing out No Posts. So something about the query isn't processing correctly.
$latest = "(SELECT * FROM forum_posts WHERE post_subcat = '1' ORDER BY post_id) UNION ALL (SELECT * FROM forum_replies WHERE reply_subcat = '1' ORDER BY reply_id) LIMIT 1";
if(!$getlatest = $con->query($latest)){ echo 'No Posts'; }
if($getlatest = $con->query($latest)){ echo 'Post'; }
I'm new to unions, so I have a few questions.
1) I've seen a union work in mysql, but do they also work in mysqli?
2) Are there any restrictions to using unions (union/union all/ etc.)? Do columns have to be the same for comparison?
3) Did I do something wrong in my above code? I am probably overlooking something minor just from working too long, just not sure at this point.
You generally have to (overly flexible DBMS' notwithstanding) have the same column types and identifiers in the two queries you're unioning together and the order by applies to the final result set, not the interim one (although you can use sub-queries to order interim results if needed, not something that seems to be required in this specific case).
So something like this:
SELECT post_id as id,
post_date as dt
FROM forum_posts
WHERE post_subcat = '1'
UNION ALL
SELECT reply_id as id,
reply_date as dt
FROM forum_replies
WHERE reply_subcat = '1'
ORDER BY dt DESC
LIMIT 1
In terms of the mysql/mysqli distinction, that shouldn't matter, both methods end up doing the relevant work at the server side.
You usually use union all if you know there's no chance of duplicates so as to avoid any unnecessary sorting for the duplicate removal. Otherwise, use union if you do want duplicates removed.
As to whether you have a problem with your code, if the query function returns false, you should be checking the error functions for the specific failure reason. See mysqli_error for details.
And keep in mind it doesn't return false if there were no posts, it only returns false if there was an error. If the query worked and there were no posts, you'd end up with an empty result set.
1) I've seen a union work in mysql, but do they also work in mysqli?
It should work on mysqli also because mysqli is another extension to access Mysql 4.1 onwards
2) Are there any restrictions to using unions (union/union all/ etc.)? Do columns have to be the same for comparison?
Yes. All the columns from two tables needs to be of same data type and the no of columns also should match. I suspect this is your problem.
3) Did I do something wrong in my above code? I am probably overlooking something minor just from working too long, just not sure at this point.
If I've understood what you're after correctly, I believe the following will work:
SELECT * FROM forum_posts fp WHERE fp.post_subcat = (SELECT MAX(fp1.post_subcat) FROM forum_posts fp1)
UNION
SELECT * FROM forum_replies fr WHERE fr.reply_subcat = (SELECT MAX(fp.post_subcat) FROM forum_posts fp)
Did I do something wrong in my above code?
Sure.
First, you are running your query two times.
Second, you are checking wrong value to see if there were any posts. http://php.net/mysqli_query