Запрос UNION без таблиц в MS Access (Jet/ACE)

Это работает как ожидалось:

SELECT "Mike" AS FName

Это приводит к ошибке "Ввод запроса должен содержать хотя бы одну таблицу или запрос":

SELECT "Mike" AS FName
UNION ALL
SELECT "John" AS FName

Это просто причуды / ограничения ядра базы данных Jet/ACE или я что-то упустил?

6 ответов

Решение

Вы ничего не упустили. Доступ к базе данных двигателя позволит одну строку SELECT без FROM источник данных. Но если вы хотите UNION или же UNION ALL несколько строк, вы должны включить FROM... даже если вы не ссылаетесь ни на одно поле из этого источника данных.

Я создал таблицу с одной строкой и добавил проверочное ограничение, чтобы гарантировать, что в ней всегда будет одна и только одна строка.

Public Sub CreateDualTable()
    Dim strSql As String
    strSql = "CREATE TABLE Dual (id COUNTER CONSTRAINT pkey PRIMARY KEY);"
    Debug.Print strSql
    CurrentProject.Connection.Execute strSql
    strSql = "INSERT INTO Dual (id) VALUES (1);"
    Debug.Print strSql
    CurrentProject.Connection.Execute strSql

    strSql = "ALTER TABLE Dual" & vbNewLine & _
        vbTab & "ADD CONSTRAINT there_can_be_only_one" & vbNewLine & _
        vbTab & "CHECK (" & vbNewLine & _
        vbTab & vbTab & "(SELECT Count(*) FROM Dual) = 1" & vbNewLine & _
        vbTab & vbTab & ");"
    Debug.Print strSql
    CurrentProject.Connection.Execute strSql
End Sub

Тот Dual таблица полезна для запросов, таких как этот:

SELECT "foo" AS my_text
FROM Dual
UNION ALL
SELECT "bar"
FROM Dual;

Другой подход, который я видел, это использовать SELECT заявление с TOP 1 или WHERE предложение, которое ограничивает результирующий набор одной строкой.

Примечание. Проверочные ограничения были добавлены в Jet 4 и доступны только для операторов, выполняемых из ADO. CurrentProject.Connection.Execute strSql работает потому что CurrentProject.Connection является объектом ADO. Если вы попытаетесь выполнить тот же оператор с DAO (т.е. CurrentDb.Execute или из конструктора запросов Access), вы получите синтаксическую ошибку, потому что DAO не может создавать ограничения проверки.

Если у вас есть доступ к некоторым системным таблицам, вы можете эмулировать двойную таблицу следующим образом:

(SELECT COUNT(*) FROM MSysResources) AS DUAL

К сожалению, я не знаю ни о каких системных таблицах, которые...

  • всегда доступны для чтения (объекты MSysObject могут быть недоступны для каждого соединения)
  • содержат ровно одну запись, например Oracle DUAL или DB2 SYSIBM.DUAL

Так что вы бы написали:

SELECT 'Mike' AS FName
FROM (SELECT COUNT(*) FROM MSysResources) AS DUAL
UNION ALL
SELECT 'John' AS FName
FROM (SELECT COUNT(*) FROM MSysResources) AS DUAL

Это то, что реализуется как синтаксический элемент в jOOQ, например.

Если у вас ограничен доступ только для чтения к базе данных (т.е. вы не можете создавать новые таблицы или обращаться к системным ресурсам), это может сработать:

SELECT "Mike" AS FName
FROM (SELECT COUNT(*) FROM anyTable WHERE 1=0) AS dual
  1. anyTable - первая найденная вами таблица пользователя (я с трудом могу представить реальную базу данных без таблицы пользователей!).

  2. ГДЕ 1=0 должен быстро возвращать счет 0 даже на большом столе (надеюсь, что реактивный двигатель достаточно умен, чтобы распознавать такие тривиальные условия).

Вот гораздо более простой способ сделать это:

SELECT 'foo', 'boo', 'hoo' from TableWith1Row
union
SELECT 'foo1', 'boo1', 'hoo1' from TableWith1Row

Важное замечание: TableWith1Row может ЛИБО быть таблицей с буквально 1 записью (которую вы в любом случае игнорируете) ИЛИ это может быть таблица с любым количеством строк (должно быть по крайней мере 1 строка), но вы добавляете предложение WHERE для обеспечения 1 строки. Это немного непросто, но это быстрый способ сделать это без создания дополнительных таблиц.

Если кто-то хочет использовать метод Top 1, он будет выглядеть так:

SELECT first_name AS FName
FROM tblname
UNION ALL
SELECT "Mike" as Fname
FROM (Select Top 1 Count(*) FROM tblsometable);

Псевдоним для поля должен быть одинаковым на обеих сторонах объединения, в данном случае "FName".

Введите любое имя таблицы (вам не нужно выбирать столбец из нее).

Этот запрос дает мне 3 финансовых года, которые мне нужны для выпадающего списка. Финансовый год начинается в июле.

SELECT IIf(Month(Now())>6,Year(Now())-1,Year(Now())-2) AS FY
FROM table
UNION 
SELECT IIf(Month(Now())>6,Year(Now()),Year(Now())-1) AS FY
FROM table
UNION 
SELECT IIf(Month(Now())>6,Year(Now())+1,Year(Now())) AS FY
FROM table;

Вот запрос, который действительно работает при доступе. Я это тестировал. Он использует подзапрос как "двойной": (SELECT count(*) as cnt from MSysObjects).

select 1 as a from (SELECT count(*) as cnt from MSysObjects) UNION ALL
select 2 as a from (SELECT count(*) as cnt from MSysObjects) UNION ALL
select 3 as a from (SELECT count(*) as cnt from MSysObjects)
Другие вопросы по тегам