Я видел, как многие люди утверждали, что вы должны специально называть каждый столбец, который хотите, в вашем запросе выбора.
Предполагая, что я все равно буду использовать все столбцы, почему бы мне не использовать SELECT *?
Даже учитывая вопрос * SQL-запрос - выберите * из представления или выберите col1, col2,… colN из представления *, я не думаю, что это точный дубликат, поскольку я подхожу к проблеме с несколько иной точки зрения.
Один из наших принципов - не проводить оптимизацию раньше времени. Имея это в виду, похоже, что использование SELECT * должно быть методом предпочтительный, пока не будет доказано, что это проблема с ресурсами, или схема в значительной степени не высечена в камне. Как мы знаем, этого не произойдет, пока разработка не будет полностью завершена.
Тем не менее, есть ли основная проблема в отказе от использования SELECT *?


Одна из основных причин заключается в том, что если вы когда-либо добавляете / удаляете столбцы из своей таблицы, любой запрос / процедура, выполняющая вызов SELECT *, теперь будет получать больше или меньше столбцов данных, чем ожидалось.
Но все пишут код, который требует, чтобы программисты знали, какие данные возвращаются. Вы не можете Ctrl + F имя своего столбца, если оно скрыто в SELECT *.
Если ваш код зависит от того, что столбцы расположены в определенном порядке, ваш код сломается при изменении таблицы. Кроме того, при выборе * вы можете получать слишком много данных из таблицы, особенно если в таблице есть двоичное поле.
Тот факт, что вы сейчас используете все столбцы, не означает, что кто-то еще не собирается добавлять дополнительный столбец в таблицу.
Это также увеличивает накладные расходы на кэширование выполнения плана, поскольку ему необходимо получить метаданные о таблице, чтобы узнать, какие столбцы находятся в *.
Хороший ответ, но я бы изменил «код сломается» на «код МОЖЕТ сломаться». Вот в чем настоящая проблема: использование "select *" не ВСЕГДА приводит к критическим изменениям. И когда разрыв действительно случается, это обычно сильно отделено от использования, которое в конечном итоге оказывается сломанным.
Если кто-то обычно ссылается на столбцы в своем коде, у него проблемы, независимо от того, используют ли они SELECT * или нет. Накладные расходы на выполнение плана тривиальны и в любом случае не будут иметь значения после кеширования плана.
Тогда ошибка программиста заключается в написании кода, который зависит от последовательности столбцов. Тебе никогда не нужно этого делать.
@doofledorfer - никогда не говори никогда. Доступ к порядковым столбцам быстрее, и иногда это практично. Использование select * - большая ошибка, чем использование порядкового доступа.
Даже если вы используете каждый столбец, но адресуетесь к массиву строк по числовому индексу, у вас будут проблемы, если вы добавите еще одну строку позже.
Так что в основном это вопрос ремонтопригодности! Если вы не используете селектор *, вам не придется беспокоиться о своих запросах.
Выбор только необходимых столбцов позволяет уменьшить размер набора данных в памяти и, следовательно, ускорить работу вашего приложения.
Кроме того, многие инструменты (например, хранимые процедуры) также кэшируют планы выполнения запросов. Если вы позже добавите или удалите столбец (что особенно просто, если вы выбираете вне представления), инструмент часто будет выдавать ошибку, если не вернет ожидаемые результаты.
Причин несколько:
Примечание. В приведенном выше примере я выбрал INTEGER, потому что они имеют фиксированный размер 4 байта.
1 и 2 будут запахом кода, а 3 и 4 звучат как преждевременная оптимизация.
Если ваше приложение получает данные с помощью SELECT * и структура таблицы в базе данных изменена (например, столбец удален), ваше приложение будет терпеть неудачу во всех местах, где вы ссылаетесь на отсутствующее поле. Если вместо этого вы включите все столбцы в свой запрос, ваше приложение сломается (надеюсь) в одном месте, где вы изначально получаете данные, что упростит исправление.
При этом существует ряд ситуаций, в которых желателен SELECT *. Одна из них - это ситуация, с которой я сталкиваюсь все время, когда мне нужно реплицировать всю таблицу в другую базу данных (например, SQL Server в DB2). Другой - приложение, написанное для отображения таблиц в общем виде (т.е. без каких-либо сведений о какой-либо конкретной таблице).
Вопрос не в том, что «желательно ли * когда-либо», поэтому вторая часть вашего ответа не имеет значения. Вопрос гласит, что использование 'select *' должно быть предпочтительнее, что, конечно, полная чушь.
Да, моя 2-я часть не имеет значения. OQ изменил вопрос, указав, что SELECT * предпочтительнее, и да, это какая-то чушь.
Ах да, извините - вопрос изменил свое направление после вашего ответа.
Все в порядке. Даже Моцарт был редактором (stackoverflow.com/questions/292682/…). В моем исходном сообщении говорилось, что использование SELECT * привело к каннибализму. :)
Вы можете объединить две таблицы и использовать столбец A из второй таблицы. Если вы позже добавите столбец A в первую таблицу (с тем же именем, но, возможно, с другим значением), вы, скорее всего, получите значения из первой таблицы, а не из второй, как раньше. Этого не произойдет, если вы явно укажете столбцы, которые хотите выбрать.
Конечно, указание столбцов также иногда вызывает ошибки, если вы забываете добавлять новые столбцы в каждое предложение select. Если новый столбец не нужен каждый раз при выполнении запроса, может пройти некоторое время, прежде чем ошибка будет замечена.
Это нормально, когда ты делаешь exists(select * ...), потому что он никогда не расширяется. В противном случае это действительно полезно только при изучении таблиц с временными параметрами выбора или если у вас есть CTE, определенный выше, и вы хотите, чтобы каждый столбец не вводил их все снова.
Я действительно заметил странное поведение, когда использовал select * в представлениях в SQL Server 2005.
Выполните следующий запрос, и вы поймете, что я имею в виду.
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[starTest]') AND type in (N'U'))
DROP TABLE [dbo].[starTest]
CREATE TABLE [dbo].[starTest](
[id] [int] IDENTITY(1,1) NOT NULL,
[A] [varchar](50) NULL,
[B] [varchar](50) NULL,
[C] [varchar](50) NULL
) ON [PRIMARY]
GO
insert into dbo.starTest
select 'a1','b1','c1'
union all select 'a2','b2','c2'
union all select 'a3','b3','c3'
go
IF EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vStartest]'))
DROP VIEW [dbo].[vStartest]
go
create view dbo.vStartest as
select * from dbo.starTest
go
go
IF EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vExplicittest]'))
DROP VIEW [dbo].[vExplicittest]
go
create view dbo.[vExplicittest] as
select a,b,c from dbo.starTest
go
select a,b,c from dbo.vStartest
select a,b,c from dbo.vExplicitTest
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[starTest]') AND type in (N'U'))
DROP TABLE [dbo].[starTest]
CREATE TABLE [dbo].[starTest](
[id] [int] IDENTITY(1,1) NOT NULL,
[A] [varchar](50) NULL,
[B] [varchar](50) NULL,
[D] [varchar](50) NULL,
[C] [varchar](50) NULL
) ON [PRIMARY]
GO
insert into dbo.starTest
select 'a1','b1','d1','c1'
union all select 'a2','b2','d2','c2'
union all select 'a3','b3','d3','c3'
select a,b,c from dbo.vStartest
select a,b,c from dbo.vExplicittest
Сравните результаты двух последних операторов выбора. Я считаю, что вы увидите результат того, что Выбирать * ссылается на столбцы по индексу, а не по имени.
Если вы перестроите представление, он снова будет работать нормально.
РЕДАКТИРОВАТЬ
Я добавил отдельный вопрос, * Интересное поведение «выбрать * из таблицы» против «выбрать colA, colB и т. д. Из таблицы» в SQL Server 2005 *, чтобы более подробно изучить это поведение.
Это делает ваш код более неоднозначным и сложным в обслуживании; потому что вы добавляете в домен лишние неиспользуемые данные, и неясно, что вы намеревались, а какие нет. (Это также предполагает, что вы можете не знать или не заботиться.)
Чтобы напрямую ответить на ваш вопрос: не используйте «SELECT *», если он делает ваш код более уязвимым для изменений в базовых таблицах. Ваш код должен сломаться только тогда, когда в таблицу вносятся изменения, которые напрямую влияют на требования вашей программы.
Ваше приложение должно использовать уровень абстракции, который обеспечивает реляционный доступ.
Я понимаю, к чему вы идете в отношении преждевременной оптимизации, но на самом деле это только до конца. Намерение состоит в том, чтобы избежать оптимизации ненужный в начале. Ваши таблицы не проиндексированы? Вы бы использовали nvarchar (4000) для хранения почтового индекса?
Как отмечали другие, есть и другие положительные стороны для указания каждого столбца, который вы собираетесь использовать в запросе (например, ремонтопригодность).
Суть предложения не проводить преждевременную оптимизацию состоит в том, чтобы использовать простой и понятный код, а потом использует профилировщик, чтобы указать на горячие точки, которые затем можно оптимизировать для повышения эффективности.
Когда вы используете select *, вы делаете невозможным профилирование, поэтому вы не пишете ясный и простой код и идете вразрез с духом цитаты. select * - это анти-паттерн.
Так что выбор столбцов - это не преждевременная оптимизация. Кое-что не в моей голове ....
Следствие - использование select * ...
Написать правильный SQL так же просто, как написать Select *. Итак, настоящий ленивый человек пишет правильный SQL, потому что он не хочет возвращаться к коду и пытаться вспомнить, что они делали, когда они это делали. Они не хотят объяснять администраторам баз данных каждый бит кода. Они не хотят объяснять своим клиентам, почему приложение работает как собака.
В вашем первом разделе пункт 5 должен гласить: «select * дает вам все столбцы из всех таблиц в объединении». Во втором разделе пункты №2 и №5 не обязательно верны, и их не следует указывать в качестве причин не использовать «select *».
@uglysmurf - спасибо за исправление, но что касается 2 и 5 - хотя они не обязательно могут быть верными для всех баз данных / dba во всех случаях, я считаю, что они важны и действительны для большинства случаев, и оставлю их. Использование 'select *' никогда не облегчало работу dba.
Классный пост! В качестве примечания, я считаю, что единственное место, где SELECT * действительно действителен, - это внутри подзапросов EXISTS () (где он в любом случае оптимизирован для SELECT 1) и в коде отладки внутри хранимых процедур, который обычно никогда не запускается, если не передан специальный параметр.
Я бы сказал, что № 3 (хрупкий код) на самом деле не соответствует действительности. В зависимости от реализации Select * может сделать его МЕНЬШЕ хрупким, но я не понимаю, как это может быть еще хуже.
@JohnFx, я думаю, вы определяете хрупкость по-другому. Хрупкость обычно определяется как «легко ломается». Наличие неизвестных или труднодоступных зависимостей, потому что каждый фрагмент кода будет использовать разные столбцы, означает, что я не могу легко изменить что-либо на уровне данных без полной регрессии ... что кажется хрупким.
Я, JohnFX, указывает, что, хотя «select *» может затруднить отслеживание поломки, на самом деле это (немного) снижает вероятность ее возникновения. Следовательно, менее хрупкий. Я согласен, но (как и JohnFX, я подозреваю) не думаю, что это действительно противоречит всем другим действительным аргументам против select * почти во всех ситуациях.
@mavnn, w.r.t. Боюсь, что это проблема семантики, связанная с моим выбором слова «хрупкий». Мое последнее слово - сказать, что в любом случае это не имеет большого значения. Единственный сценарий - переименование / удаление столбцов. Вы просто перемещаете разрыв с момента выполнения sql (явным) по сравнению с разрывом, когда результаты потребляются. Способ использования результата запроса может различаться, и код может или не может автоматически завершиться ошибкой, но механизм выполнения sql определенно завершится с ошибкой с недопустимым sql. Так вам помог select *? Явный отказ IMO ближе к БД для проблемы с БД лучше. Спасибо
А как насчет SELECT * FROM (SELECT TABLE1.id, TABLE1.count UNION SELECT TABLE2.id, TABLE2.count) AS AGG, где AGG.id и AGG.count используются позже? Если я не укажу эти столбцы вручную, отключит ли база данных оптимизацию для этого (части) запроса?
@izogfif Я не уверен в оптимизации, но все же предпочитаю явные списки выбора из соображений обслуживания. Я часто наблюдаю, как другие разработчики добавляют во внутренние запросы агрегации и выбирают *, скрывает это (или, по крайней мере, затрудняет обнаружение изменений). С другой стороны, я часто предлагаю другим разработчикам выполнять соединения на самом внешнем уровне, чтобы упростить агрегирование, и выбор * больше не актуален.
Окольными путями вы нарушаете правило модульности об использовании по возможности строгий набор текста. Явный почти всегда лучше.
Даже если вам теперь нужен каждый столбец в таблице, можно добавить больше позже, который будет опускаться каждый раз, когда вы запускаете запрос и может повредить производительность. Это вредит производительности, потому что
Когда вам явно НЕОБХОДИМ каждый столбец в таблице, а не каждый столбец в таблице, КОТОРЫЙ СУЩЕСТВОВАЛ В ВРЕМЯ, КОТОРОЕ ВЫ НАПИСИЛИ ЗАПРОС. Например, если вы пишете приложение для управления БД, которое должно отображать все содержимое таблицы (каким бы оно ни было), вы можете использовать этот подход.
В другой раз использовать SELECT * было бы, когда вы выполняете тестовые запросы с помощью клиента db.
Это кажется странным исключением, учитывая контекст вопроса. В чем преимущество этого для тестовых запросов, кроме экономии времени на вводе текста?
Также SELECT * FROM (SELECT a, b, c FROM table) в порядке.
Я не использую SELECT * просто потому, что приятно видеть и знать, какие поля я получаю.
Как правило, неправильно использовать 'select *' внутри представлений, потому что вы будете вынуждены перекомпилировать представление в случае изменения столбца таблицы. При изменении столбцов базовой таблицы представления вы получите ошибку для несуществующих столбцов, пока вы не вернетесь и не перекомпилируете.
Просто чтобы добавить одну вещь, о которой никто не упомянул. Select * возвращает все столбцы, кто-то может добавить столбец позже, который вы не обязательно хотите, чтобы пользователи могли видеть, например, кто последний раз обновлял данные или временную метку, или отмечает, что только менеджеры должны видеть не всех пользователей и т. д.
Кроме того, при добавлении столбца следует проанализировать влияние на существующий код и принять во внимание необходимость изменений на основе того, какая информация хранится в столбце. При использовании select * этот обзор часто пропускают, потому что разработчик предполагает, что ничего не сломается. И на самом деле может показаться, что ничего явно не сломано, но теперь запросы могут начать возвращать не то. То, что ничего явно не ломается, не означает, что в запросы не должно быть изменений.
Когда вы определяете столбцы, вы также привязываете себя к определенному набору столбцов и делаете себя менее гибкими, заставляя Фейерштейна перекатываться, ну где бы он ни был. Просто мысль.
Я понятия не имею, кто такой Фейерштейн. Пытался погуглить и нашел психолога, телевизионного персонажа и блогера, так что лучшее, что я мог придумать, было шуткой.
Автор книг О'Рейли по PL / SQL. Попробуйте поискать в Google "feuerstein sql" вместо просто "feuerstein".
потому что "select *" тратит память, когда вам не нужны все поля. Но для sql server их производительность такая же.
ВЫБРАТЬ * не всегда зло. По крайней мере, на мой взгляд. Я довольно часто использую его для динамических запросов, возвращающих всю таблицу плюс некоторые вычисленные поля.
Например, я хочу вычислить географическую геометрию из «нормальной» таблицы, то есть таблицы без какого-либо поля геометрии, но с полями, содержащими координаты. Я использую postgresql и его пространственное расширение postgis. Но этот принцип применим и во многих других случаях.
Пример:
таблица мест с координатами, хранящимися в полях, помеченных x, y, z:
СОЗДАТЬ ТАБЛИЦУ мест (place_id integer, x numeric (10, 3), y numeric (10, 3), z numeric (10, 3), description varchar);
давайте добавим в него несколько примеров значений:
ВСТАВИТЬ места (place_id, x, y, z, description)
ЦЕННОСТИ
(1, 2.295, 48.863, 64, Париж, площадь Этуаль),
(2, 2.945, 48.858, 40, Париж, Эйфелева башня),
(3, 0.373, 43.958, 90, «Презерватив, собор Сен-Пьер»);
Я хочу иметь возможность отображать содержимое этой таблицы с помощью какого-нибудь клиента ГИС. Обычный способ - добавить поле геометрии в таблицу и построить геометрию на основе координат. Но я бы предпочел получить динамический запрос: таким образом, когда я меняю координаты (исправления, повышенная точность и т. д.), Отображаемые объекты фактически перемещаются динамически. Итак, вот запрос с ВЫБРАТЬ *:
СОЗДАТЬ ИЛИ ЗАМЕНИТЬ ПРОСМОТР places_points AS
ВЫБРАТЬ *,
GeomFromewkt ('SRID = 4326; POINT (' || x || '' || y || '' || z || ')')
С мест;
Обратитесь к postgis, чтобы узнать об использовании функции GeomFromewkt ().
Вот результат:
ВЫБРАТЬ * FROM places_points;
place_id | x | y | z | description | geomfromewkt
----------+-------+--------+--------+------------------------------+--------------------------------------------------------------------
1 | 2.295 | 48.863 | 64.000 | Paris, Place de l'Étoile | 01010000A0E61000005C8FC2F5285C02405839B4C8766E48400000000000005040
2 | 2.945 | 48.858 | 40.000 | Paris, Tour Eiffel | 01010000A0E61000008FC2F5285C8F0740E7FBA9F1D26D48400000000000004440
3 | 0.373 | 43.958 | 90.000 | Condom, Cathédrale St-Pierre | 01010000A0E6100000AC1C5A643BDFD73FB4C876BE9FFA45400000000000805640
(3 lignes)
Крайний правый столбец теперь может использоваться любой программой ГИС для правильного отображения точек.
Я бы хотел, чтобы определение VIEW могло быть сохранено «как есть» с *, но это не так: postgresql хранит его внутри так:
ВЫБЕРИТЕ place.place_id, places.x, places.y, places.z, places.description, geomfromewkt (((((('SRID = 4326; POINT (' :: text || places.x) || '': : text) || places.y) || '' :: text) || places.z) || ')' :: text) AS geomfromewkt FROM places;
Вы никогда не должны писать код, который в любом случае зависит от количества возвращаемых столбцов.