Snowflake SQL Tutorial 157 - I Dropped A Resource Monitor That Was Guarding A Warehouse, And Snowflake Said Nothing

Snowflake SQL · Episode 157

In this post we are going to cover resource monitors in Snowflake.

A resource monitor is a credit budget. You give it a quota, you give it triggers that say what to do as that quota is used up, and you attach it to one or more warehouses.

The thing to know about them is that a monitor guarding nothing looks almost exactly like one guarding something. Same row, same quota, same triggers. One column tells them apart.

We will start with the warehouses to monitor and build it up step by step. Every query below was run on a real Snowflake account, and every result you see is the output it gave back.

Episode 157 of the Snowflake SQL: Zero to Hero series on TechBrothersIT.

Let's set up two warehouses to monitor

There is no sample table in this post. What we need instead is something to put a budget on.

Where we are running
-- No sample table in this post either.
-- Resource monitors are account-level objects, like warehouses.
USE DATABASE DEMO_DB;
USE SCHEMA PUBLIC;

First, how many resource monitors does this account already have?

Query
SHOW RESOURCE MONITORS;
Result0 rows · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users

None at all, so everything you see below is ours. And here is the session.

Query
SELECT CURRENT_WAREHOUSE() AS THE_WAREHOUSE,
       CURRENT_ROLE()      AS THE_ROLE,
       CURRENT_VERSION()   AS THE_VERSION;
Result1 row · 3 columns
THE_WAREHOUSETHE_ROLETHE_VERSION
NULLACCOUNTADMIN10.36.101

CURRENT_WAREHOUSE() is still NULL, which is where the warehouse episodes left it. Nothing in this post needs to read rows, so it does not get in our way this time.

Now two warehouses of our own.

Create
CREATE OR REPLACE WAREHOUSE WH_T157
    WAREHOUSE_SIZE      = XSMALL
    AUTO_SUSPEND        = 60
    INITIALLY_SUSPENDED = TRUE
    COMMENT             = 'episode 157 - monitored, never used';
Result1 row · 1 column
status
Warehouse WH_T157 successfully created.
Create
CREATE OR REPLACE WAREHOUSE WH2_T157
    WAREHOUSE_SIZE      = XSMALL
    AUTO_SUSPEND        = 60
    INITIALLY_SUSPENDED = TRUE
    COMMENT             = 'episode 157 - the second one';
Result1 row · 1 column
status
Warehouse WH2_T157 successfully created.
Query
SHOW WAREHOUSES LIKE '%\\_T157';
Result2 rows · 38 columns · scroll →
namestatetypesizemin_cluster_countmax_cluster_countstarted_clustersrunningqueuedis_defaultis_currentauto_suspendauto_resumeavailableprovisioningquiescingothercreated_onresumed_onupdated_onownercommentenable_query_accelerationquery_acceleration_max_scale_factorresource_monitoractivespendingsfailedsuspendeduuidscaling_policyowner_role_typeresource_constraintgenerationquery_throughput_multipliermax_query_performance_leveldisabled_reasonstables
WH2_T157SUSPENDEDSTANDARDX-Small11000NY60true2026-10-06 11:10:49.029000-07:002026-10-06 11:10:49.029000-07:002026-10-06 11:10:49.051000-07:00ACCOUNTADMINepisode 157 - the second onetrue8null000172652554737STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
WH_T157SUSPENDEDSTANDARDX-Small11000NN60true2026-10-06 11:10:48.781000-07:002026-10-06 11:10:48.781000-07:002026-10-06 11:10:48.806000-07:00ACCOUNTADMINepisode 157 - monitored, never usedtrue8null000172652554649STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL

That grid is 38 columns wide, so here is the one column that matters right now, read back as rows.

Query
SELECT "name", "state", "size", "resource_monitor"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))
 ORDER BY "name";
Result2 rows · 4 columns
namestatesizeresource_monitor
WH2_T157SUSPENDEDX-Smallnull
WH_T157SUSPENDEDX-Smallnull

Both asleep, and resource_monitor says null on both. Nothing is guarding either of them.

Two spellings of nothing

Look closely at those two cells. They hold the four characters null, in lower case, which is how SHOW WAREHOUSES writes “no monitor attached”. That is a piece of text and not a real SQL NULL. A grid further down this page shows a real NULL, printed in capitals, and the two are different values from two different statements.

Let's create a monitor with a quota and three triggers

A monitor needs a name. Everything else is optional, but the useful form is a credit quota and some triggers. Here is one with a quota of one credit and three triggers at different percentages.

