I've been using VBA to examine all the queries, forms, and modules in my Access 2000 database, but it can be quite tedious and slow. Recently, I decided to take a closer look at the system tables in Access, in particular, MSysQueries and MSysObjects. Can I use these tables to examine my objects in the database faster? Of course, these tables are read-only, so I can't make any modifications to the database through them without returning to VBA. What do the attributes in MSysQueries mean?
Well, I came across this post on Google groups. I did further investigation on my own tables and wanted to share a table of information that I created inspired by work already done.
Each query can take up multiple rows in the table.
The row with attribute 0 is the beginning of the query.
The row with attribute 1 indicates the type of the query.
Flag value 1 = SELECT query.
Flag value 2 = SELECT ... INTO query, or a make table query. Name1 will have the name of the table that is created.
Flag value 3 = INSERT query; Name1 will have the name of the table to insert to.
Flag value 4 = UPDATE query
Flag value 5 = DELETE query
Flag value 6 = Crosstab query (TRANSFORM)
Flag value 9 = UNION query
The rows with attribute 2 (there could be multiple) are each formal parameter of the query. The Flag column indicates the data type (i.e. "10" for dbText) and the Name1 column indicates the name of the parameter. If there are no rows with attribute 2, then the query does not have formal parameters.
The row with attribute 3 indicates the presence of UNION or DISTINCT keywords.
Flag value 0 = Nothing special
Flag value 1 = UNION ALL
Flag value 2 = SELECT DISTINCT
Flag value 3 = UNION
Flag value 8 = SELECT DISTINCTROW
Flag value 9 = Queries on master fields and child fields
The row with attribute 4 indicates if the query comes from an external database. Name1 will contain the source if attribute 4 exists.
The rows with attribute 5 (there could be multiple) indicate each table found in the query. If the query is a UNION query, the Expression field has a split on the UNION keyword and the Name2 field has a system-generated table alias. For all other tables in a query, Name1 is the name of the table and Name2 is the alias, if there is one.
The rows with attribute 6 (there could be multiple) indicate each single field or expression in the query. If there is no attribute 6 for the query, the behavior assumed is that all fields are included. The Expression field contains each field expression or name, and Name1 contains the field alias if there is one.
Flag value 0 = Value of the field or expression
Flag value 1 = The field is a column heading in a crosstab query.
Flag value 2 = The field is a row heading in a crosstab query.
The rows with attribute 7 (there could be multiple) indicate each single join "ON" expression. The Expression field contains the actual join expression. Name1 contains the first table in the join. Name2 contains the second table in the join.
Flag value 1 = Inner Join
Flag value 2 = Left Join
Flag value 3 = Right Join
The row with attribute 8 contains the whole WHERE clause in the Expression field. If there is no where clause, attribute 8 is omitted from the query.
The rows with attribute 9 (there could be multiple) indicate each single Group By expression in the GROUP BY clause of the query. The Expression field contains each group by expression.
Flag value 0 = Value of the field or expression
Flag value 1 = The field is a column heading in a crosstab query.
Flag value 2 = The field is a row heading in a crosstab query.
The rows with attribute 11 (there could be multiple) indicate each single Order By expression in the ORDER BY clause of the query. The Expression field contains each order by expression. Name1 has "D" or "d" to indicate that the sort is done in descending order.
The row with attribute 255 is the end of the query.
I'm not exactly sure what the Order field does, but I did find that it is not Null, and though it sometimes has a value of an empty string, it doesn't always have that value. Empty strings occur on attributes 5, 6, 7, and 9, but it is not always an empty string for those attributes.
Thanks to #Bobort great explanations, I was able to create a query that lists all queries in current database, with their input tables/queries, query type, and target table (for action queries).
I thought I could share that here.
SELECT MSysObjects.Name AS queryName,
Mid("SelectMakTblAppendUpdateDeleteXtab 777777PassThUnion ",([msysqueries]![Flag]-1)*6+1,6) AS queryType,
src.Name1 AS [Input],
MSysQueries.Name1 AS Target
FROM (MSysQueries INNER JOIN MSysObjects ON MSysQueries.ObjectId = MSysObjects.Id)
LEFT JOIN (select * from MSysQueries WHERE Attribute = 5 ) AS src
ON MSysQueries.ObjectId = src.ObjectId
WHERE (((MSysObjects.Name)>"~z") AND ((MSysQueries.Attribute) =1))
ORDER BY MSysObjects.Name, src.Name1;
To use, just create a query in SQL view and paste the above code.
Further to Bobort and iDevlop's answers:
The row with attribute 1 indicates the type of the query.
Flag value 7 = DDL Query (eg CREATE TABLE...)
Flag value 9 = Pass through Query
The row with attribute 3 indicates the predicate.
Flag value 1 = All values, or UNION ALL (if a UNION query)
Flag value 4 = WITH OWNERACCESS OPTION
Flag value 16 = TOP N
Flag value 48 = TOP N PERCENT
The rows with attribute 5 (there could be multiple) indicate each FROM table/query found in the query
Expression contains the FROM source, or the SELECT statement if a UNION query
The row with attribute 10 contains the whole HAVING clause in the Expression field. If there is no HAVING clause, attribute 10 is omitted from the query.
The Order field is a BIG-ENDIAN binary value that contains an array of 4 bytes (binary fields can be added with VBA, but cannot be added using the UI, unless you copy and paste from a binary field in a system table.) However, in most databases, in the MSysQueries table, you're unlikely to encounter binary values greater than 255, so you can shortcut the conversion to a byte by inspecting the byte at index 3. For example:
Sub EnumOrder()
Dim rst As Recordset
Set rst = CurrentDb.OpenRecordset( _
" SELECT * FROM MSysQueries " & _
" WHERE Attribute = 6 " & _
"ORDER BY ObjectId Asc, [Order] Asc")
With rst
Do While Not .EOF
Debug.Print .Fields("ObjectId"), .Fields("Order")(3)
.MoveNext
Loop
.Close
End With
End Sub
Cumulative values occur for Attribute 3. So additional items include:
Flag 12 SELECT DISTINCT.... WITH OWNERACCESS OPTION
Flag 18 SELECT DISTINCT TOP (i.e. 2+16)
Flag 24 SELECT DISTINCTROW TOP (i.e. 8+16)
Flag 50 SELECT DISTINCT TOP PERCENT (i.e. 2+48)
Flag 56 SELECT DISTINCTROW TOP PERCENT (i.e. 8+48)
I have written an extended article about the workings of the MSysQueries table. See How Access stores queries.
Related
I have a Mysql database that contains some category ids on it which stores comma-separated values on a table.
sql table view
By using select * from style where categories like '%8,%'; it returns all the values end with 8. For example, if the table rows have two values like 8 and 148 it returns both rows. But I want to get only the rows that contain 8. How to do it
Storing multiple values in a single column is a denormalised design that will almost always cause you problems. However you need to add commas to both sides and compare:
select *
from Style
where concat(',',Categories,',') like '%,8,%';
Like everyone else: normalize your data. But if you can't mySQL supports find_in_set() for set datatypes which this appears to be.
DEMO dbfiddle.uk
DOC LINK: Find_in_set()
DOC LINK: SET data type
SQL
With CTE as (SELECT 'T-Shrits' as baseCategory, '8,21,75,87,148' categories UNION ALL
SELECT 'T-Shrits' as baseCategory, '8,21,75,87,148' categories UNION ALL
SELECT 'T-Shrits - Long Sleeve' as baseCategory, '8,21,75,87,148,92' categories UNION ALL
SELECT 'T-Shrits' as baseCategory, '21,75,87,100,148' categories)
SELECT * FROM CTE where find_in_set(8,categories) >0
OR we can use a boolean evaluation and eliminate the > 0
SELECT * FROM CTE where find_in_set(8,categories)
Giving us:
+------------------------+-------------------+
| baseCategory | categories |
+------------------------+-------------------+
| T-Shrits | 8,21,75,87,148 |
| T-Shrits | 8,21,75,87,148 |
| T-Shrits - Long Sleeve | 8,21,75,87,148,92 |
+------------------------+-------------------+
Notes
Find_in_set() returns the Returns a value in the range of 1 to N in the pseudo array of the value being searched. We need to ensure the result is greater than 0 (or treat it as a Boolean) in order for the searched value to "exist" within a record column.
The engine didn't return my 4th union value in CTE because it doesn't have an "alone" 8 value
If we searched for just 100 it would return that last record.
This function comes at a cost of performance on large datasets; which if data was normalized and indexed, you wouldn't have.
So why does this exit? For small enumerated lists or properties. It's still not ideal but if you have just a few using it "can" make sense. but in a very limited use case and often is missused.
This design violates 3rd normal form. Which is why most RDBMS designs cringe when it's brought up as it's not scalable.
as to why people are up in arms about multi value columns: Read this or This
You can also use rlike and in fact it is much better than like as it has much more options.
* = repetition of what is in front of it zero or more times
. = Equivalent to any character including none
^ = Anchor start (Forces that begins with ...)
$ = final anchor (forces it to end with ....)
[ ] = [ RST ] Contain an R or S or T but only one
[^] = DENY IT
And many more options
select * from style where concat(',',categories,',') rlike '*,8,*';
Let's say I have an arbitrary MySQL query called query1 that runs on a table table1 and generates a subset of the rows of that table. From a functional perspective, this query is just yielding a boolean for each row, where if the boolean is true, the row is returned in the results, and if it is false, it is not (or vice-versa, w.l.o.g.).
Let's also say that I'm taking a JSON blob, parsing it into a row, and adding the row to table1 every minute. I want to test and see if query1 returns true or false on the row immediately after I add it.
Is there a way to perform that test without actually adding the row somewhere in the database and then seeing if the row is returned by the query? My intuition is that it should be possible to 'emulate' the query on the new data without actually adding it anywhere, but I'm not familiar with how (if it's even possible in the first place).
You could use a derived table. E.g.:
select max(id) from (select 1 id, 'test' name from dual) as t;
In the above example 1 and 'test' represent the row you would need to emulate being present in the table and select max(id) from t represent query1.
You can use SQL to evaluate expressions without touching any table.
Example:
mysql> set #age = 29;
mysql> set #state = 'CA';
mysql> select #age > 30 and #state = 'CA';
+-----------------------------+
| #age > 30 and #state = 'CA' |
+-----------------------------+
| 0 |
+-----------------------------+
This example returns 0 (false) because the #age variable is not greater than 30.
You are certainly free to use values you extract from your JSON data (or any other source) as terms in an SQL expression like this.
It isn't required to use user variables as I showed in my example above. You can just use literal values too. I just showed using variables because it might be more clear to test your query conditions if you use variable names that match your column names.
At the moment I am running a simple select query i.e.
SELECT * FROM `form_expense`
One of the columns in this case titled 'value' returns the following text list_expense_category. I would like to then look up all the values in the table list_expense_category and replace the value with all the results separated by a comma.
I know I am able to do this with an inner join if the value always references the same table but how would I go about this if the value could reference different tables. i.e.
Row 1 - value = list_expense_category
Row 2 - value = list_expense_projects
Row 3 - value = list_expense_currency
I have a bit of an odd mysql query I need to run.
I am passing two arguments to a WHERE clause, $source and $destination (via php). I want mysql to simply return nothing if $source and destination are the same number. Otherwise do the lookup in the DB and pull the record.
Query:
SELECT count(id) AS count
FROM approval
WHERE source=1 AND destination=2 AND approved!=0
Now this will return all rows where source is one and destination is two.
BUT! I want it it to return nothing if source and destination are the same number.
Some rows in certain cases may contain the same number, but that happens in a different potion of the code, in this specific select I want it only to perform the search if source and destination are different. I looked at comparison operators but those all seem to be checking the argument against a value in the column, rather than an argument against an argument.
I think this query does what you want
SELECT count(id) AS count
FROM approvals
WHERE source = $source AND destination = $destination AND approved <> 0
HAVING $source <> $destination;
That would be a simple matter of including:
and source <> destination
in the query. This will exclude rows where the columns are the same which, because of the other clauses, means the arguments you've provided are the same as well.
This will probably still go out to the database but it's guaranteed to return a count of zero (assuming no NULLs of course - if you want to handle those, you'll need extra clauses with IS [NOT] NULL). If you don't even want to go out to the database if your two arguments are the same, the time to do that is in your PHP code by comparing the arguments beforehand.
By way of example, the following DDL:
create table xyzzy (a int, b int);
insert into xyzzy (a,b) values (1,1);
creates a table with one row. When you execute the queries:
SELECT count(a) AS x FROM xyzzy WHERE a = 1 AND b = 1;
SELECT count(a) AS x FROM xyzzy WHERE a = 1 AND b = 1 and a <> b;
you'll get the rows 1 (equivalent to what you currently have) and 0 (equivalent to the change I've proposed).
If you really want to use the parameters instead of the columns, that will work as well:
SELECT count(a) AS x FROM xyzzy WHERE a = 1 AND b = 1 and 1 <> 1;
This also returns a count of zero and is equivalent to just tacking:
and $source <> $destination
onto your query. If the two numbers are identical, this will boil down to and false which will result in a count of zero.
So will either of:
and source <> $destination
and $source <> destination
for that matter.
I need to write a query that combines a variable number of AND WHERE statements.
One statement would look like
SELECT name
FROM users
WHERE field_id=16
AND value BETWEEN 1 AND 100
but I need to return one array of results from a variable amount of fields so field_id's of 16,18,20,25 with each field_id having a specific "AND value..." criteria.
How could I go about doing this to return on set of values?
I am also getting the criteria for the search from a html form and processing it using php (wordpress)
If field_id needs to relate to the value field criteria, you need to AND your field_id and value values together while ORing each set of criteria.
SELECT name
FROM users
WHERE (field_id=16 AND value BETWEEN 1 AND 100)
OR (field_id=17 AND value between 101 and 199)
OR (field_id=18 AND value between 201 and 299)
OR (...
You may also be looking for an IN statement. Which would look like this.
SELECT name
FROM users
WHERE (field_id IN (16,17,18) AND value IN (100,200,300))
...
Fiddle
You can use combination of AND and OR conditions:
SELECT name
FROM users
WHERE (field_id=16 AND value BETWEEN 1 AND 100)
OR (field_id=18 AND value BETWEEN 1 AND 150)
OR (field_id=20 AND value BETWEEN 10 AND 50)
OR (field_id=25 AND value BETWEEN 100 AND 500)
....