Wednesday, June 29, 2022

Searching through a database via SQL

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

Graphical user interface, text, application Description automatically generated

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;

 

Graphical user interface, text Description automatically generated

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%’

Graphical user interface, text, application Description automatically generated

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

No comments:

Post a Comment