Create
CREATE OR REPLACE RESOURCE MONITOR RM_T157
    WITH CREDIT_QUOTA = 1
    TRIGGERS ON 50 PERCENT DO NOTIFY
             ON 100 PERCENT DO SUSPEND
             ON 110 PERCENT DO SUSPEND_IMMEDIATE;
Result1 row · 1 column
status
Resource monitor RM_T157 successfully created.
Query
SHOW RESOURCE MONITORS LIKE 'RM\\_T157';
Result1 row · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users
RM_T1571.000.001.00NULLMONTHLY2026-09-30 17:00:00-07:00NULL50%100%110%2026-10-06 11:10:51.102000-07:00ACCOUNTADMINNULL

That is 15 columns. Here are the nine worth reading.

Query
SELECT "name", "credit_quota", "used_credits", "remaining_credits", "level",
       "frequency", "notify_at", "suspend_at", "suspend_immediately_at"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Result1 row · 9 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencynotify_atsuspend_atsuspend_immediately_at
RM_T1571.000.001.00NULLMONTHLY50%100%110%

A quota of 1.00, nothing used, 1.00 remaining. The three triggers landed in three separate columns: notify_at 50%, suspend_at 100%, suspend_immediately_at 110%.

And now the column this whole post is about. level reads NULL.

That monitor exists. It has a quota and three triggers. And it is guarding absolutely nothing, because it is not attached to anything yet. level is the only place that shows.

Now let's attach it to a warehouse

Attaching is an ALTER WAREHOUSE, not an alter of the monitor. The warehouse is the thing that gains a property.

Alter
ALTER WAREHOUSE WH_T157 SET RESOURCE_MONITOR = RM_T157;
Result1 row · 1 column
status
Statement executed successfully.
Query
SHOW WAREHOUSES LIKE 'WH\\_T157';
Result1 row · 38 columns · scroll →
namestatetypesizemin_cluster_countmax_cluster_countstarted_clustersrunningqueuedis_defaultis_currentauto_suspendauto_resumeavailableprovisioningquiescingothercreated_onresumed_onupdated_onownercommentenable_query_accelerationquery_acceleration_max_scale_factorresource_monitoractivespendingsfailedsuspendeduuidscaling_policyowner_role_typeresource_constraintgenerationquery_throughput_multipliermax_query_performance_leveldisabled_reasonstables
WH_T157SUSPENDEDSTANDARDX-Small11000NN60true2026-10-06 11:10:48.781000-07:002026-10-06 11:10:48.781000-07:002026-10-06 11:10:52.334000-07:00ACCOUNTADMINepisode 157 - monitored, never usedtrue8RM_T157000172652554649STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
Query
SELECT "name", "state", "resource_monitor"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Result1 row · 3 columns
namestateresource_monitor
WH_T157SUSPENDEDRM_T157

The warehouse now names the monitor, where it said null before.

And the monitor's own row has changed too.

Query
SHOW RESOURCE MONITORS LIKE 'RM\\_T157';
Result1 row · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users
RM_T1571.000.001.00WAREHOUSEMONTHLY2026-09-30 17:00:00-07:00NULL50%100%110%2026-10-06 11:10:51.102000-07:00ACCOUNTADMINNULL
Query
SELECT "name", "credit_quota", "used_credits", "level", "owner"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Result1 row · 5 columns
namecredit_quotaused_creditslevelowner
RM_T1571.000.00WAREHOUSEACCOUNTADMIN

level is now WAREHOUSE. So the monitor does not tell you which warehouse it is on. It only tells you what kind of thing it is attached to. To find out which warehouses, you ask the warehouses.

Can one monitor cover two warehouses?

Let's point the second warehouse at the same monitor and find out.

