Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Optimize sub-selects and views that use UNION ALL. This optimization isn't very well tested yet. (CVS 5331) |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
3ef468e7046b2091b5b6880fe19261ef |
User & Date: | danielk1977 2008-07-01 14:09:14.000 |
References
2013-01-28
| ||
19:34 | • New ticket [db4d96798d] LIMIT does not work with nested views containing UNION ALL. (artifact: 8c39a3c9ee user: drh) | |
Context
2008-07-01
| ||
14:39 | Fix a problem with LIMIT and OFFSET clauses on the parent query when optimizing a UNION ALL sub-select. (CVS 5332) (check-in: a79786a961 user: danielk1977 tags: trunk) | |
14:09 | Optimize sub-selects and views that use UNION ALL. This optimization isn't very well tested yet. (CVS 5331) (check-in: 3ef468e704 user: danielk1977 tags: trunk) | |
2008-06-30
| ||
18:12 | Call the query flattener while processing the parent query. Previously, it was called while processing the sub-queries. (CVS 5330) (check-in: 6fcb3bffe2 user: danielk1977 tags: trunk) | |
Changes
Changes to src/select.c.
︙ | ︙ | |||
8 9 10 11 12 13 14 | ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains C code routines that are called by the parser ** to handle SELECT statements in SQLite. ** | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains C code routines that are called by the parser ** to handle SELECT statements in SQLite. ** ** $Id: select.c,v 1.441 2008/07/01 14:09:14 danielk1977 Exp $ */ #include "sqliteInt.h" /* ** Delete all the content of a Select structure but do not deallocate ** the select structure itself. |
︙ | ︙ | |||
1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 | */ Table *sqlite3ResultSetOfSelect(Parse *pParse, char *zTabName, Select *pSelect){ Table *pTab; int i, j; ExprList *pEList; Column *aCol, *pCol; sqlite3 *db = pParse->db; while( pSelect->pPrior ) pSelect = pSelect->pPrior; if( prepSelectStmt(pParse, pSelect) ){ return 0; } if( sqlite3SelectResolve(pParse, pSelect, 0) ){ return 0; | > > > > | 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 | */ Table *sqlite3ResultSetOfSelect(Parse *pParse, char *zTabName, Select *pSelect){ Table *pTab; int i, j; ExprList *pEList; Column *aCol, *pCol; sqlite3 *db = pParse->db; if( sqlite3SelectResolve(pParse, pSelect, 0) ){ return 0; } while( pSelect->pPrior ) pSelect = pSelect->pPrior; if( prepSelectStmt(pParse, pSelect) ){ return 0; } if( sqlite3SelectResolve(pParse, pSelect, 0) ){ return 0; |
︙ | ︙ | |||
3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 | ** (15) The outer query is not part of a compound select or the ** subquery does not have both an ORDER BY and a LIMIT clause. ** (See ticket #2339) ** ** (16) The outer query is not an aggregate or the subquery does ** not contain ORDER BY. (Ticket #2942) This used to not matter ** until we introduced the group_concat() function. ** ** In this routine, the "p" parameter is a pointer to the outer query. ** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query ** uses aggregates and subqueryIsAgg is true if the subquery uses aggregates. ** ** If flattening is not attempted, this routine is a no-op and returns 0. ** If flattening is attempted this routine returns 1. ** ** All of the expression analysis must occur on both the outer query and ** the subquery before this routine runs. */ static int flattenSubquery( sqlite3 *db, /* Database connection */ Select *p, /* The parent or outer SELECT statement */ int iFrom, /* Index in p->pSrc->a[] of the inner subquery */ int isAgg, /* True if outer SELECT uses aggregate functions */ int subqueryIsAgg /* True if the subquery uses aggregate functions */ ){ Select *pSub; /* The inner query or "subquery" */ SrcList *pSrc; /* The FROM clause of the outer query */ SrcList *pSubSrc; /* The FROM clause of the subquery */ ExprList *pList; /* The result set of the outer query */ int iParent; /* VDBE cursor number of the pSub result set temp table */ int i; /* Loop counter */ Expr *pWhere; /* The WHERE clause */ struct SrcList_item *pSubitem; /* The subquery */ | > > > > > > > > > > > > > > | 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 | ** (15) The outer query is not part of a compound select or the ** subquery does not have both an ORDER BY and a LIMIT clause. ** (See ticket #2339) ** ** (16) The outer query is not an aggregate or the subquery does ** not contain ORDER BY. (Ticket #2942) This used to not matter ** until we introduced the group_concat() function. ** ** (17) The sub-query is not a compound select, or it is a UNION ALL ** compound without an ORDER BY, LIMIT or OFFSET clause made up ** entirely of non-aggregate queries, and ** the parent query: ** ** * is not itself part of a compound select, ** * is not an aggregate or DISTINCT query, and ** * has no other tables or sub-selects in the FROM clause. ** ** The parent query may have WHERE, ORDER BY, LIMIT and OFFSET ** clauses. ** ** In this routine, the "p" parameter is a pointer to the outer query. ** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query ** uses aggregates and subqueryIsAgg is true if the subquery uses aggregates. ** ** If flattening is not attempted, this routine is a no-op and returns 0. ** If flattening is attempted this routine returns 1. ** ** All of the expression analysis must occur on both the outer query and ** the subquery before this routine runs. */ static int flattenSubquery( sqlite3 *db, /* Database connection */ Select *p, /* The parent or outer SELECT statement */ int iFrom, /* Index in p->pSrc->a[] of the inner subquery */ int isAgg, /* True if outer SELECT uses aggregate functions */ int subqueryIsAgg /* True if the subquery uses aggregate functions */ ){ Select *pParent; Select *pSub; /* The inner query or "subquery" */ Select *pSub1; /* Pointer to the rightmost select in sub-query */ SrcList *pSrc; /* The FROM clause of the outer query */ SrcList *pSubSrc; /* The FROM clause of the subquery */ ExprList *pList; /* The result set of the outer query */ int iParent; /* VDBE cursor number of the pSub result set temp table */ int i; /* Loop counter */ Expr *pWhere; /* The WHERE clause */ struct SrcList_item *pSubitem; /* The subquery */ |
︙ | ︙ | |||
3137 3138 3139 3140 3141 3142 3143 3144 | ** ** But the t2.x>0 test will always fail on a NULL row of t2, which ** effectively converts the OUTER JOIN into an INNER JOIN. */ if( (pSubitem->jointype & JT_OUTER)!=0 && pSub->pWhere!=0 ){ return 0; } | | > > | > > > | > > > > > > > | > | > > > > > | < < < > | > > > > > > > > > > > > | > > > | > > > > > > | > > > > > > > > > > > > > > | | | | | | | | > | | | | < | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | > | 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 | ** ** But the t2.x>0 test will always fail on a NULL row of t2, which ** effectively converts the OUTER JOIN into an INNER JOIN. */ if( (pSubitem->jointype & JT_OUTER)!=0 && pSub->pWhere!=0 ){ return 0; } /* Restriction 17: If the sub-query is a compound SELECT, then it must ** use only the UNION ALL operator. And none of the simple select queries ** that make up the compound SELECT are allowed to be aggregate or distinct ** queries. */ if( pSub->pPrior ){ if( p->pPrior || isAgg || p->isDistinct || pSrc->nSrc!=1 ){ return 0; } for(pSub1=pSub; pSub1; pSub1=pSub1->pPrior){ if( pSub1->isAgg || pSub1->isDistinct || (pSub1->pPrior && pSub1->op!=TK_ALL) ){ return 0; } } } /* If the sub-query is a compound SELECT statement, then it must be ** a UNION ALL and the parent query must be of the form: ** ** SELECT <expr-list> FROM (<sub-query>) <where-clause> ** ** followed by any ORDER BY, LIMIT and/or OFFSET clauses. This block ** creates N copies of the parent query without any ORDER BY, LIMIT or ** OFFSET clauses and joins them to the left-hand-side of the original ** using UNION ALL operators. In this case N is the number of simple ** select statements in the compound sub-query. */ for(pSub=pSub->pPrior; pSub; pSub=pSub->pPrior){ Select *pNew; ExprList *pOrderBy = p->pOrderBy; Select *pPrior = p->pPrior; p->pOrderBy = 0; p->pSrc = 0; p->pPrior = 0; pNew = sqlite3SelectDup(db, p); pNew->pPrior = pPrior; p->pPrior = pNew; p->pOrderBy = pOrderBy; p->op = TK_ALL; p->pSrc = pSrc; p->pRightmost = 0; pNew->pRightmost = 0; } /* If we reach this point, it means flattening is permitted for the ** iFrom-th entry of the FROM clause in the outer query. */ pSub = pSub1 = pSubitem->pSelect; iParent = pSubitem->iCursor; for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){ int nSubSrc = pSubSrc->nSrc; int jointype = 0; pSubSrc = pSub->pSrc; pSrc = pParent->pSrc; /* Move all of the FROM elements of the subquery into the ** the FROM clause of the outer query. Before doing this, remember ** the cursor number for the original outer query FROM element in ** iParent. The iParent cursor will never be used. Subsequent code ** will scan expressions looking for iParent references and replace ** those references with expressions that resolve to the subquery FROM ** elements we are now copying in. */ if( pSrc ){ pSubitem = &pSrc->a[iFrom]; nSubSrc = pSubSrc->nSrc; jointype = pSubitem->jointype; sqlite3DeleteTable(pSubitem->pTab); sqlite3_free(pSubitem->zDatabase); sqlite3_free(pSubitem->zName); sqlite3_free(pSubitem->zAlias); pSubitem->pTab = 0; pSubitem->zDatabase = 0; pSubitem->zName = 0; pSubitem->zAlias = 0; } if( nSubSrc!=1 || !pSrc ){ int extra = nSubSrc - 1; for(i=(pSrc?1:0); i<nSubSrc; i++){ pSrc = sqlite3SrcListAppend(db, pSrc, 0, 0); if( pSrc==0 ){ pParent->pSrc = 0; return 1; } } pParent->pSrc = pSrc; for(i=pSrc->nSrc-1; i-extra>=iFrom; i--){ pSrc->a[i] = pSrc->a[i-extra]; } } for(i=0; i<nSubSrc; i++){ pSrc->a[i+iFrom] = pSubSrc->a[i]; memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); } pSrc->a[iFrom].jointype = jointype; /* Now begin substituting subquery result set expressions for ** references to the iParent in the outer query. ** ** Example: ** ** SELECT a+5, b*10 FROM (SELECT x*3 AS a, y+10 AS b FROM t1) WHERE a>b; ** \ \_____________ subquery __________/ / ** \_____________________ outer query ______________________________/ ** ** We look at every expression in the outer query and every place we see ** "a" we substitute "x*3" and every place we see "b" we substitute "y+10". */ pList = pParent->pEList; for(i=0; i<pList->nExpr; i++){ Expr *pExpr; if( pList->a[i].zName==0 && (pExpr = pList->a[i].pExpr)->span.z!=0 ){ pList->a[i].zName = sqlite3DbStrNDup(db, (char*)pExpr->span.z, pExpr->span.n); } } substExprList(db, pParent->pEList, iParent, pSub->pEList); if( isAgg ){ substExprList(db, pParent->pGroupBy, iParent, pSub->pEList); substExpr(db, pParent->pHaving, iParent, pSub->pEList); } if( pSub->pOrderBy ){ assert( pParent->pOrderBy==0 ); pParent->pOrderBy = pSub->pOrderBy; pSub->pOrderBy = 0; }else if( pParent->pOrderBy ){ substExprList(db, pParent->pOrderBy, iParent, pSub->pEList); } if( pSub->pWhere ){ pWhere = sqlite3ExprDup(db, pSub->pWhere); }else{ pWhere = 0; } if( subqueryIsAgg ){ assert( pParent->pHaving==0 ); pParent->pHaving = pParent->pWhere; pParent->pWhere = pWhere; substExpr(db, pParent->pHaving, iParent, pSub->pEList); pParent->pHaving = sqlite3ExprAnd(db, pParent->pHaving, sqlite3ExprDup(db, pSub->pHaving)); assert( pParent->pGroupBy==0 ); pParent->pGroupBy = sqlite3ExprListDup(db, pSub->pGroupBy); }else{ substExpr(db, pParent->pWhere, iParent, pSub->pEList); pParent->pWhere = sqlite3ExprAnd(db, pParent->pWhere, pWhere); } /* The flattened query is distinct if either the inner or the ** outer query is distinct. */ pParent->isDistinct = pParent->isDistinct || pSub->isDistinct; /* ** SELECT ... FROM (SELECT ... LIMIT a OFFSET b) LIMIT x OFFSET y; ** ** One is tempted to try to add a and b to combine the limits. But this ** does not work if either limit is negative. */ if( pSub->pLimit ){ pParent->pLimit = pSub->pLimit; pSub->pLimit = 0; } } /* Finially, delete what is left of the subquery and return ** success. */ sqlite3SelectDelete(pSub1); return 1; } #endif /* SQLITE_OMIT_VIEW */ /* ** Analyze the SELECT statement passed as an argument to see if it ** is a min() or max() query. Return WHERE_ORDERBY_MIN or WHERE_ORDERBY_MAX if |
︙ | ︙ | |||
3667 3668 3669 3670 3671 3672 3673 | } if( sqlite3SelectResolve(pParse, p, 0) ){ goto select_end; } p->pOrderBy = pOrderBy; | < < < < < < < < < < < < < < < < < < < < < < < | 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 | } if( sqlite3SelectResolve(pParse, p, 0) ){ goto select_end; } p->pOrderBy = pOrderBy; /* Make local copies of the parameters for this query. */ pTabList = p->pSrc; isAgg = p->isAgg; pEList = p->pEList; if( pEList==0 ) goto select_end; |
︙ | ︙ | |||
3726 3727 3728 3729 3730 3731 3732 | */ v = sqlite3GetVdbe(pParse); if( v==0 ) goto select_end; /* Generate code for all sub-queries in the FROM clause */ #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) | | > | 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 | */ v = sqlite3GetVdbe(pParse); if( v==0 ) goto select_end; /* Generate code for all sub-queries in the FROM clause */ #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) for(i=0; !p->pPrior && i<pTabList->nSrc; i++){ struct SrcList_item *pItem = &pTabList->a[i]; SelectDest dest; Select *pSub = pItem->pSelect; int isAggSub; if( pSub==0 || pItem->isPopulated ) continue; if( pItem->zName!=0 ){ /* An sql view */ const char *zSavedAuthContext = pParse->zAuthContext; pParse->zAuthContext = pItem->zName; rc = sqlite3SelectResolve(pParse, pSub, 0); pParse->zAuthContext = zSavedAuthContext; |
︙ | ︙ | |||
3752 3753 3754 3755 3756 3757 3758 | ** (SQLITE_MAX_EXPR_DEPTH-Parse.nHeight) height. This is a bit ** more conservative than necessary, but much easier than enforcing ** an exact limit. */ pParse->nHeight += sqlite3SelectExprHeight(p); /* Check to see if the subquery can be absorbed into the parent. */ | > | | | 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 | ** (SQLITE_MAX_EXPR_DEPTH-Parse.nHeight) height. This is a bit ** more conservative than necessary, but much easier than enforcing ** an exact limit. */ pParse->nHeight += sqlite3SelectExprHeight(p); /* Check to see if the subquery can be absorbed into the parent. */ isAggSub = pSub->isAgg; if( flattenSubquery(db, p, i, isAgg, isAggSub) ){ if( isAggSub ){ p->isAgg = isAgg = 1; } i = -1; }else{ sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); sqlite3Select(pParse, pSub, &dest, p, i, &isAgg, 0); } |
︙ | ︙ | |||
3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 | } pEList = p->pEList; #endif pWhere = p->pWhere; pGroupBy = p->pGroupBy; pHaving = p->pHaving; isDistinct = p->isDistinct; /* If possible, rewrite the query to use GROUP BY instead of DISTINCT. ** GROUP BY may use an index, DISTINCT never does. */ if( p->isDistinct && !p->isAgg && !p->pGroupBy ){ p->pGroupBy = sqlite3ExprListDup(db, p->pEList); pGroupBy = p->pGroupBy; | > > > > > > > > > > > > > > > > > > > > > > > | 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 | } pEList = p->pEList; #endif pWhere = p->pWhere; pGroupBy = p->pGroupBy; pHaving = p->pHaving; isDistinct = p->isDistinct; #ifndef SQLITE_OMIT_COMPOUND_SELECT /* If there is are a sequence of queries, do the earlier ones first. */ if( p->pPrior ){ if( p->pRightmost==0 ){ Select *pLoop, *pRight = 0; int cnt = 0; int mxSelect; for(pLoop=p; pLoop; pLoop=pLoop->pPrior, cnt++){ pLoop->pRightmost = p; pLoop->pNext = pRight; pRight = pLoop; } mxSelect = db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT]; if( mxSelect && cnt>mxSelect ){ sqlite3ErrorMsg(pParse, "too many terms in compound SELECT"); return 1; } } return multiSelect(pParse, p, pDest, aff); } #endif /* If possible, rewrite the query to use GROUP BY instead of DISTINCT. ** GROUP BY may use an index, DISTINCT never does. */ if( p->isDistinct && !p->isAgg && !p->pGroupBy ){ p->pGroupBy = sqlite3ExprListDup(db, p->pEList); pGroupBy = p->pGroupBy; |
︙ | ︙ |
Added test/selectB.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | # 2008 June 24 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # This file implements regression tests for SQLite library. # # $Id: selectB.test,v 1.1 2008/07/01 14:09:14 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl proc test_transform {testname sql1 sql2 results} { set ::vdbe1 [list] set ::vdbe2 [list] db eval "explain $sql1" { lappend ::vdbe1 $opcode } db eval "explain $sql2" { lappend ::vdbe2 $opcode } do_test $testname.transform { set ::vdbe1 } $::vdbe2 set ::sql1 $sql1 do_test $testname.sql1 { execsql $::sql1 } $results set ::sql2 $sql2 do_test $testname.sql2 { execsql $::sql2 } $results } do_test selectB-1.1 { execsql { CREATE TABLE t1(a, b, c); CREATE TABLE t2(d, e, f); INSERT INTO t1 VALUES( 2, 4, 6); INSERT INTO t1 VALUES( 8, 10, 12); INSERT INTO t1 VALUES(14, 16, 18); INSERT INTO t2 VALUES(3, 6, 9); INSERT INTO t2 VALUES(12, 15, 18); INSERT INTO t2 VALUES(21, 24, 27); } } {} test_transform selectB-1.2 { SELECT * FROM (SELECT a FROM t1 UNION ALL SELECT d FROM t2) } { SELECT a FROM t1 UNION ALL SELECT d FROM t2 } {2 8 14 3 12 21} test_transform selectB-1.3 { SELECT * FROM (SELECT a FROM t1 UNION ALL SELECT d FROM t2) ORDER BY 1 } { SELECT a FROM t1 UNION ALL SELECT d FROM t2 ORDER BY 1 } {2 3 8 12 14 21} test_transform selectB-1.4 { SELECT * FROM (SELECT a FROM t1 UNION ALL SELECT d FROM t2) WHERE a>10 ORDER BY 1 } { SELECT a FROM t1 WHERE a>10 UNION ALL SELECT d FROM t2 WHERE d>10 ORDER BY 1 } {12 14 21} test_transform selectB-1.5 { SELECT * FROM (SELECT a FROM t1 UNION ALL SELECT d FROM t2) WHERE a>10 ORDER BY a } { SELECT a FROM t1 WHERE a>10 UNION ALL SELECT d FROM t2 WHERE d>10 ORDER BY a } {12 14 21} test_transform selectB-1.6 { SELECT * FROM (SELECT a FROM t1 UNION ALL SELECT d FROM t2 WHERE d > 12) WHERE a>10 ORDER BY a } { SELECT a FROM t1 WHERE a>10 UNION ALL SELECT d FROM t2 WHERE d>12 AND d>10 ORDER BY a } {14 21} finish_test |