Modern SQL: With – Organize Complex Queries
modern-sql.com
modern-sql.com
I highly recommend his "No to offset" tutorial http://use-the-index-luke.com/no-offset Should be common knowledge, but I still see offset being used way to often, even in core ORM frameworks :-(
(Bonus if you're in Postgres: Postgres optimizes across views, but not across CTEs. Go figure.)
Notably, MSSQL does optimize across CTEs, so there's not as much pressure to break out to views on that platform. And if you're feeling especially perverse, you can make a CTE a view, which sorta... sporks the problem.
My rule is to refactor into a view when and only when it's going to be reused.
> Postgres optimizes across views, but not across CTEs
Sadly true, and one of the few big gripes I have with Postgres. I half recall reading that they are reconsidering this decision, so there's hope.
I've read quite a few people on the email lists claiming that this is a benefit of CTEs. How they justify that, I don't know. Sure, you get predictability of performance, but I have never once written a query worried about how predictably it performed, instead of how fast it performed.
1. Postgres will never implement query hints, because query hints are evil
2. In the real world people need them anyway, so they (ab)use implementation details that offer some control over the query plan (WITH is one example, see also OFFSET 0 and other hacks)
3. Everyone starts relying on these implementation details, e.g. you'll find people actually recommending this as a way to optimize queries in the mailing lists
4. Now when someone asks to fix the planner, someone else will point out this will break all the queries that have been "optimized" by depending on the old behavior
Postgres is generally a fairly sane project though, so I have some confidence they will come to their senses. Eventually.
I'm not sure what you mean by "opaque"; they encapsulate logic, which is a plus.
On the "single namespace" thing, that sort of depends on the RDBMS; e.g., in postgres, within a single DB, one can have multiple schemas, and there is nothing stopping a view in one schema from referencing objects in another schema, so views for one database may exist in separate schemas, which act as namespaces. Other DBs offer equivalent functionality, though the names of structures may be different.
;WITH cte AS(SELECT
ROW_NUMBER() OVER(PARTITION BY SomeVal
ORDER BY SomeDate DESC) AS row,
* FROM SomeTable)
UPDATE cte
SET id = CASE WHEN row = 1 THEN 1 ELSE 0 END
Here is the DDL and DML in case you want to play around with this CREATE TABLE SomeTable(id int,SomeVal char(1),SomeDate date)
INSERT SomeTable
SELECT null,'A','20110101'
UNION ALL
SELECT null,'A','20100101'
UNION ALL
SELECT null,'A','20090101'
UNION ALL
SELECT null,'B','20110101'
UNION ALL
SELECT null,'B','20100101'
UNION ALL
SELECT null,'C','20110101'
UNION ALL
SELECT null,'C','20100101'
UNION ALL
SELECT null,'C','20090101'Any reason to prefer that over INSERT INTO table (col, col) VALUES (v1, v2), (v1, v2), ... ?
A German IT media website (heise.de) pioniered that approach in 2011: https://web.archive.org/web/20150318081341/http://www.heise.... ; the newer code: https://web.archive.org/web/20150424102213/http://www.heise....
It looks like Postgres can do it [1] with its JSON capabilities, and to be honest its being a little while since I've dug into it, so things might have changed (I'm actually not even sure I'm phrasing the question correctly), but the old way of doing a single select and then looping over it in whatever code is calling the SQL and doing N more selects, one for each row to get the sub-array is horrible.
[1] http://bender.io/2013/09/22/returning-hierarchical-data-in-a...
Postgres could do it long before its JSON capabilities, array_agg was added in Postgres 8.4 (2009) and hstore in 8.3 (2008)
SELECT key, value1,
ARRAY(SELECT value2 FROM bar WHERE bar.key = foo.key)
AS value2_array
FROM foo;
?(In Postgres; no "JSON capabilities" needed.)
MySQL with e.g. its InnoDB storage engine is very fast and web scale. Whould a materialized view or other advanced features be useful in MySQL? Sure. But it's a tradeoff. And many devs are willing to go with MySQL for websites.
And SQLite is different than the rest too. And then there are a bunch of NoSQL databases for various usecases.
For some use cases that's fine, sure. But I've also seen many developers come to regret choosing MySQL when, months or years down the line, they ran into issues that hadn't been apparent at first. And honestly I don't see how it's a "tradeoff" when Postgres can offer performance that's every bit the equal of MySQL.
Which seems to be a general with those two DBMS'...
Just like a pipe operator makes long strings of function calls more readable than the semantically identical nested function calls.
Of course this is easy to do with a loop, but can you do it all in pure SQL? If you have a solution I would love to see it.
Here is my history of attempts:
DROP TABLE IF EXISTS dogs;
DROP TABLE IF EXISTS doghouses;
DROP SEQUENCE IF EXISTS dogs_id_seq;
DROP SEQUENCE IF EXISTS doghouses_id_seq;
BEGIN;
CREATE SEQUENCE dogs_id_seq;
CREATE SEQUENCE doghouses_id_seq;
CREATE TABLE dogs (
id INTEGER PRIMARY KEY DEFAULT nextval('dogs_id_seq'),
name TEXT NOT NULL
);
INSERT INTO dogs
(name)
VALUES
('Sparky'),
('Spot')
;
-- Now we want to give each dog a doghouse:
CREATE TABLE doghouses (
id INTEGER PRIMARY KEY DEFAULT nextval('doghouses_id_seq'),
name TEXT NOT NULL
);
ALTER TABLE dogs ADD COLUMN doghouse_id INTEGER REFERENCES doghouses (id);
/*
-- ERROR: syntax error at or near "INTO"
UPDATE dogs AS d
SET doghouse_id = (
INSERT INTO doghouses
(name) VALUES (d.name)
RETURNING id
)
;
*/
/*
-- ERROR: WITH clause containing a data-modifying statement must be at the top level
UPDATE dogs AS d
SET doghouse_id = (
WITH x AS (
INSERT INTO doghouses
(name) VALUES (d.name)
RETURNING id
) SELECT * FROM x
)
;
*/
/*
-- ERROR: missing FROM-clause entry for table "dogs"
WITH homes AS (
INSERT INTO doghouses
(name)
SELECT name
FROM dogs
RETURNING doghouses.id AS doghouse_id, dogs.id AS dog_id
)
UPDATE dogs
SET doghouse_id = homes.doghouse_id
FROM homes
WHERE dogs.id = homes.dog_id
;
*/
-- ERROR: syntax error at or near "INTO"
UPDATE dogs AS d1
SET doghouse_id = h.id
FROM dogs d2
INNER JOIN LATERAL (
INSERT INTO doghouses
(name) VALUES (d2.name)
RETURNING id
)
ON true
WHERE d1.id = d2.id
;
COMMIT;From the description it sounds like you want to have two tables with 1:1 relationship (every dog has a single doghouse). Although this is different than typical 1:1, because both entries supposed to always match. In that scenario you should actually have a single table with two columns (dog name and doghouse name). It'll also be more performant (no need to use joins to fetch the data). There's no benefit to have two tables, especially when you're duplicating the data (doghouse.name = dog.name).
If every dog has to have dog house, and no dogs can share one, then this information is useless and there's no point for storing it.
If for example there are dogs without a doghouse, you can still use one table and have a column with boolean field to store that information.
If multiple dogs can share same doghouse, then you have a foreign key for dog, kind of the way you set it up, but you can't blindly insert data to two tables, because you still need to know which doghouse is shared by which dogs.
If you have multiple dogs and multiple dog houses and need to match one house with one dog, you use 3 tables (one for dogs, one for doghouses, and the third one containing primary keys for both of them that performs the matching. In that scenario you first need to populate dogs and doghouses tables and then you do the matching, so once again you shouldn't insert all data at the same time. This pattern also allows you to easily change which dogs reside in which doghouses.
If you absolutely need to insert to two tables at the same time (your example doesn't show justification for that) then you can do two inserts within a single transaction. You can wrap it in stored procedure or use triggers.
Although initially I'm creating one doghouse for each dog, the model will let dogs start to share doghouses. It's a one-to-many relationship, but to migrate the old data I need to do something, so I'm starting out by giving each dog its own doghouse.
Of course I can use stored procedures, but I'm asking if there is any way to avoid that.
"you can't blindly insert data to two tables, because you still need to know which doghouse is shared by which dogs." That is indeed the crux of the question. :-)
For example:
INSERT INTO tableB (whatever, tempIdFromTableA) SELECT whatever, id FROM tableA;
UPDATE tableA SET yourForeignKeyColumn = (SELECT id FROM tableB WHERE tempIdFromTableA=tableA.id)
... or instead of that update you could do it via a JOIN: UPDATE tableA INNER JOIN tableB ON tableA.id=tableB.tempIdFromTableA SET tableA.yourForeignKeyColumn=tableB.id;
Or you could use a temp table to achieve similar results (if you don't want to have to add and then remove the temp column from table B).Note that it does require 2 separate steps rather than 1 as you appear to desire, so may not work for you:
IF OBJECT_ID('dbo.dogs', 'U') IS NOT NULL
DROP TABLE dbo.dogs
GO
IF OBJECT_ID('dbo.doghouses', 'U') IS NOT NULL
DROP TABLE dbo.doghouses
GO
CREATE TABLE dogs (
id INTEGER IDENTITY (1, 1) PRIMARY KEY
,name VARCHAR(MAX) NOT NULL
);
-- Now we want to give each dog a doghouse:
CREATE TABLE doghouses (
id INTEGER IDENTITY (1, 1) PRIMARY KEY
,name VARCHAR(MAX) NOT NULL
);
GO
ALTER TABLE dogs ADD doghouse_id INTEGER REFERENCES doghouses (id);
GO
INSERT INTO dogs (name)
VALUES
('Sparky'),
('Spot')
;
DECLARE @DogsAndHouses TABLE (
doghouse_id INT
,name VARCHAR(MAX)
);
INSERT INTO dbo.doghouses (name)
OUTPUT INSERTED.id, INSERTED.name INTO @DogsAndHouses
SELECT
d.name
FROM
dbo.dogs d;
UPDATE d
SET
doghouse_id = dah.doghouse_id
FROM
dogs d
JOIN @DogsAndHouses dah ON d.name = dah.name;
GO