Alter
ALTER WAREHOUSE WH2_T157 SET RESOURCE_MONITOR = RM_T157;
Result1 row · 1 column
status
Statement executed successfully.
Query
SHOW WAREHOUSES LIKE '%\\_T157';
Result2 rows · 38 columns · scroll →
namestatetypesizemin_cluster_countmax_cluster_countstarted_clustersrunningqueuedis_defaultis_currentauto_suspendauto_resumeavailableprovisioningquiescingothercreated_onresumed_onupdated_onownercommentenable_query_accelerationquery_acceleration_max_scale_factorresource_monitoractivespendingsfailedsuspendeduuidscaling_policyowner_role_typeresource_constraintgenerationquery_throughput_multipliermax_query_performance_leveldisabled_reasonstables
WH2_T157STARTEDSTANDARDX-Small11100NY60true 1000002026-10-06 11:10:49.029000-07:002026-10-06 11:10:50.493000-07:002026-10-06 11:10:54.397000-07:00ACCOUNTADMINepisode 157 - the second onetrue8RM_T157100072652554737STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
WH_T157SUSPENDEDSTANDARDX-Small11000NN60true2026-10-06 11:10:48.781000-07:002026-10-06 11:10:48.781000-07:002026-10-06 11:10:52.334000-07:00ACCOUNTADMINepisode 157 - monitored, never usedtrue8RM_T157000172652554649STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
Query
SELECT "name", "state", "resource_monitor"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))
 ORDER BY "name";
Result2 rows · 3 columns
namestateresource_monitor
WH2_T157STARTEDRM_T157
WH_T157SUSPENDEDRM_T157

Both warehouses name the same monitor. So one budget can cover several warehouses, and they share the quota between them.

Now what is optional, and it is nearly all of it

Before we go further, the shape of a trigger is worth getting straight. Triggers are not a list. They are three fixed columns, one per action.

ActionColumn it lands in
DO NOTIFYnotify_at
DO SUSPENDsuspend_at
DO SUSPEND_IMMEDIATEsuspend_immediately_at

So three triggers of three different kinds fit. A fourth of the same kind does not, because there is nowhere to put it.

Now a monitor with a quota and no triggers at all. Is that allowed?

Create
CREATE OR REPLACE RESOURCE MONITOR RMBARE_T157
    WITH CREDIT_QUOTA = 1;
Result1 row · 1 column
status
Resource monitor RMBARE_T157 successfully created.
Query
SHOW RESOURCE MONITORS LIKE 'RMBARE\\_T157';
Result1 row · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users
RMBARE_T1571.000.001.00NULLMONTHLY2026-09-30 17:00:00-07:00NULLNULLNULLNULL2026-10-06 11:10:56.583000-07:00ACCOUNTADMINNULL
Query
SELECT "name", "credit_quota", "notify_at", "suspend_at",
       "suspend_immediately_at"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Result1 row · 5 columns
namecredit_quotanotify_atsuspend_atsuspend_immediately_at
RMBARE_T1571.00NULLNULLNULL

It is. A quota of 1.00 and all three trigger columns NULL. So a monitor can watch and never act, which may be exactly what you want while you find out what something costs.

Now the other way round. Triggers and no quota.

Create
CREATE OR REPLACE RESOURCE MONITOR RMNOQUOTA_T157
    TRIGGERS ON 100 PERCENT DO SUSPEND;
Result1 row · 1 column
status
Resource monitor RMNOQUOTA_T157 successfully created.
Query
SHOW RESOURCE MONITORS LIKE 'RMNOQUOTA\\_T157';
Result1 row · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users
RMNOQUOTA_T157NULL0.00NULLNULLMONTHLY2026-09-30 17:00:00-07:00NULLNULL100%NULL2026-10-06 11:10:57.439000-07:00ACCOUNTADMINNULL

Also allowed. credit_quota NULL, remaining_credits NULL, and a suspend_at of 100%. 100% of NULL.

I do not know what that monitor would ever do. The grid does not say, so I am not going to tell you. It was accepted. That is the fact.

And a trigger at 0 percent.

Create
CREATE OR REPLACE RESOURCE MONITOR RMZERO_T157
    WITH CREDIT_QUOTA = 1
    TRIGGERS ON 0 PERCENT DO NOTIFY;
Result1 row · 1 column
status
Resource monitor RMZERO_T157 successfully created.
Query
SHOW RESOURCE MONITORS LIKE 'RMZERO\\_T157';
Result1 row · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users
RMZERO_T1571.000.001.00NULLMONTHLY2026-09-30 17:00:00-07:00NULL0%NULLNULL2026-10-06 11:10:57.893000-07:00ACCOUNTADMINNULL
Query
SELECT "name", "credit_quota", "notify_at", "suspend_at"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Result1 row · 4 columns
namecredit_quotanotify_atsuspend_at
RMZERO_T1571.000%NULL

Accepted, and the column reads 0%, exactly as written.

And two columns I never typed came back filled in

Look back at the grids above and you will find a frequency of MONTHLY and a start_time at the beginning of this month, on monitors where I wrote neither. Both are defaults.

Let's write them out on purpose and see what changes.

