drizzle-orm icon indicating copy to clipboard operation
drizzle-orm copied to clipboard

[FEATURE]: allow INSERT in CTEs (WITH clauses)

Open stabildev opened this issue 1 year ago • 5 comments
trafficstars

Describe what you want

Drizzle ORM supports SELECT queries in CTEs (WITH clauses). From the docs:

const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42)));
const result = await db.with(sq).select().from(sq);

Currently, Drizzle does not support INSERT queries in CTEs. Example of such a query:

with inserted as (
  insert into orders (user_id, customer_id, order_date, shipping_product, payment_method)
    values (1, 1, now(), 'default', 'cash')
    returning *
)
insert into order_positions (position, order_id, product_id, quantity, price)
  values 
    (1, (select id from inserted), 1, 1, 99.90),
    (2, (select id from inserted), 2, 1, 49.90),
    (3, (select id from inserted), 3, 4, 149.90)
  returning *
;

As you can see, this would be very useful for nested inserts in a single query and should be supported by Drizzle to be a feature complete SQL query builder.

stabildev avatar Mar 28 '24 12:03 stabildev

@AndriiSherman should this also include DELETE as well?

WITH deleted AS (DELETE FROM table WHERE condition IS TRUE RETURNING *) SELECT count(*) FROM deleted;

aaroned avatar Apr 03 '24 06:04 aaroned

IMO it should include INSERT, SELECT, UPDATE and DELETE as Drizzle aims to be full featured SQL-in-TS

stabildev avatar Apr 03 '24 11:04 stabildev

This needs to be added, as it is something that appears in complicated apps! And both MySQL and Postgres support such CTE queries.

TomDo1234 avatar Jun 01 '24 13:06 TomDo1234

+1

In my case, I need to use it with UPDATE. All operations are important for this feature.

Maybe there needs to be a note on the documentation that it only supports SELECT for now.

olivermaldonado avatar Jun 25 '24 23:06 olivermaldonado

Please add this!

valenterry avatar Aug 04 '24 14:08 valenterry

Is this why I get the error qb.getSelectedFields is not a function (pg-core/db.js:88:44) with the following code?

const newTimezoneId = db
	.$with("new_timezone_id")
	.as(
		db
			.insert(timezones)
			.values({ name: "UTC" })
			.onConflictDoNothing()
			.returning({ id: timezones.id }),
	);

michrome avatar Oct 26 '24 14:10 michrome

This would be a game changer.

oliveryasuna avatar Nov 27 '24 02:11 oliveryasuna

Has anyone come up with a workaround?

oliveryasuna avatar Nov 27 '24 03:11 oliveryasuna

I also need this.

benstpierre avatar Nov 27 '24 03:11 benstpierre

@michrome Yes. Have you come up with a workaround?

oliveryasuna avatar Dec 01 '24 01:12 oliveryasuna

Have you come up with a workaround?

I wanted a find_or_create, so was trying to insert then return the value if it already existed. In my app specifically, I know the record will exist most of the time so I broke it into two queries as below. Not a workaround but suitable (better?) for my use case 😀

I agree with comments above though: Drizzle should add support for INSERT with CTEs as there are many valid use cases for it.

async function getTimezoneId(timezoneName) {
	const existingTimezone = await db
		.select({ id: timezones.id })
		.from(timezones)
		.where(eq(timezones.name, timezoneName))
		.limit(1);
	if (existingTimezone.length !== 1) {
		return await createTimezone(timezoneName); // executes an INSERT and returns the new ID
	}
	return existingTimezone[0].id;
}

michrome avatar Dec 01 '24 10:12 michrome

Have you come up with a workaround?

I wanted a find_or_create, so was trying to insert then return the value if it already existed. In my app specifically, I know the record will exist most of the time so I broke it into two queries as below. Not a workaround but suitable (better?) for my use case 😀

I agree with comments above though: Drizzle should add support for INSERT with CTEs as there are many valid use cases for it.

async function getTimezoneId(timezoneName) {
	const existingTimezone = await db
		.select({ id: timezones.id })
		.from(timezones)
		.where(eq(timezones.name, timezoneName))
		.limit(1);
	if (existingTimezone.length !== 1) {
		return await createTimezone(timezoneName); // executes an INSERT and returns the new ID
	}
	return existingTimezone[0].id;
}

Bummer, I was hoping to avoid multiple queries. My company will have to stick with Kysely for data-intensive applications.

