diff --git a/strmangle/strmangle.go b/strmangle/strmangle.go
index 28e7672..c9d3e56 100644
--- a/strmangle/strmangle.go
+++ b/strmangle/strmangle.go
@@ -258,6 +258,24 @@ func WhereClause(cols []string, start int) string {
 	return strings.Join(ret, " AND ")
 }
 
+// InClause generates SQL that could go inside an "IN ()" statement
+// $1, $2, $3
+func InClause(start, count int) string {
+	if start == 0 {
+		panic("0 is not a valid start number for inClause")
+	}
+
+	buf := &bytes.Buffer{}
+	for i := 0; i < count; i++ {
+		if i > 0 {
+			buf.WriteByte(',')
+		}
+		fmt.Fprintf(buf, "$%d", i+start)
+	}
+
+	return buf.String()
+}
+
 // DriverUsesLastInsertID returns whether the database driver supports the
 // sql.Result interface.
 func DriverUsesLastInsertID(driverName string) bool {
diff --git a/strmangle/strmangle_test.go b/strmangle/strmangle_test.go
index 67655e1..39165fa 100644
--- a/strmangle/strmangle_test.go
+++ b/strmangle/strmangle_test.go
@@ -334,6 +334,29 @@ func TestWherePrimaryKeyPanic(t *testing.T) {
 	WhereClause(nil, 0)
 }
 
+func TestInClause(t *testing.T) {
+	t.Parallel()
+
+	if str := InClause(1, 2); str != `$1,$2` {
+		t.Error("wrong output:", str)
+	}
+	if str := InClause(2, 2); str != `$2,$3` {
+		t.Error("wrong output:", str)
+	}
+}
+
+func TestInClausePanic(t *testing.T) {
+	t.Parallel()
+
+	defer func() {
+		if recover() == nil {
+			t.Error("did not panic")
+		}
+	}()
+
+	InClause(0, 0)
+}
+
 func TestSubstring(t *testing.T) {
 	t.Parallel()