Create
CREATE OR REPLACE RESOURCE MONITOR RMFREQ_T157
    WITH CREDIT_QUOTA   = 1
         FREQUENCY       = MONTHLY
         START_TIMESTAMP = IMMEDIATELY
    TRIGGERS ON 100 PERCENT DO SUSPEND;
Result1 row · 1 column
status
Resource monitor RMFREQ_T157 successfully created.
Query
SHOW RESOURCE MONITORS LIKE 'RMFREQ\\_T157';
Result1 row · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users
RMFREQ_T1571.000.001.00NULLMONTHLY2026-10-06 11:10:59.489000-07:00NULLNULL100%NULL2026-10-06 11:10:59.475000-07:00ACCOUNTADMINNULL
Query
SELECT "name", "credit_quota", "frequency", "start_time", "end_time"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Result1 row · 5 columns
namecredit_quotafrequencystart_timeend_time
RMFREQ_T1571.00MONTHLY2026-10-06 11:10:59.489000-07:00NULL

The frequency is the same MONTHLY as before, because that is what I asked for. The start_time is different: START_TIMESTAMP = IMMEDIATELY put the create time in it, rather than the start of the month.

And end_time is NULL, which is the monitor saying it has no end date. A quota that resets monthly, for ever.

Five refusals, and they are not all the same kind of no

First, an action word that does not exist.

Create
CREATE OR REPLACE RESOURCE MONITOR RMBADACTION_T157
    WITH CREDIT_QUOTA = 1
    TRIGGERS ON 100 PERCENT DO PANIC;
Query failed — Snowflake returned an error
001003 (42000): SQL compilation error:
syntax error line 3 at position 31 unexpected 'PANIC'.

001003, a syntax error at the word PANIC. So the action list is closed, and the grammar is what refuses you.

Second, a trigger with the percentage left out.

Create
CREATE OR REPLACE RESOURCE MONITOR RMNOPCT_T157
    WITH CREDIT_QUOTA = 1
    TRIGGERS DO SUSPEND;
Query failed — Snowflake returned an error
001003 (42000): SQL compilation error:
syntax error line 3 at position 13 unexpected 'DO'.

001003 again, this time at the word DO. The same kind of refusal.

Third, a negative credit quota.

Create
CREATE OR REPLACE RESOURCE MONITOR RMNEG_T157
    WITH CREDIT_QUOTA = -1
    TRIGGERS ON 100 PERCENT DO SUSPEND;
Query failed — Snowflake returned an error
090254 (42601): Negative credit quota not allowed.

090254, negative credit quota not allowed. That one is different in kind. It is not a syntax error: the sentence parsed perfectly well and the value was rejected.

Fourth, a frequency with no start timestamp.

Create
CREATE OR REPLACE RESOURCE MONITOR RMNOSTART_T157
    WITH CREDIT_QUOTA = 1
         FREQUENCY     = WEEKLY
    TRIGGERS ON 100 PERCENT DO SUSPEND;
Query failed — Snowflake returned an error
090259 (42601): Must specify frequency and start time together.

090259, must specify frequency and start time together. That is my favourite refusal in the episode, because it tells you the rule rather than the position of the problem.

Query
SHOW RESOURCE MONITORS LIKE 'RMNOSTART\\_T157';
Result0 rows · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users

And nothing was created, which is the half worth checking.

And fifth, a frequency that does not exist.

Create
CREATE OR REPLACE RESOURCE MONITOR RMBADFREQ_T157
    WITH CREDIT_QUOTA = 1
         FREQUENCY     = FORTNIGHTLY
    TRIGGERS ON 100 PERCENT DO SUSPEND;
Query failed — Snowflake returned an error
090257 (42601): Unsupported frequency for resource monitor.

090257, unsupported frequency for resource monitor. Another value refusal rather than a grammar one: FORTNIGHTLY parses fine, it is simply not on the list.

Now altering one, and the statement I got wrong

Raising the quota first, because that part is simple.

Alter
ALTER RESOURCE MONITOR RM_T157 SET CREDIT_QUOTA = 2;
Result1 row · 1 column
status
Statement executed successfully.
Query
SHOW RESOURCE MONITORS LIKE 'RM\\_T157';
Result1 row · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users
RM_T1572.000.002.00WAREHOUSEMONTHLY2026-09-30 17:00:00-07:00NULL50%100%110%2026-10-06 11:10:51.102000-07:00ACCOUNTADMINNULL
Query
SELECT "name", "credit_quota", "used_credits", "remaining_credits"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Result1 row · 4 columns
namecredit_quotaused_creditsremaining_credits
RM_T1572.000.002.00

