Repository: great-expectations/great_expectations

Issue:
Use cleaner solution for non-truncating division in python 2
Prefer `from __future__ import division` to `1.*x/y`


Discussion:
Done inline during improve_partitions push.

Resolving patch:
diff --git a/great_expectations/execution_engine/split_and_sample/data_sampler.py b/great_expectations/execution_engine/split_and_sample/data_sampler.py
--- a/great_expectations/execution_engine/split_and_sample/data_sampler.py
+++ b/great_expectations/execution_engine/split_and_sample/data_sampler.py
@@ -71,8 +71,8 @@ def verify_batch_spec_sampling_kwargs_key_exists(
                 f"Please make sure to provide the {key} key in sampling_kwargs in addition to your sampling_method."
             )
 
+    @staticmethod
     def get_sampling_kwargs_value_or_default(
-        self,
         batch_spec: BatchSpec,
         sampling_kwargs_key: str,
         default_value: Optional[Any] = None,
diff --git a/great_expectations/execution_engine/split_and_sample/sqlalchemy_data_splitter.py b/great_expectations/execution_engine/split_and_sample/sqlalchemy_data_splitter.py
--- a/great_expectations/execution_engine/split_and_sample/sqlalchemy_data_splitter.py
+++ b/great_expectations/execution_engine/split_and_sample/sqlalchemy_data_splitter.py
@@ -48,6 +48,9 @@ class SqlAlchemyDataSplitter(DataSplitter):
     date_part e.g. SqlAlchemyDataSplitter.date_part.MONTH
     """
 
+    def __init__(self, dialect: str):
+        self._dialect = dialect
+
     DATETIME_SPLITTER_METHOD_TO_GET_UNIQUE_BATCH_IDENTIFIERS_METHOD_MAPPING: dict = {
         "split_on_year": "get_data_for_batch_identifiers_year",
         "split_on_year_and_month": "get_data_for_batch_identifiers_year_and_month",
@@ -209,28 +212,84 @@ def split_on_converted_datetime(
             == batch_identifiers[column_name]
         )
 
-    @staticmethod
     def split_on_divided_integer(
+        self,
         column_name: str,
         divisor: int,
         batch_identifiers: dict,
     ) -> bool:
         """Divide the values in the named column by `divisor`, and split on that"""
+        if self._dialect == "sqlite":
+            return (
+                sa.cast(
+                    (sa.cast(sa.column(column_name), sa.Integer) / divisor), sa.Integer
+                )
+                == batch_identifiers[column_name]
+            )
+
+        if self._dialect == "mysql":
+            return (
+                sa.cast(
+                    sa.func.truncate(
+                        (sa.cast(sa.column(column_name), sa.Integer) / divisor), 0
+                    ),
+                    sa.Integer,
+                )
+                == batch_identifiers[column_name]
+            )
+
+        if self._dialect == "mssql":
+            return (
+                sa.cast(
+                    sa.func.round(
+                        (sa.cast(sa.column(column_name), sa.Integer) / divisor), 0, 1
+                    ),
+                    sa.Integer,
+                )
+                == batch_identifiers[column_name]
+            )
+
+        if self._dialect == "awsathena":
+            return (
+                sa.cast(
+                    sa.func.truncate(
+                        sa.cast(sa.column(column_name), sa.Integer) / divisor
+                    ),
+                    sa.Integer,
+                )
+                == batch_identifiers[column_name]
+            )
 
         return (
-            sa.cast(sa.column(column_name) / divisor, sa.Integer)
+            sa.cast(
+                sa.func.trunc(
+                    (sa.cast(sa.column(column_name), sa.Integer) / divisor), 0
+                ),
+                sa.Integer,
+            )
             == batch_identifiers[column_name]
         )
 
-    @staticmethod
     def split_on_mod_integer(
+        self,
         column_name: str,
         mod: int,
         batch_identifiers: dict,
     ) -> bool:
-        """Divide the values in the named column by `divisor`, and split on that"""
+        """Divide the values in the named column by `mod`, and split on that"""
+        if self._dialect in [
+            "sqlite",
+            "mssql",
+        ]:
+            return (
+                sa.cast(sa.column(column_name), sa.Integer) % mod
+                == batch_identifiers[column_name]
+            )
 
-        return sa.column(column_name) % mod == batch_identifiers[column_name]
+        return (
+            sa.func.mod(sa.cast(sa.column(column_name), sa.Integer), mod)
+            == batch_identifiers[column_name]
+        )
 
     @staticmethod
     def split_on_multi_column_values(
@@ -246,17 +305,21 @@ def split_on_multi_column_values(
             )
         )
 
-    @staticmethod
     def split_on_hashed_column(
+        self,
         column_name: str,
         hash_digits: int,
         batch_identifiers: dict,
     ) -> bool:
         """Split on the hashed value of the named column"""
+        if self._dialect == "sqlite":
+            return (
+                sa.func.md5(sa.cast(sa.column(column_name), sa.VARCHAR), hash_digits)
+                == batch_identifiers[column_name]
+            )
 
-        return (
-            sa.func.right(sa.func.md5(sa.column(column_name)), hash_digits)
-            == batch_identifiers[column_name]
+        raise NotImplementedError(
+            f'Splitter method "split_on_hashed_column" is not supported for "{self._dialect}" SQL dialect.'
         )
 
     def get_data_for_batch_identifiers(
@@ -646,8 +709,6 @@ def get_split_query_for_data_for_batch_identifiers_for_split_on_column_value(
         column_name: str,
     ) -> Selectable:
         """Split using the values in the named column"""
-        # query = f"SELECT DISTINCT(\"{self.column_name}\") FROM {self.table_name}"
-
         return (
             sa.select([sa.func.distinct(sa.column(column_name))])
             .select_from(sa.text(table_name))
@@ -661,8 +722,6 @@ def get_split_query_for_data_for_batch_identifiers_for_split_on_converted_dateti
         date_format_string: str = "%Y-%m-%d",
     ) -> Selectable:
         """Convert the values in the named column to the given date_format, and split on that"""
-        # query = f"SELECT DISTINCT( strftime(\"{date_format_string}\", \"{self.column_name}\")) as my_var FROM {self.table_name}"
-
         return sa.select(
             [
                 sa.func.distinct(
@@ -674,30 +733,104 @@ def get_split_query_for_data_for_batch_identifiers_for_split_on_converted_dateti
             ]
         ).select_from(sa.text(table_name))
 
-    @staticmethod
     def get_split_query_for_data_for_batch_identifiers_for_split_on_divided_integer(
+        self,
         table_name: str,
         column_name: str,
         divisor: int,
     ) -> Selectable:
         """Divide the values in the named column by `divisor`, and split on that"""
-        # query = f"SELECT DISTINCT(\"{self.column_name}\" / {divisor}) AS my_var FROM {self.table_name}"
+        if self._dialect == "sqlite":
+            return sa.select(
+                [
+                    sa.func.distinct(
+                        sa.cast(
+                            (sa.cast(sa.column(column_name), sa.Integer) / divisor),
+                            sa.Integer,
+                        )
+                    )
+                ]
+            ).select_from(sa.text(table_name))
+
+        if self._dialect == "mysql":
+            return sa.select(
+                [
+                    sa.func.distinct(
+                        sa.cast(
+                            sa.func.truncate(
+                                (sa.cast(sa.column(column_name), sa.Integer) / divisor),
+                                0,
+                            ),
+                            sa.Integer,
+                        )
+                    )
+                ]
+            ).select_from(sa.text(table_name))
+
+        if self._dialect == "mssql":
+            return sa.select(
+                [
+                    sa.func.distinct(
+                        sa.cast(
+                            sa.func.round(
+                                (sa.cast(sa.column(column_name), sa.Integer) / divisor),
+                                0,
+                                1,
+                            ),
+                            sa.Integer,
+                        )
+                    )
+                ]
+            ).select_from(sa.text(table_name))
+
+        if self._dialect == "awsathena":
+            return sa.select(
+                [
+                    sa.func.distinct(
+                        sa.cast(
+                            sa.func.truncate(
+                                sa.cast(sa.column(column_name), sa.Integer) / divisor
+                            ),
+                            sa.Integer,
+                        )
+                    )
+                ]
+            ).select_from(sa.text(table_name))
 
         return sa.select(
-            [sa.func.distinct(sa.cast(sa.column(column_name) / divisor, sa.Integer))]
+            [
+                sa.func.distinct(
+                    sa.cast(
+                        sa.func.trunc(
+                            (sa.cast(sa.column(column_name), sa.Integer) / divisor), 0
+                        ),
+                        sa.Integer,
+                    )
+                )
+            ]
         ).select_from(sa.text(table_name))
 
-    @staticmethod
     def get_split_query_for_data_for_batch_identifiers_for_split_on_mod_integer(
+        self,
         table_name: str,
         column_name: str,
         mod: int,
     ) -> Selectable:
-        """Divide the values in the named column by `divisor`, and split on that"""
-        # query = f"SELECT DISTINCT(\"{self.column_name}\" / {divisor}) AS my_var FROM {self.table_name}"
+        """Divide the values in the named column by `mod`, and split on that"""
+        if self._dialect in [
+            "sqlite",
+            "mssql",
+        ]:
+            return sa.select(
+                [sa.func.distinct(sa.cast(sa.column(column_name), sa.Integer) % mod)]
+            ).select_from(sa.text(table_name))
 
         return sa.select(
-            [sa.func.distinct(sa.cast(sa.column(column_name) % mod, sa.Integer))]
+            [
+                sa.func.distinct(
+                    sa.func.mod(sa.cast(sa.column(column_name), sa.Integer), mod)
+                )
+            ]
         ).select_from(sa.text(table_name))
 
     @staticmethod
@@ -706,23 +839,30 @@ def get_split_query_for_data_for_batch_identifiers_for_split_on_multi_column_val
         column_names: List[str],
     ) -> Selectable:
         """Split on the joint values in the named columns"""
-        # query = f"SELECT DISTINCT(\"{self.column_name}\") FROM {self.table_name}"
-
         return (
             sa.select([sa.column(column_name) for column_name in column_names])
             .distinct()
             .select_from(sa.text(table_name))
         )
 
-    @staticmethod
     def get_split_query_for_data_for_batch_identifiers_for_split_on_hashed_column(
+        self,
         table_name: str,
         column_name: str,
         hash_digits: int,
     ) -> Selectable:
         """Note: this method is experimental. It does not work with all SQL dialects."""
-        # query = f"SELECT MD5(\"{self.column_name}\") = {matching_hash}) AS hashed_var FROM {self.table_name}"
+        if self._dialect == "sqlite":
+            return sa.select(
+                [
+                    sa.func.distinct(
+                        sa.func.md5(
+                            sa.cast(sa.column(column_name), sa.VARCHAR), hash_digits
+                        )
+                    )
+                ]
+            ).select_from(sa.text(table_name))
 
-        return sa.select([sa.func.md5(sa.column(column_name))]).select_from(
-            sa.text(table_name)
+        raise NotImplementedError(
+            f'Splitter method "split_on_hashed_column" is not supported for "{self._dialect}" SQL dialect.'
         )
diff --git a/great_expectations/execution_engine/sqlalchemy_execution_engine.py b/great_expectations/execution_engine/sqlalchemy_execution_engine.py
--- a/great_expectations/execution_engine/sqlalchemy_execution_engine.py
+++ b/great_expectations/execution_engine/sqlalchemy_execution_engine.py
@@ -1,5 +1,6 @@
 import copy
 import datetime
+import hashlib
 import logging
 import math
 import random
@@ -353,6 +354,13 @@ def __init__(
             ):
                 raw_connection = self._engine_backup.raw_connection()
                 raw_connection.create_function("sqrt", 1, lambda x: math.sqrt(x))
+                raw_connection.create_function(
+                    "md5",
+                    2,
+                    lambda x, d: hashlib.md5(str(x).encode("utf-8")).hexdigest()[
+                        -1 * d :
+                    ],
+                )
 
         # Send a connect event to provide dialect type
         if data_context is not None and getattr(
@@ -384,7 +392,9 @@ def __init__(
         self._config.update(kwargs)
         filter_properties_dict(properties=self._config, clean_falsy=True, inplace=True)
 
-        self._data_splitter = SqlAlchemyDataSplitter()
+        self._data_splitter = SqlAlchemyDataSplitter(
+            dialect=self.engine.dialect.name.lower()
+        )
         self._data_sampler = SqlAlchemyDataSampler()
 
     @property
@@ -442,8 +452,10 @@ def _build_engine(self, credentials: dict, **kwargs) -> "sa.engine.Engine":
         engine = sa.create_engine(options, **create_engine_kwargs)
         return engine
 
+    @staticmethod
     def _get_sqlalchemy_key_pair_auth_url(
-        self, drivername: str, credentials: dict
+        drivername: str,
+        credentials: dict,
     ) -> Tuple["sa.engine.url.URL", Dict]:
         """
         Utilizing a private key path and a passphrase in a given credentials dictionary, attempts to encode the provided


Test patch:
diff --git a/tests/datasource/conftest.py b/tests/datasource/conftest.py
--- a/tests/datasource/conftest.py
+++ b/tests/datasource/conftest.py
@@ -1,3 +1,5 @@
+import hashlib
+import math
 import os
 
 import pytest
@@ -47,6 +49,9 @@ def test_cases_for_sql_data_connector_sqlite_execution_engine(sa):
     conn: sa.engine.Connection = engine.connect()
     raw_connection = engine.raw_connection()
     raw_connection.create_function("sqrt", 1, lambda x: math.sqrt(x))
+    raw_connection.create_function(
+        "md5", 2, lambda x, d: hashlib.md5(str(x).encode("utf-8")).hexdigest()[-1 * d :]
+    )
 
     # Build a SqlAlchemyDataset using that database
     return SqlAlchemyExecutionEngine(
diff --git a/tests/execution_engine/split_and_sample/test_sqlalchemy_execution_engine_splitting.py b/tests/execution_engine/split_and_sample/test_sqlalchemy_execution_engine_splitting.py
--- a/tests/execution_engine/split_and_sample/test_sqlalchemy_execution_engine_splitting.py
+++ b/tests/execution_engine/split_and_sample/test_sqlalchemy_execution_engine_splitting.py
@@ -33,6 +33,8 @@
     TaxiSplittingTestCasesColumnValue,
     TaxiSplittingTestCasesDateTime,
     TaxiSplittingTestCasesDividedInteger,
+    TaxiSplittingTestCasesHashedColumn,
+    TaxiSplittingTestCasesModInteger,
     TaxiSplittingTestCasesWholeTable,
     TaxiTestData,
 )
@@ -73,7 +75,7 @@ def test_named_date_part_methods(
     """Test that a partially pre-filled version of split_on_date_parts() was called with the appropriate params.
     For example, split_on_year.
     """
-    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter()
+    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter(dialect="sqlite")
     column_name: str = "column_name"
     batch_identifiers: dict = {column_name: {"year": 2018, "month": 10, "day": 31}}
 
@@ -108,7 +110,7 @@ def test_split_on_date_parts_single_date_parts(
      or a datetime and also fail when parameters are invalid.
     """
 
-    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter()
+    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter(dialect="sqlite")
     column_name: str = "column_name"
 
     result: sa.sql.elements.BooleanClauseList = data_splitter.split_on_date_parts(
@@ -153,7 +155,7 @@ def test_split_on_date_parts_multiple_date_parts(
      or a datetime and also fail when parameters are invalid.
     """
 
-    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter()
+    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter(dialect="sqlite")
     column_name: str = "column_name"
 
     result: sa.sql.elements.BooleanClauseList = data_splitter.split_on_date_parts(
@@ -195,7 +197,7 @@ def test_get_data_for_batch_identifiers_year(
     mock_get_data_for_batch_identifiers_for_split_on_date_parts: mock.MagicMock,
 ):
     """test that get_data_for_batch_identifiers_for_split_on_date_parts() was called with the appropriate params."""
-    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter()
+    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter(dialect="sqlite")
     table_name: str = "table_name"
     column_name: str = "column_name"
 
@@ -222,7 +224,7 @@ def test_get_data_for_batch_identifiers_year_and_month(
     mock_get_data_for_batch_identifiers_for_split_on_date_parts: mock.MagicMock,
 ):
     """test that get_data_for_batch_identifiers_for_split_on_date_parts() was called with the appropriate params."""
-    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter()
+    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter(dialect="sqlite")
     table_name: str = "table_name"
     column_name: str = "column_name"
 
@@ -249,7 +251,7 @@ def test_get_data_for_batch_identifiers_year_and_month_and_day(
     mock_get_data_for_batch_identifiers_for_split_on_date_parts: mock.MagicMock,
 ):
     """test that get_data_for_batch_identifiers_for_split_on_date_parts() was called with the appropriate params."""
-    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter()
+    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter(dialect="sqlite")
     table_name: str = "table_name"
     column_name: str = "column_name"
 
@@ -280,7 +282,7 @@ def test_get_split_query_for_data_for_batch_identifiers_for_split_on_date_parts_
     query when passed a single element list of date_parts that is a string, DatePart enum object, or mixed case string.
     """
 
-    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter()
+    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter(dialect="sqlite")
     table_name: str = "table_name"
     column_name: str = "column_name"
 
@@ -321,7 +323,7 @@ def test_get_split_query_for_data_for_batch_identifiers_for_split_on_date_parts_
     get_split_query_for_data_for_batch_identifiers_for_split_on_date_parts should
     return the correct query when passed any valid set of parameters including multiple date parts.
     """
-    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter()
+    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter(dialect="sqlite")
     table_name: str = "table_name"
     column_name: str = "column_name"
 
@@ -373,7 +375,7 @@ def test_get_split_query_for_data_for_batch_identifiers_for_split_on_date_parts_
     ],
 )
 def test_get_splitter_method(underscore_prefix: str, splitter_method_name: str):
-    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter()
+    data_splitter: SqlAlchemyDataSplitter = SqlAlchemyDataSplitter(dialect="sqlite")
 
     splitter_method_name_with_prefix = f"{underscore_prefix}{splitter_method_name}"
 
@@ -425,7 +427,19 @@ def in_memory_sqlite_taxi_ten_trips_per_month_execution_engine(sa):
         TaxiSplittingTestCasesDividedInteger(
             taxi_test_data=TaxiTestData(
                 test_df=ten_trips_per_month_df(),
-                test_column_name="passenger_count",
+                test_column_name="pickup_location_id",
+            )
+        ),
+        TaxiSplittingTestCasesModInteger(
+            taxi_test_data=TaxiTestData(
+                test_df=ten_trips_per_month_df(),
+                test_column_name="pickup_location_id",
+            )
+        ),
+        TaxiSplittingTestCasesHashedColumn(
+            taxi_test_data=TaxiTestData(
+                test_df=ten_trips_per_month_df(),
+                test_column_name="pickup_location_id",
             )
         ),
         TaxiSplittingTestCasesDateTime(
diff --git a/tests/integration/db/test_sql_data_split_on_column_value.py b/tests/integration/db/test_sql_data_split_on_column_value.py
--- a/tests/integration/db/test_sql_data_split_on_column_value.py
+++ b/tests/integration/db/test_sql_data_split_on_column_value.py
@@ -28,6 +28,7 @@
     test_df: pd.DataFrame = loaded_table.inserted_dataframe
 
     test_column_name: str = "passenger_count"
+
     if _is_dialect_athena(dialect):
         df_null: pd.DataFrame = test_df[test_df[test_column_name].isnull()]
         df_null[test_column_name] = df_null[test_column_name].apply(
diff --git a/tests/integration/db/test_sql_data_split_on_datetime_and_day_part.py b/tests/integration/db/test_sql_data_split_on_datetime_and_day_part.py
--- a/tests/integration/db/test_sql_data_split_on_datetime_and_day_part.py
+++ b/tests/integration/db/test_sql_data_split_on_datetime_and_day_part.py
@@ -25,9 +25,11 @@
     table_name: str = loaded_table.table_name
     test_df: pd.DataFrame = loaded_table.inserted_dataframe
 
+    test_column_name: str = "pickup_datetime"
+
     taxi_test_data: TaxiTestData = TaxiTestData(
         test_df=test_df,
-        test_column_name="pickup_datetime",
+        test_column_name=test_column_name,
     )
     taxi_splitting_test_cases: TaxiSplittingTestCasesBase = (
         TaxiSplittingTestCasesDateTime(taxi_test_data=taxi_test_data)
diff --git a/tests/integration/db/test_sql_data_split_on_divided_integer.py b/tests/integration/db/test_sql_data_split_on_divided_integer.py
--- a/tests/integration/db/test_sql_data_split_on_divided_integer.py
+++ b/tests/integration/db/test_sql_data_split_on_divided_integer.py
@@ -1,4 +1,3 @@
-import numpy as np
 import pandas as pd
 
 from tests.integration.db.taxi_data_utils import (
@@ -26,14 +25,7 @@
     table_name: str = loaded_table.table_name
     test_df: pd.DataFrame = loaded_table.inserted_dataframe
 
-    test_column_name: str = "passenger_count"
-    df_null: pd.DataFrame = test_df[test_df[test_column_name].isnull()]
-    df_null[test_column_name] = df_null[test_column_name].apply(
-        lambda x: None if np.isnan(x) else x
-    )
-    df_nonnull: pd.DataFrame = test_df[~test_df[test_column_name].isnull()]
-    df_nonnull[test_column_name] = df_nonnull[test_column_name].astype(int)
-    test_df = pd.concat([df_null, df_nonnull])
+    test_column_name: str = "pickup_location_id"
 
     taxi_test_data: TaxiTestData = TaxiTestData(
         test_df=test_df,
diff --git a/tests/integration/db/test_sql_data_split_on_hashed_column.py b/tests/integration/db/test_sql_data_split_on_hashed_column.py
new file mode 100644
--- /dev/null
+++ b/tests/integration/db/test_sql_data_split_on_hashed_column.py
@@ -0,0 +1,41 @@
+import pandas as pd
+
+from tests.integration.db.taxi_data_utils import (
+    _execute_taxi_splitting_test_cases,
+    _get_loaded_table,
+)
+from tests.integration.fixtures.split_and_sample_data.splitter_test_cases_and_fixtures import (
+    TaxiSplittingTestCasesBase,
+    TaxiSplittingTestCasesHashedColumn,
+    TaxiTestData,
+)
+from tests.test_utils import LoadedTable, get_connection_string_and_dialect
+
+if __name__ == "test_script_module":
+
+    dialect: str
+    connection_string: str
+    dialect, connection_string = get_connection_string_and_dialect(
+        athena_db_name_env_var="ATHENA_TEN_TRIPS_DB_NAME"
+    )
+    print(f"Testing dialect: {dialect}")
+
+    loaded_table: LoadedTable = _get_loaded_table(dialect=dialect)
+
+    table_name: str = loaded_table.table_name
+    test_df: pd.DataFrame = loaded_table.inserted_dataframe
+
+    test_column_name: str = "pickup_location_id"
+
+    taxi_test_data: TaxiTestData = TaxiTestData(
+        test_df=test_df,
+        test_column_name=test_column_name,
+    )
+    taxi_splitting_test_cases: TaxiSplittingTestCasesBase = (
+        TaxiSplittingTestCasesHashedColumn(taxi_test_data=taxi_test_data)
+    )
+    _execute_taxi_splitting_test_cases(
+        taxi_splitting_test_cases=taxi_splitting_test_cases,
+        connection_string=connection_string,
+        table_name=table_name,
+    )
diff --git a/tests/integration/db/test_sql_data_split_on_mod_integer.py b/tests/integration/db/test_sql_data_split_on_mod_integer.py
new file mode 100644
--- /dev/null
+++ b/tests/integration/db/test_sql_data_split_on_mod_integer.py
@@ -0,0 +1,41 @@
+import pandas as pd
+
+from tests.integration.db.taxi_data_utils import (
+    _execute_taxi_splitting_test_cases,
+    _get_loaded_table,
+)
+from tests.integration.fixtures.split_and_sample_data.splitter_test_cases_and_fixtures import (
+    TaxiSplittingTestCasesBase,
+    TaxiSplittingTestCasesModInteger,
+    TaxiTestData,
+)
+from tests.test_utils import LoadedTable, get_connection_string_and_dialect
+
+if __name__ == "test_script_module":
+
+    dialect: str
+    connection_string: str
+    dialect, connection_string = get_connection_string_and_dialect(
+        athena_db_name_env_var="ATHENA_TEN_TRIPS_DB_NAME"
+    )
+    print(f"Testing dialect: {dialect}")
+
+    loaded_table: LoadedTable = _get_loaded_table(dialect=dialect)
+
+    table_name: str = loaded_table.table_name
+    test_df: pd.DataFrame = loaded_table.inserted_dataframe
+
+    test_column_name: str = "pickup_location_id"
+
+    taxi_test_data: TaxiTestData = TaxiTestData(
+        test_df=test_df,
+        test_column_name=test_column_name,
+    )
+    taxi_splitting_test_cases: TaxiSplittingTestCasesBase = (
+        TaxiSplittingTestCasesModInteger(taxi_test_data=taxi_test_data)
+    )
+    _execute_taxi_splitting_test_cases(
+        taxi_splitting_test_cases=taxi_splitting_test_cases,
+        connection_string=connection_string,
+        table_name=table_name,
+    )
diff --git a/tests/integration/fixtures/split_and_sample_data/splitter_test_cases_and_fixtures.py b/tests/integration/fixtures/split_and_sample_data/splitter_test_cases_and_fixtures.py
--- a/tests/integration/fixtures/split_and_sample_data/splitter_test_cases_and_fixtures.py
+++ b/tests/integration/fixtures/split_and_sample_data/splitter_test_cases_and_fixtures.py
@@ -1,4 +1,5 @@
 import datetime
+import hashlib
 from abc import ABC, abstractmethod
 from dataclasses import dataclass
 from typing import Any, List, Optional
@@ -76,26 +77,80 @@ def year_month_day_batch_identifier_data(self) -> List[dict]:
             ),
         )
 
-    def get_unique_test_column_values(
+    def get_test_column_values(self) -> List[Optional[Any]]:
+        column_values: List[Optional[Any]] = self.test_df[
+            self.test_column_name
+        ].tolist()
+        return column_values
+
+    def get_unique_sorted_test_column_values(
         self,
         reverse: Optional[bool] = False,
         move_null_to_front: Optional[bool] = False,
         limit: Optional[int] = None,
     ) -> List[Optional[Any]]:
-        column_values: List[Optional[Any]] = sorted(
-            self.test_df[self.test_column_name].unique().tolist(),
+        column_values: List[Optional[Any]] = self.get_test_column_values()
+        column_values = list(set(column_values))
+        column_values = sorted(
+            column_values,
             key=lambda element: (element is None, element),
             reverse=reverse,
         )
 
-        if move_null_to_front:
-            column_values = [None] + column_values[0:-1]
+        column_value: Any
+        if (
+            move_null_to_front
+            and any([column_value is None for column_value in column_values])
+            and column_values[0] is not None
+            and column_values[-1] is None
+        ):
+            num_null_values: int = column_values.count(None)
+            column_values = list(filter(None, column_values))
+            column_values = num_null_values * [None] + column_values
 
         if limit is None:
             return column_values
 
         return column_values[:limit]
 
+    def get_divided_integer_test_column_values(
+        self, divisor: int
+    ) -> List[Optional[Any]]:
+        column_values: List[Optional[Any]] = self.get_test_column_values()
+
+        column_value: Any
+        column_values = [column_value // divisor for column_value in column_values]
+
+        return list(set(column_values))
+
+    def get_mod_integer_test_column_values(self, mod: int) -> List[Optional[Any]]:
+        column_values: List[Optional[Any]] = self.get_test_column_values()
+
+        column_value: Any
+        column_values = [column_value % mod for column_value in column_values]
+
+        return list(set(column_values))
+
+    def get_hashed_test_column_values(self, hash_digits: int) -> List[Optional[Any]]:
+        """
+        hashlib.md5(string).hexdigest()
+        hashlib.md5(str(tuple_).encode("utf-8")).hexdigest()
+        [:num_digits]
+        """
+        column_values: List[Optional[Any]] = self.get_unique_sorted_test_column_values(
+            reverse=False, move_null_to_front=False, limit=None
+        )
+
+        column_value: Any
+        column_values = [
+            hashlib.md5(str(column_value).encode("utf-8")).hexdigest()[
+                -1 * hash_digits :
+            ]
+            for column_value in column_values
+        ]
+
+        return list(sorted(set(column_values)))
+
 
 @dataclass
 class TaxiSplittingTestCase:
@@ -153,7 +208,9 @@ def test_cases(self) -> List[TaxiSplittingTestCase]:
                 },
                 num_expected_batch_definitions=8,
                 num_expected_rows_in_first_batch_definition=9,
-                expected_column_values=self.taxi_test_data.get_unique_test_column_values(),
+                expected_column_values=self.taxi_test_data.get_unique_sorted_test_column_values(
+                    reverse=False, move_null_to_front=False, limit=None
+                ),
             ),
         ]
 
@@ -166,12 +223,50 @@ def test_cases(self) -> List[TaxiSplittingTestCase]:
                 splitter_method_name="split_on_divided_integer",
                 splitter_kwargs={
                     "column_name": self.taxi_test_data.test_column_name,
-                    "divisor": 3,
+                    "divisor": 5,
+                },
+                num_expected_batch_definitions=41,
+                num_expected_rows_in_first_batch_definition=2,
+                expected_column_values=self.taxi_test_data.get_divided_integer_test_column_values(
+                    divisor=5
+                ),
+            ),
+        ]
+
+
+class TaxiSplittingTestCasesModInteger(TaxiSplittingTestCasesBase):
+    def test_cases(self) -> List[TaxiSplittingTestCase]:
+        return [
+            TaxiSplittingTestCase(
+                table_domain_test_case=False,
+                splitter_method_name="split_on_mod_integer",
+                splitter_kwargs={
+                    "column_name": self.taxi_test_data.test_column_name,
+                    "mod": 5,
+                },
+                num_expected_batch_definitions=5,
+                num_expected_rows_in_first_batch_definition=61,
+                expected_column_values=self.taxi_test_data.get_mod_integer_test_column_values(
+                    mod=5
+                ),
+            ),
+        ]
+
+
+class TaxiSplittingTestCasesHashedColumn(TaxiSplittingTestCasesBase):
+    def test_cases(self) -> List[TaxiSplittingTestCase]:
+        return [
+            TaxiSplittingTestCase(
+                table_domain_test_case=False,
+                splitter_method_name="split_on_hashed_column",
+                splitter_kwargs={
+                    "column_name": self.taxi_test_data.test_column_name,
+                    "hash_digits": 2,
                 },
-                num_expected_batch_definitions=4,
-                num_expected_rows_in_first_batch_definition=14,
-                expected_column_values=self.taxi_test_data.get_unique_test_column_values(
-                    move_null_to_front=True, limit=4
+                num_expected_batch_definitions=5,
+                num_expected_rows_in_first_batch_definition=5,
+                expected_column_values=self.taxi_test_data.get_hashed_test_column_values(
+                    hash_digits=2
                 ),
             ),
         ]
diff --git a/tests/integration/test_script_runner.py b/tests/integration/test_script_runner.py
--- a/tests/integration/test_script_runner.py
+++ b/tests/integration/test_script_runner.py
@@ -655,6 +655,209 @@ class IntegrationTestFixture:
         ),
         extra_backend_dependencies=BackendDependencies.AWS,
     ),
+    IntegrationTestFixture(
+        name="split_data_on_mod_integer_postgres",
+        user_flow_script="tests/integration/db/test_sql_data_split_on_mod_integer.py",
+        data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+        data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+        util_script="tests/test_utils.py",
+        other_files=(
+            (
+                "tests/integration/fixtures/split_and_sample_data/postgres_connection_string.yml",
+                "connection_string.yml",
+            ),
+        ),
+        extra_backend_dependencies=BackendDependencies.POSTGRESQL,
+    ),
+    IntegrationTestFixture(
+        name="split_data_on_mod_integer_redshift",
+        user_flow_script="tests/integration/db/test_sql_data_split_on_mod_integer.py",
+        data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+        data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+        util_script="tests/test_utils.py",
+        other_files=(
+            (
+                "tests/integration/fixtures/split_and_sample_data/redshift_connection_string.yml",
+                "connection_string.yml",
+            ),
+        ),
+        extra_backend_dependencies=BackendDependencies.REDSHIFT,
+    ),
+    IntegrationTestFixture(
+        name="split_data_on_mod_integer_mssql",
+        user_flow_script="tests/integration/db/test_sql_data_split_on_mod_integer.py",
+        data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+        data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+        util_script="tests/test_utils.py",
+        other_files=(
+            (
+                "tests/integration/fixtures/split_and_sample_data/mssql_connection_string.yml",
+                "connection_string.yml",
+            ),
+        ),
+        extra_backend_dependencies=BackendDependencies.MSSQL,
+    ),
+    IntegrationTestFixture(
+        name="split_data_on_mod_integer_mysql",
+        user_flow_script="tests/integration/db/test_sql_data_split_on_mod_integer.py",
+        data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+        data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+        util_script="tests/test_utils.py",
+        other_files=(
+            (
+                "tests/integration/fixtures/split_and_sample_data/mysql_connection_string.yml",
+                "connection_string.yml",
+            ),
+        ),
+        extra_backend_dependencies=BackendDependencies.MYSQL,
+    ),
+    IntegrationTestFixture(
+        name="split_data_on_mod_integer_snowflake",
+        user_flow_script="tests/integration/db/test_sql_data_split_on_mod_integer.py",
+        data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+        data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+        util_script="tests/test_utils.py",
+        other_files=(
+            (
+                "tests/integration/fixtures/split_and_sample_data/snowflake_connection_string.yml",
+                "connection_string.yml",
+            ),
+        ),
+        extra_backend_dependencies=BackendDependencies.SNOWFLAKE,
+    ),
+    IntegrationTestFixture(
+        name="split_data_on_mod_integer_bigquery",
+        user_flow_script="tests/integration/db/test_sql_data_split_on_mod_integer.py",
+        data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+        data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+        util_script="tests/test_utils.py",
+        other_files=(
+            (
+                "tests/integration/fixtures/split_and_sample_data/bigquery_connection_string.yml",
+                "connection_string.yml",
+            ),
+        ),
+        extra_backend_dependencies=BackendDependencies.BIGQUERY,
+    ),
+    IntegrationTestFixture(
+        name="split_data_on_mod_integer_awsathena",
+        user_flow_script="tests/integration/db/test_sql_data_split_on_mod_integer.py",
+        data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+        data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+        util_script="tests/test_utils.py",
+        other_files=(
+            (
+                "tests/integration/fixtures/split_and_sample_data/awsathena_connection_string.yml",
+                "connection_string.yml",
+            ),
+        ),
+        extra_backend_dependencies=BackendDependencies.AWS,
+    ),
+    # TODO: <Alex>ALEX -- Uncomment next statement when "split_on_hashed_column" for POSTGRESQL is implemented.</Alex>
+    # IntegrationTestFixture(
+    #     name="split_data_on_hashed_column_postgres",
+    #     user_flow_script="tests/integration/db/test_sql_data_split_on_hashed_column.py",
+    #     data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+    #     data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+    #     util_script="tests/test_utils.py",
+    #     other_files=(
+    #         (
+    #             "tests/integration/fixtures/split_and_sample_data/postgres_connection_string.yml",
+    #             "connection_string.yml",
+    #         ),
+    #     ),
+    #     extra_backend_dependencies=BackendDependencies.POSTGRESQL,
+    # ),
+    # TODO: <Alex>ALEX -- Uncomment next statement when "split_on_hashed_column" for REDSHIFT is implemented.</Alex>
+    # IntegrationTestFixture(
+    #     name="split_data_on_hashed_column_redshift",
+    #     user_flow_script="tests/integration/db/test_sql_data_split_on_hashed_column.py",
+    #     data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+    #     data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+    #     util_script="tests/test_utils.py",
+    #     other_files=(
+    #         (
+    #             "tests/integration/fixtures/split_and_sample_data/redshift_connection_string.yml",
+    #             "connection_string.yml",
+    #         ),
+    #     ),
+    #     extra_backend_dependencies=BackendDependencies.REDSHIFT,
+    # ),
+    # TODO: <Alex>ALEX -- Uncomment next statement when "split_on_hashed_column" for MSSQL is implemented.</Alex>
+    # IntegrationTestFixture(
+    #     name="split_data_on_hashed_column_mssql",
+    #     user_flow_script="tests/integration/db/test_sql_data_split_on_hashed_column.py",
+    #     data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+    #     data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+    #     util_script="tests/test_utils.py",
+    #     other_files=(
+    #         (
+    #             "tests/integration/fixtures/split_and_sample_data/mssql_connection_string.yml",
+    #             "connection_string.yml",
+    #         ),
+    #     ),
+    #     extra_backend_dependencies=BackendDependencies.MSSQL,
+    # ),
+    # TODO: <Alex>ALEX -- Uncomment next statement when "split_on_hashed_column" for MYSQL is implemented.</Alex>
+    # IntegrationTestFixture(
+    #     name="split_data_on_hashed_column_mysql",
+    #     user_flow_script="tests/integration/db/test_sql_data_split_on_hashed_column.py",
+    #     data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+    #     data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+    #     util_script="tests/test_utils.py",
+    #     other_files=(
+    #         (
+    #             "tests/integration/fixtures/split_and_sample_data/mysql_connection_string.yml",
+    #             "connection_string.yml",
+    #         ),
+    #     ),
+    #     extra_backend_dependencies=BackendDependencies.MYSQL,
+    # ),
+    # TODO: <Alex>ALEX -- Uncomment next statement when "split_on_hashed_column" for SNOWFLAKE is implemented.</Alex>
+    # IntegrationTestFixture(
+    #     name="split_data_on_hashed_column_snowflake",
+    #     user_flow_script="tests/integration/db/test_sql_data_split_on_hashed_column.py",
+    #     data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+    #     data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+    #     util_script="tests/test_utils.py",
+    #     other_files=(
+    #         (
+    #             "tests/integration/fixtures/split_and_sample_data/snowflake_connection_string.yml",
+    #             "connection_string.yml",
+    #         ),
+    #     ),
+    #     extra_backend_dependencies=BackendDependencies.SNOWFLAKE,
+    # ),
+    # TODO: <Alex>ALEX -- Uncomment next statement when "split_on_hashed_column" for BIGQUERY is implemented.</Alex>
+    # IntegrationTestFixture(
+    #     name="split_data_on_hashed_column_bigquery",
+    #     user_flow_script="tests/integration/db/test_sql_data_split_on_hashed_column.py",
+    #     data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+    #     data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+    #     util_script="tests/test_utils.py",
+    #     other_files=(
+    #         (
+    #             "tests/integration/fixtures/split_and_sample_data/bigquery_connection_string.yml",
+    #             "connection_string.yml",
+    #         ),
+    #     ),
+    #     extra_backend_dependencies=BackendDependencies.BIGQUERY,
+    # ),
+    # TODO: <Alex>ALEX -- Uncomment next statement when "split_on_hashed_column" for AWS ATHENA is implemented.</Alex>
+    # IntegrationTestFixture(
+    #     name="split_data_on_hashed_column_awsathena",
+    #     user_flow_script="tests/integration/db/test_sql_data_split_on_hashed_column.py",
+    #     data_context_dir="tests/integration/fixtures/no_datasources/great_expectations",
+    #     data_dir="tests/test_sets/taxi_yellow_tripdata_samples/",
+    #     util_script="tests/test_utils.py",
+    #     other_files=(
+    #         (
+    #             "tests/integration/fixtures/split_and_sample_data/awsathena_connection_string.yml",
+    #             "connection_string.yml",
+    #         ),
+    #     ),
+    #     extra_backend_dependencies=BackendDependencies.AWS,
+    # ),
     IntegrationTestFixture(
         name="split_data_on_datetime_postgres",
         user_flow_script="tests/integration/db/test_sql_data_split_on_datetime_and_day_part.py",