oliveryasuna avatar Dec 05 '24 13:12 oliveryasuna

Great feature! It can improve multiple inserts to many tables

kravetsone avatar Dec 07 '24 11:12 kravetsone

really need this in my app, is there a workaround available even if not pretty and/or not fully typesafe that keeps the whole thing in one DB query? i was trying to figure out if i could use toSQL() to build the insert and drop it into a CTE with the sql template helper but wasn't able to get it working with parameters.

erikmunson avatar Dec 20 '24 23:12 erikmunson

I want to add that a nested insert would be useful here. Basically a with clause, but more user friendly.

await db.insert({
  authors: {
    name: 'Jane Austen',
    books: {
      create: [
        { title: 'Pride and Prejudice' },
        { title: 'Sense and Sensibility' },
      ],
    },
  },
});

J

jdgamble555 avatar Dec 24 '24 02:12 jdgamble555

I want to add that a nested insert would be useful here. Basically a with clause, but more user friendly.

await db.insert({
  authors: {
    name: 'Jane Austen',
    books: {
      create: [
        { title: 'Pride and Prejudice' },
        { title: 'Sense and Sensibility' },
      ],
    },
  },
});

J

it more about db.query.create

kravetsone avatar Dec 24 '24 09:12 kravetsone

I want to add that a nested insert would be useful here. Basically a with clause, but more user friendly.

await db.insert({
  authors: {
    name: 'Jane Austen',
    books: {
      create: [
        { title: 'Pride and Prejudice' },
        { title: 'Sense and Sensibility' },
      ],
    },
  },
});

J

it more about db.query.create

What if we want to insert the author but update the book? We need a way to differentiate the nested table action.

Note: Going to use insert instead of create for consistency from SQL CRUD (insert, select, update, delete). You could also get into connect or disconnect, but that is a different feature.

await db.insert({
  authors: {
    name: 'Jane Austen',
    books: {
      insert: [
        { title: 'Pride and Prejudice' },
        { title: 'Sense and Sensibility' },
      ],
    },
  },
});

For update, we would need a key, so:

await db.insert({
  authors: {
    name: 'Jane Austen',
    books: {
      update: [
        { id: '0x123', title: 'Pride and Prejudice' },
        { id: '0x234', title: 'Sense and Sensibility' }
      ],
    },
  },
});

J

jdgamble555 avatar Dec 24 '24 14:12 jdgamble555

I want to add that a nested insert would be useful here. Basically a with clause, but more user friendly.

await db.insert({
  authors: {
    name: 'Jane Austen',
    books: {
      create: [
        { title: 'Pride and Prejudice' },
        { title: 'Sense and Sensibility' },
      ],
    },
  },
});

J

it more about db.query.create

What if we want to insert the author but update the book? We need a way to differentiate the nested table action.

Note: Going to use insert instead of create for consistency from SQL CRUD (insert, select, update, delete). You could also get into connect or disconnect, but that is a different feature.

await db.insert({
  authors: {
    name: 'Jane Austen',
    books: {
      insert: [
        { title: 'Pride and Prejudice' },
        { title: 'Sense and Sensibility' },
      ],
    },
  },
});

For update, we would need a key, so:

await db.insert({
  authors: {
    name: 'Jane Austen',
    books: {
      update: [
        { id: '0x123', title: 'Pride and Prejudice' },
        { id: '0x234', title: 'Sense and Sensibility' }
      ],
    },
  },
});

J

i talk that it more about relation API

this differ from SQL writing - dont like it in query builder

but proposal is good i need it

kravetsone avatar Dec 24 '24 20:12 kravetsone

Would this support multiple with clauses?

J

jdgamble555 avatar Jan 27 '25 14:01 jdgamble555

Would this support multiple with clauses?

J

I have the same question. Essentially, with 0.39.0's update, can we now do chained .with statements? For example:

await db
  .with(
    db.$with('newOrder').as(
      db.insert(orders)
        .values({ userId, productId })
        .returning({ id: orders.id })
    )
  )
  .with(
    db.$with('orderItems').as(
      db.insert(orderItems)
        .values(data.items.map(item => ({
          orderId: sql`newOrder.id`,
          productId: item.productId,
          qty: item.qty,
        })))
        .returning()
    )
  )
  .selectFrom('orderItems')
  .selectAll()

jhwheeler avatar Jun 12 '25 11:06 jhwheeler