The quota moved to 2.00, nothing used, 2.00 remaining. So SET CREDIT_QUOTA behaves exactly as you would hope.

Now the triggers, and here is what I wrote. I was confident about it.

Alter
ALTER RESOURCE MONITOR RM_T157 SET TRIGGERS ON 90 PERCENT DO SUSPEND;
Query failed — Snowflake returned an error
001003 (42000): SQL compilation error:
syntax error line 1 at position 44 unexpected 'ON'.
syntax error line 1 at position 58 unexpected 'DO'.
syntax error line 1 at position 68 unexpected '<EOF>'.

001003, a syntax error at the word ON, and then two more at DO and at the end of the statement. Read where the first one is: right after TRIGGERS.

TRIGGERS is not an option inside SET. It is a clause of its own, standing next to SET.

Query
SHOW RESOURCE MONITORS LIKE 'RM\\_T157';
Result1 row · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users
RM_T1572.000.002.00WAREHOUSEMONTHLY2026-09-30 17:00:00-07:00NULL50%100%110%2026-10-06 11:10:51.102000-07:00ACCOUNTADMINNULL
Query
SELECT "name", "notify_at", "suspend_at", "suspend_immediately_at"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Result1 row · 4 columns
namenotify_atsuspend_atsuspend_immediately_at
RM_T15750%100%110%

And the triggers are exactly as they were. 50%, 100% and 110%. Nothing was half applied, because the statement never ran at all.

Here is the form that works.

Statement (no captured result)
-- The form that works: TRIGGERS is a clause of its own,
-- standing next to SET, not an option inside it.
-- This episode's capture file has no result for this statement,
-- so there is no result grid for it on this page.
ALTER RESOURCE MONITOR RM_T157 TRIGGERS ON 90 PERCENT DO SUSPEND;
What this page cannot tell you

This episode's capture file has no result for that statement, so there is no grid for it on this page. That also means the obvious follow-up question — whether TRIGGERS replaces every existing trigger or adds one — is not answered by anything on this page, and I am not going to answer it from memory. The grid above is the last word this capture has on those three columns.

One more alter refusal, on a monitor that does not exist.

Alter
ALTER RESOURCE MONITOR NO_SUCH_MONITOR_T157 SET CREDIT_QUOTA = 1;
Query failed — Snowflake returned an error
002003 (02000): SQL compilation error:
Resource monitor 'NO_SUCH_MONITOR_T157' does not exist or not authorized. Your primary role ACCOUNTADMIN or one of your secondary roles must have at least one privilege granted on RESOURCE MONITOR NO_SUCH_MONITOR_T157.

002003, and it names the monitor.

So what if a warehouse already has a monitor?

Let's make a second monitor and attach it to the warehouse that already has the first one.

Create
CREATE OR REPLACE RESOURCE MONITOR RM2_T157
    WITH CREDIT_QUOTA = 1
    TRIGGERS ON 100 PERCENT DO SUSPEND;
Result1 row · 1 column
status
Resource monitor RM2_T157 successfully created.
Alter
ALTER WAREHOUSE WH_T157 SET RESOURCE_MONITOR = RM2_T157;
Result1 row · 1 column
status
Statement executed successfully.

Statement executed successfully. So which one does the warehouse end up with?

Query
SHOW WAREHOUSES LIKE 'WH\\_T157';
Result1 row · 38 columns · scroll →
namestatetypesizemin_cluster_countmax_cluster_countstarted_clustersrunningqueuedis_defaultis_currentauto_suspendauto_resumeavailableprovisioningquiescingothercreated_onresumed_onupdated_onownercommentenable_query_accelerationquery_acceleration_max_scale_factorresource_monitoractivespendingsfailedsuspendeduuidscaling_policyowner_role_typeresource_constraintgenerationquery_throughput_multipliermax_query_performance_leveldisabled_reasonstables
WH_T157SUSPENDEDSTANDARDX-Small11000NN60true2026-10-06 11:10:48.781000-07:002026-10-06 11:10:48.781000-07:002026-10-06 11:11:03.167000-07:00ACCOUNTADMINepisode 157 - monitored, never usedtrue8RM2_T157000172652554649STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
Query
SELECT "name", "resource_monitor"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Result1 row · 2 columns
nameresource_monitor
WH_T157RM2_T157

