Imagine that while developing or testing a SQL Server database, you get an error message mentioning a database object, and you scratch your head, and mutter ‘by the bones of the saints, what is that?’. If it is a small database, then you can glance, maybe, at the browser pane. Once it gets to any size, you’ve got to search the metadata. If you know it is a column than you can look in information_schema.Columns or in sys.columns, but if you don’t know, or if you want to know more about the column, you’re into a certain difficulty. Someone has probably explained what the column does in an extended property. What table or view is it in? what else is in the table?
Finding things in databases isn’t always straightforward. You might think that you can just search the database objects in SQL Server. No, because user types, for example are stored elsewhere, despite being schema-bound. You might want to search in the scheduled jobs too, but there is no way of working out whether the code in a job step relates to a particular database. You might think that the information_schema represented a consistent industry standard, providing a rational logical layer over the seething reality underneath. You might be wrong.
SQL Server, like all RDBMSs, grow in rather strange way, sometimes in response to the esoteric needs of a major customer, the hobbyhorse of a senior manager, or to head off an industry-trend such as NoSQL or Big Data. We have to make retrospective sense of it all.
SQL Server has the concept of a parent object. This roughly corresponds to table, view and module (aka Routine). These parent objects have child objects, which correspond to constraints, and triggers. Internal tables can be either, depending on whether they are queue messages or XML Index nodes that do, or otherwise don’t.
Columns, indexes or parameters aren’t objects. They are treated as ordered lists of attributes to one particular type. Types are schema-based but they aren’t objects. Databases have attributes that aren’t schema-based but which apply to the database as a whole, such as datafiles or dataSpaces. You see some of them in the CREATE database statements in generated scripts.
All this might seem complicated, but I’ve done it for you. I’ve created two SQL Server routines. One is a Table-valued function that list database objects and their paths, and the other is a view that lists the database attributes as well.
You might use the schemaObjects function by itself if you aren’t interested in the database properties, just the database metadata. The obvious way of searching is this. Want to find out where the database processes credit cards?
SELECT Name, path, Comment FROM SchemaObjects () WHERE Name + comment LIKE '%credit%' ORDER BY path;
…which gives you (in AdventureWorks) the following
The ‘path’ column gives you a reasonable sort order, except for the order of columns. It comes in handy for searching for a specific database object type, or for, say, the foreign key for a particular table.
Here we are trying to find out all the foreign keys in person.BusinessEntityAddress
SELECT Name, path, Comment FROM SchemaObjects () WHERE path LIKE '%.foreign_key_constraint.%' AND path LIKE '%person.table.BusinessEntityAddress%';
Maybe you just want to see what is in a table
SELECT Name, path, Comment FROM SchemaObjects () WHERE path LIKE '%humanresources.table.Department.%' ORDER BY path;
So here is the source of the application. If it is updated, the latest version will be in the Gloop project TheGloopSQLServerDatabaseDocumenter/SearchDatabase.sql
GO CREATE OR ALTER FUNCTION dbo.SchemaObjects ( /** Summary: > Returns all the schema objects, their name, path and so on. Author: Phil Factor Date: Tuesday, 28 June 2022 Examples: - Select * from SchemaObjects() order by path - Select Name, path, Comment from SchemaObjects() where Name+comment like '%credit%' order by path - Select Name, path, Comment from SchemaObjects() where path like '%person.table.businessentity.%' - Select Name, path, Comment from SchemaObjects() where path like '%.foreign_key_constraint.%' Returns: > table **/ ) RETURNS @FoundObjects TABLE (TableOrderType INT, object_id INT, minor_id INT, TheType CHAR(2), SQL_Name NVARCHAR(400), "Name" sysname, "path" NVARCHAR(400), comment NVARCHAR(3870)) AS BEGIN INSERT INTO @FoundObjects (TableOrderType, object_id, minor_id, TheType, SQL_Name, "Name", "path", comment) SELECT --objects -- SQL Prompt formatting off CASE WHEN ob.parent_object_id >0 THEN 10 ELSE 0 END AS TableOrderType, ob.object_id, CASE WHEN ob.parent_object_id >0 THEN 1 ELSE 0 END AS minor_id, ob.type AS TheType, CASE WHEN ob.parent_object_id>0 THEN Object_Schema_Name(ob.parent_object_id) + '.'+Object_Name(ob.parent_object_id)+'.'+ob.name ELSE Object_Schema_Name(ob.object_id)+'.'+ob.name END AS SQL_Name,ob.name AS name, '$.Objects.'+Object_Schema_Name(ob.object_id) + CASE WHEN ob.parent_object_id>0 THEN CASE WHEN pa.type IN ('TF','FN','IF','FS','FT') THEN '.function' WHEN pa.type IN ('P', 'PC','RF','X') THEN '.procedure' WHEN pa.type IN ('U','IT') THEN '.table' WHEN pa.type='SQ' THEN '.queue' ELSE '.'+Lower(pa.type_desc) END +'.' +pa.name+'.' ELSE '.' END+ CASE WHEN ob.type IN ('TF','FN','IF','FS','FT') THEN 'function' WHEN ob.type IN ('P', 'PC','RF','X') THEN 'procedure' WHEN ob.type IN ('U','IT') THEN 'table' WHEN ob.type='SQ' THEN 'queue' ELSE Lower(ob.type_desc) END+'.' --+ CASE WHEN ob.parent_object_id>0 -- THEN Object_Name(ob.parent_object_id) COLLATE DATABASE_DEFAULT+'.' -- ELSE '' -- END + ob.name AS "path", -- SQL Prompt formatting on Coalesce (Convert (NVARCHAR(3870), ep.value), '') AS comment FROM sys.objects ob LEFT OUTER JOIN sys.objects pa ON pa.object_id = ob.parent_object_id LEFT OUTER JOIN sys.extended_properties ep ON ep.major_id = ob.object_id AND ep.class = 1 AND ep.minor_id = 0 AND ep.name = 'MS_Description' WHERE ob.is_ms_shipped = 0 AND ob.is_ms_shipped = 0 UNION ALL SELECT 0, 0, xc.xml_collection_id, 'XT', Schema_Name (xc.schema_id) + '.' + xc.name, xc.name, '$.types.' + Schema_Name (xc.schema_id) + '.xml_Schema_collection.' + xc.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') AS comment FROM sys.xml_schema_collections xc LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 10 AND ep.major_id = xc.xml_collection_id AND ep.name = 'MS_Description' WHERE xc.xml_collection_id > 65535 UNION ALL SELECT 0, 0, UT.user_type_id, 'UT', Schema_Name (UT.schema_id) + '.' + UT.name, UT.name, '$.types.' + Schema_Name (UT.schema_id) + '.user_types.' + UT.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') AS comment FROM sys.types UT LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 6 AND ep.major_id = UT.user_type_id AND ep.name = 'MS_Description' WHERE UT.user_type_id <> UT.system_type_id AND Schema_Name (UT.schema_id) <> 'sys'; INSERT INTO @FoundObjects (TableOrderType, object_id, minor_id, TheType, SQL_Name, "Name", "path", comment) SELECT 7, F.object_id AS "OBJECT_ID", ix.index_id AS Minor_id, 'IX', F.SQL_Name + '.' + ix.name, ix.name, F.path + '.indexes.' + ix.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM @FoundObjects F INNER JOIN sys.indexes ix ON F.object_id = ix.object_id LEFT OUTER JOIN sys.extended_properties ep ON ep.major_id = ix.object_id AND ep.class = 7 AND ep.name = 'MS_Description' WHERE ix.index_id > 0 AND ep.minor_id = ix.index_id UNION ALL SELECT 2, F.object_id AS "OBJECT_ID", par.parameter_id AS Minor_id, 'PA', F.SQL_Name + '.' + par.name, par.name, F.path + '.parameters.' + par.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM @FoundObjects F INNER JOIN sys.parameters par ON F.object_id = par.object_id LEFT OUTER JOIN sys.extended_properties ep ON ep.major_id = par.object_id AND ep.class = 2 AND ep.name = 'MS_Description' AND ep.minor_id = par.parameter_id WHERE par.parameter_id > 0 UNION ALL SELECT 3, F.object_id AS "OBJECT_ID", Col.column_id AS Minor_id, 'CO', F.SQL_Name + '.' + Col.name, Col.name, F.path + '.columns.' + Col.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM @FoundObjects F INNER JOIN sys.columns Col ON F.object_id = Col.object_id LEFT OUTER JOIN sys.extended_properties ep ON ep.major_id = Col.object_id AND ep.class = 2 AND ep.name = 'MS_Description' AND ep.minor_id = Col.column_id; RETURN; END; GO
The view adds the attributes at database level. Of course, there aren’t going to be many of these in a sample database but here goes
Select * from The_Metadata where path like ‘$.Attributes%’
CREATE OR ALTER VIEW The_Metadata /** Summary: > This is a view of all the database-level attributes of the current database Author: Phil Factor Date: Tuesday, 28 June 2022 Database: AdventureWorks Examples: -Select Name, Path, Comment from The_Metadata where name+' '+comment like '%credit%' order by path -Select * from The_Metadata where path like '$.Attributes%' Returns: > table **/ AS SELECT Name, path, comment FROM SchemaObjects () UNION ALL SELECT --schemas sch.name, '$.Attributes.schema.' + sch.name AS Attribute, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.schemas sch LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 3 AND ep.major_id = sch.schema_id AND ep.name = 'MS_Description' WHERE sch.schema_id > 4 AND sch.name NOT LIKE 'DB%' UNION ALL --Database SELECT f.Object, '$.Attributes.Name.' + f.Object, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM (VALUES (Db_Name (), 'database')) f ("Object", "type") LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 0 AND ep.name = 'MS_Description' UNION ALL SELECT --Database Files df.name, '$.Attributes.database_file.' + df.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.database_files df LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 22 AND ep.major_id = df.file_id AND ep.name = 'MS_Description' UNION ALL SELECT --Data Spaces ds.name, '$.Attributes.dataspace.' + ds.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.data_spaces ds LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 20 AND ep.major_id = ds.data_space_id AND ep.name = 'MS_Description' UNION ALL SELECT --USER dp.name, '$.Attributes.database_principal.' + dp.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.database_principals dp LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 4 AND ep.major_id = dp.principal_id AND ep.name = 'MS_Description' WHERE dp.type = 'S' UNION ALL SELECT --PARTITION FUNCTION pf.name, '$.Attributes.partition_function.' + pf.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.partition_functions pf LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 21 AND ep.major_id = pf.function_id AND ep.name = 'MS_Description' UNION ALL SELECT --REMOTE SERVICE BINDING rsb.name, '$.Attributes.remote_service_binding.' + rsb.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.remote_service_bindings rsb LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 18 AND ep.major_id = rsb.remote_service_binding_id AND ep.name = 'MS_Description' UNION ALL SELECT --Route rt.name, '$.Attributes.route.' + rt.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.routes rt LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 19 AND ep.major_id = rt.route_id AND ep.name = 'MS_Description' UNION ALL SELECT --Service sv.name COLLATE DATABASE_DEFAULT, '$.Attributes.service.' + sv.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.services sv LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 17 AND ep.major_id = sv.service_id AND ep.name = 'MS_Description' WHERE sv.name NOT LIKE 'http://schemas.microsoft.com/SQL%' UNION ALL SELECT -- 'CONTRACT' svc.name COLLATE DATABASE_DEFAULT, '$.Attributes.service_contract.' + svc.name COLLATE DATABASE_DEFAULT, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.service_contracts svc LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 16 AND ep.major_id = svc.service_contract_id AND ep.name = 'MS_Description' WHERE svc.name NOT LIKE 'http://schemas.microsoft.com/SQL%' UNION ALL SELECT -- 'MESSAGE TYPE' smt.name, '$.Attributes.message_type.' + smt.name COLLATE DATABASE_DEFAULT, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.service_message_types smt LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 15 AND ep.major_id = smt.message_type_id AND ep.name = 'MS_Description' WHERE smt.name NOT LIKE 'http://schemas.microsoft.com/SQL%' UNION ALL SELECT -- 'assembly' asy.name, '$.Attributes.assembly.' + asy.name COLLATE DATABASE_DEFAULT, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.assemblies asy LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 5 AND ep.major_id = asy.assembly_id AND ep.name = 'MS_Description' WHERE asy.name NOT LIKE 'Microsoft.SqlServer.Types' UNION ALL SELECT --'CERTIFICATE' cer.name, '$.Attributes.certificate.' + cer.name, '' FROM sys.certificates cer UNION ALL SELECT --'ASYMMETRIC KEY' amk.name, '$.Attributes.asymmetric_key.' + amk.name, '' FROM sys.asymmetric_keys amk UNION ALL SELECT --'SYMMETRIC KEY' smk.name, '$.Attributes.symmetric_key.' + smk.name, '' FROM sys.symmetric_keys smk UNION ALL SELECT -- 'PLAN GUIDE' pg.name, '$.Attributes.plan_guide.' + pg.name, Coalesce (Convert (NVARCHAR(3870), ep.value), '') FROM sys.plan_guides pg LEFT OUTER JOIN sys.extended_properties ep ON ep.class = 27 AND ep.major_id = pg.plan_guide_id AND ep.name = 'MS_Description'; GO
Conclusion
If you are like me, you will tend to do ad-hoc searching via SQL Calls that are written for the immediate purpose. They aren’t always good for their purpose and I weas inspired to write this so as to be able to do path-based searches and to be able to see the comments (extended properties.) It is based various routines I’d needed for another purpose so it is good to pull it all into shape for this function and view.
The post Searching through a database via SQL appeared first on Simple Talk.
from Simple Talk https://ift.tt/VeCMIgG
via