The second one. The first was not refused and it is not shared. It was replaced, silently.

So a warehouse holds one monitor and the last statement wins. Here is the whole picture before we touch anything else.

Query
SHOW WAREHOUSES LIKE '%\\_T157';
Result2 rows · 38 columns · scroll →
namestatetypesizemin_cluster_countmax_cluster_countstarted_clustersrunningqueuedis_defaultis_currentauto_suspendauto_resumeavailableprovisioningquiescingothercreated_onresumed_onupdated_onownercommentenable_query_accelerationquery_acceleration_max_scale_factorresource_monitoractivespendingsfailedsuspendeduuidscaling_policyowner_role_typeresource_constraintgenerationquery_throughput_multipliermax_query_performance_leveldisabled_reasonstables
WH2_T157STARTEDSTANDARDX-Small11100NY60true 1000002026-10-06 11:10:49.029000-07:002026-10-06 11:10:50.493000-07:002026-10-06 11:10:54.397000-07:00ACCOUNTADMINepisode 157 - the second onetrue8RM_T157100072652554737STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
WH_T157SUSPENDEDSTANDARDX-Small11000NN60true2026-10-06 11:10:48.781000-07:002026-10-06 11:10:48.781000-07:002026-10-06 11:11:03.167000-07:00ACCOUNTADMINepisode 157 - monitored, never usedtrue8RM2_T157000172652554649STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
Query
SELECT "name", "resource_monitor"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))
 ORDER BY "name";
Result2 rows · 2 columns
nameresource_monitor
WH2_T157RM_T157
WH_T157RM2_T157

Two warehouses, two different monitors. One of them lost its original monitor without anyone being told.

Now the one to remember: dropping a monitor that is guarding something

One of those monitors is actively attached to a warehouse. Let's drop it.

Drop
DROP RESOURCE MONITOR IF EXISTS RM2_T157;
Result1 row · 1 column
status
RM2_T157 successfully dropped.

Successfully dropped. No refusal. No warning. Not even a note. And the warehouse it was guarding?

Query
SHOW WAREHOUSES LIKE 'WH\\_T157';
Result1 row · 38 columns · scroll →
namestatetypesizemin_cluster_countmax_cluster_countstarted_clustersrunningqueuedis_defaultis_currentauto_suspendauto_resumeavailableprovisioningquiescingothercreated_onresumed_onupdated_onownercommentenable_query_accelerationquery_acceleration_max_scale_factorresource_monitoractivespendingsfailedsuspendeduuidscaling_policyowner_role_typeresource_constraintgenerationquery_throughput_multipliermax_query_performance_leveldisabled_reasonstables
WH_T157SUSPENDEDSTANDARDX-Small11000NN60true2026-10-06 11:10:48.781000-07:002026-10-06 11:10:48.781000-07:002026-10-06 11:11:03.167000-07:00ACCOUNTADMINepisode 157 - monitored, never usedtrue8null000172652554649STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
Query
SELECT "name", "state", "resource_monitor"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
Result1 row · 3 columns
namestateresource_monitor
WH_T157SUSPENDEDnull

resource_monitor is back to null. The thing that was supposed to stop that warehouse spending is simply gone.

Query
SHOW RESOURCE MONITORS LIKE 'RM2\\_T157';
Result0 rows · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users
The quiet one

Nothing about that sequence looks like a mistake. The drop succeeds, the warehouse carries on, and the only way to find out is to go and look afterwards. If you drop a monitor, check what was attached to it first.

And the level column, across all of them

Here is every monitor this post made, in one grid.

Query
SHOW RESOURCE MONITORS LIKE '%\\_T157';
Result5 rows · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users
RMBARE_T1571.000.001.00NULLMONTHLY2026-09-30 17:00:00-07:00NULLNULLNULLNULL2026-10-06 11:10:56.583000-07:00ACCOUNTADMINNULL
RMFREQ_T1571.000.001.00NULLMONTHLY2026-10-06 11:10:59.489000-07:00NULLNULL100%NULL2026-10-06 11:10:59.475000-07:00ACCOUNTADMINNULL
RMNOQUOTA_T157NULL0.00NULLNULLMONTHLY2026-09-30 17:00:00-07:00NULLNULL100%NULL2026-10-06 11:10:57.439000-07:00ACCOUNTADMINNULL
RMZERO_T1571.000.001.00NULLMONTHLY2026-09-30 17:00:00-07:00NULL0%NULLNULL2026-10-06 11:10:57.893000-07:00ACCOUNTADMINNULL
RM_T1572.000.002.00WAREHOUSEMONTHLY2026-09-30 17:00:00-07:00NULL50%100%110%2026-10-06 11:10:51.102000-07:00ACCOUNTADMINNULL
Query
SELECT "name", "level", "credit_quota", "owner"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))
 ORDER BY "name";
Result5 rows · 4 columns
namelevelcredit_quotaowner
RMBARE_T157NULL1.00ACCOUNTADMIN
RMFREQ_T157NULL1.00ACCOUNTADMIN
RMNOQUOTA_T157NULLNULLACCOUNTADMIN
RMZERO_T157NULL1.00ACCOUNTADMIN
RM_T157WAREHOUSE2.00ACCOUNTADMIN

Five monitors. The level column reads WAREHOUSE on one of them and NULL on the other four.

So four of those exist, hold a quota, and guard nothing at all. They cost nothing and they do nothing. One column is the only place that is visible, and it is the first column to read when somebody tells you a warehouse is budgeted.

And credit_quota on the monitor with no quota is NULL in this grid too, which is the real SQL NULL promised at the top of the page. Compare it with the lower-case null in the warehouse grids. Different statements, different spellings, different kinds of value.

One last thing: let's put the account back

Detach first, then drop, because that is the order that leaves nothing surprising behind.

Alter
ALTER WAREHOUSE WH_T157 UNSET RESOURCE_MONITOR;
Result1 row · 1 column
status
Statement executed successfully.
Alter
ALTER WAREHOUSE WH2_T157 UNSET RESOURCE_MONITOR;
Result1 row · 1 column
status
Statement executed successfully.
Query
SHOW WAREHOUSES LIKE '%\\_T157';
Result2 rows · 38 columns · scroll →
namestatetypesizemin_cluster_countmax_cluster_countstarted_clustersrunningqueuedis_defaultis_currentauto_suspendauto_resumeavailableprovisioningquiescingothercreated_onresumed_onupdated_onownercommentenable_query_accelerationquery_acceleration_max_scale_factorresource_monitoractivespendingsfailedsuspendeduuidscaling_policyowner_role_typeresource_constraintgenerationquery_throughput_multipliermax_query_performance_leveldisabled_reasonstables
WH2_T157STARTEDSTANDARDX-Small11100NY60true 1000002026-10-06 11:10:49.029000-07:002026-10-06 11:10:50.493000-07:002026-10-06 11:11:06.918000-07:00ACCOUNTADMINepisode 157 - the second onetrue8null100072652554737STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
WH_T157SUSPENDEDSTANDARDX-Small11000NN60true2026-10-06 11:10:48.781000-07:002026-10-06 11:10:48.781000-07:002026-10-06 11:11:06.751000-07:00ACCOUNTADMINepisode 157 - monitored, never usedtrue8null000172652554649STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
Query
SELECT "name", "state", "resource_monitor"
  FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))
 ORDER BY "name";
Result2 rows · 3 columns
namestateresource_monitor
WH2_T157STARTEDnull
WH_T157SUSPENDEDnull

Both back to null. ALTER WAREHOUSE ... UNSET RESOURCE_MONITOR is the way to take one off.

Now the drops. Two of them here, and the rest are the same statement with different names.

Drop
DROP RESOURCE MONITOR IF EXISTS RM_T157;
Result1 row · 1 column
status
RM_T157 successfully dropped.
Drop
DROP RESOURCE MONITOR IF EXISTS RMNOQUOTA_T157;
Result1 row · 1 column
status
RMNOQUOTA_T157 successfully dropped.
Drop
DROP WAREHOUSE IF EXISTS WH_T157;
Result1 row · 1 column
status
WH_T157 successfully dropped.
Drop
DROP WAREHOUSE IF EXISTS WH2_T157;
Result1 row · 1 column
status
WH2_T157 successfully dropped.

And the checks.

Query
SHOW RESOURCE MONITORS;
Result0 rows · 15 columns · scroll →
namecredit_quotaused_creditsremaining_creditslevelfrequencystart_timeend_timenotify_atsuspend_atsuspend_immediately_atcreated_onownercommentnotify_users
Query
SHOW WAREHOUSES;
Result3 rows · 38 columns · scroll →
namestatetypesizemin_cluster_countmax_cluster_countstarted_clustersrunningqueuedis_defaultis_currentauto_suspendauto_resumeavailableprovisioningquiescingothercreated_onresumed_onupdated_onownercommentenable_query_accelerationquery_acceleration_max_scale_factorresource_monitoractivespendingsfailedsuspendeduuidscaling_policyowner_role_typeresource_constraintgenerationquery_throughput_multipliermax_query_performance_leveldisabled_reasonstables
COMPUTE_WHSTARTEDSTANDARDX-Small11100YN300true 1000002026-09-21 18:36:58.764000-07:002026-10-06 11:05:18.848000-07:002026-10-06 11:05:18.848000-07:00ACCOUNTADMINtrue8null1000283799204STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
SNOWFLAKE_LEARNING_WHSUSPENDEDSTANDARDX-Small11000NN300true2026-09-21 18:33:47.899000-07:002026-09-21 18:33:47.899000-07:002026-09-21 18:33:47.913000-07:00ACCOUNTADMINCreated by Snowflake during account provisioningtrue8null0001283799128STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL
SYSTEM$STREAMLIT_NOTEBOOK_WHSUSPENDEDSTANDARDX-Small110000NN60true2026-09-21 18:33:46.101000-07:002026-09-21 18:33:46.115000-07:002026-09-21 18:33:46.136000-07:00ACCOUNTADMINtrue8null0001283799044STANDARDROLESTANDARD_GEN_22NULLNULLNULLNULL

No monitors at all, which is where we came in, and the same three warehouses the account shipped with.

Query
SELECT COUNT(*) AS WAREHOUSES_OF_MINE_LEFT
  FROM DEMO_DB.INFORMATION_SCHEMA.WAREHOUSES
 WHERE ENDSWITH(UPPER(WAREHOUSE_NAME), '_T157');
Query failed — Snowflake returned an error
002003 (42S02): SQL compilation error:
Object 'DEMO_DB.INFORMATION_SCHEMA.WAREHOUSES' does not exist or not authorized. Your primary role ACCOUNTADMIN or one of your secondary roles must have at least one privilege granted on TABLE DEMO_DB.INFORMATION_SCHEMA.WAREHOUSES.

That closing check went looking for warehouses in INFORMATION_SCHEMA and got 002003, because warehouses are account-level objects and are not in there. The grid above it is the real proof.

Query
SELECT CURRENT_WAREHOUSE() AS THE_WAREHOUSE,
       CURRENT_ROLE()      AS THE_ROLE,
       CURRENT_DATABASE()  AS THE_DATABASE,
       CURRENT_SCHEMA()    AS THE_SCHEMA;
Result1 row · 4 columns
THE_WAREHOUSETHE_ROLETHE_DATABASETHE_SCHEMA
NULLACCOUNTADMINDEMO_DBPUBLIC

And the session is where it was: no warehouse, and the same role, database and schema.

About the numbers

No credit is deliberately spent anywhere in this post. used_credits reads 0.00 in every grid that has the column, so nothing here shows a trigger firing. What this post shows is how monitors are written, attached, read and removed.

Key points

  • level is the only column that tells you whether a monitor guards anything. It is NULL until the monitor is attached.
  • Triggers are three fixed columns rather than a list: notify_at, suspend_at and suspend_immediately_at.
  • A monitor with no triggers is allowed, and so is a monitor with no quota.
  • FREQUENCY and start_time are filled in for you if you do not write them.
  • Attaching a monitor is an ALTER WAREHOUSE. The warehouse names the monitor, and the monitor only says what kind of thing it is on.
  • One monitor can cover several warehouses, and they share its quota.
  • A warehouse holds one monitor. Attach a second and the first is replaced, silently.
  • DROP RESOURCE MONITOR on an attached monitor succeeds with no warning and leaves the warehouse unguarded.
  • A grammar refusal and a value refusal are different things: 001003 against 090254, 090259 and 090257.
  • ALTER ... SET TRIGGERS is a syntax error. TRIGGERS is a clause of its own, standing next to SET.
  • A failed alter changes nothing. The three trigger columns were exactly as they had been.
Next in this series

In the next post we start on access: roles. What a brand new role can do, what it cannot, and the question of what you are actually wearing.

Every query and every result grid on this page was captured from a live Snowflake account (version 10.36.101) running the demo script for this episode. One statement is shown with no result, because this episode's capture file does not contain one for it, and the post says which question that leaves open rather than answering it from memory. used_credits reads 0.00 in every grid, so no trigger fires anywhere on this page. Everything created here is dropped here. Nothing is timed and no performance claims are made anywhere in this series. Behaviour follows the Snowflake resource monitor documentation.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.