#sql #sql-server #tsql #dynamic-sql #dynamic-tables
Вопрос:
Я новичок в SQL Server (версия 15), и мне нужна помощь в написании SQL.
У меня есть таблица Invoice_Header, в которой в качестве ключей указаны Invoice_ID и Invoice_Dt:
Идентификатор счета-фактуры | Дата выставления счета |
---|---|
1 | 1/1/2021 |
2 | 1/1/2021 |
3 | 2/1/2021 |
4 | 3/1/2021 |
Существует таблица Invoice_Detail с ключами Invoice_ID, Invoice_Dt, Invoice_Line в качестве ключей и полями ID и Table_Name.
Идентификатор счета-фактуры | Дата выставления счета | Строка счета-фактуры | ID | Имя таблицы |
---|---|---|---|---|
1 | 1/1/2021 | 1 | 2 | Food_Table |
1 | 1/1/2021 | 2 | 1 | Питьевая таблица |
2 | 1/1/2021 | 1 | 1 | Food_Table |
2 | 1/1/2021 | 2 | 3 | Питьевая таблица |
3 | 2/1/2021 | 1 | 3 | Food_Table |
4 | 3/1/2021 | 1 | 2 | Питьевая таблица |
Food_Table содержит эти 2 поля
ID | Имя продукта питания |
---|---|
1 | Гамбургер |
2 | хот-дог |
3 | Макароны |
В таблице Drink_Table есть эти 2 поля
ID | Имя напитка |
---|---|
1 | Сода |
2 | Молоко |
3 | Вода |
Я хочу извлечь данные с помощью этих столбцов:
Счет-фактура | Счет-фактура | Строка счета-фактуры | Имя заказа |
---|---|---|---|
1 | 1/1/2021 | 1 | хот-дог |
1 | 1/1/2021 | 2 | Сода |
2 | 1/1/2021 | 1 | Гамбургер |
2 | 1/1/2021 | 2 | Вода |
3 | 2/1/2021 | 1 | Макароны |
4 | 3/1/2021 | 1 | Молоко |
Таким образом, для каждой строки счета в таблице Invoice_Detail у нее есть идентификатор и имя таблицы, соответствующие таблице Food_Table или Drink_Table. Независимо от того, какой идентификатор указан в этих двух соответствующих таблицах, мне нужно получить имя Food_Name или имя Drink_Name в качестве имени заказа. Обратите внимание, что пример содержит только эти две таблицы. Однако в нем может быть несколько таблиц. Таким образом, SQL должен был бы перейти к имени таблицы, которое находится в таблице invoice_detail. Поэтому невозможно выполнить жесткое кодирование Food_Table и Drink_Table.
Я не знаю, как написать такой SQL, и был бы очень признателен, если бы кто-нибудь показал мне, как его написать.
Спасибо
Комментарии:
1. Предпринимали ли вы какие-либо попытки? Если нет, то почему вы провели исследование, чтобы попытаться решить эту проблему? Возможно ли исправить конструкцию? Как человек, который новичок в SQL, этот дизайн-очень плохой выбор. Создание базы данных, которая требует динамического SQL, является недостатком дизайна даже для тех, кто очень хорошо разбирается в языке или как в SQL, так и в динамическом SQL.
2. Дизайн базы данных-это ваша проблема здесь. У вас не должно быть отдельных столов «Еда» и «Напитки». Вместо этого у вас должна быть одна таблица для всех товаров, подлежащих оплате. Если вам нужно классифицировать отдельные товары, добавьте поле «тип товара», которое будет содержать значения «Еда» и «Напитки».
3. В зависимости от того, насколько обширной будет ваша сборка по этому пути, вы все равно можете довольно легко избежать динамического sql. Вы можете написать один запрос, используя условные соединения для каждой таблицы, и фактически выполнить соединения только с таблицей, которая соответствует значению
Invoice_Detail.Table_Name
. Или вы можете сделать что-то вроде создания хранимой процедуры и использовать инструкции потока, напримерIF Table_Name = 'Food_Table' BEGIN SELECT YourColumns FROM YourTables LEFT JOIN Food_Table ON YourConditions; END;
, для условного выполнения соответствующего запроса.4. @Ларну. Я не создавал базу данных. Я просто пользователь, пытающийся получить данные для целей отчетности.
5. Я действительно предложил бы поговорить с дизайнерами о том, как исправить дизайн, тогда, если честно, @user3241884 . В противном случае вам придется отказаться от динамического подхода SQL, он вряд ли будет эффективным, и если вы действительно не знаете, что делаете, вы будете внедрять множество уязвимостей безопасности в свою систему.
Ответ №1:
Если бы было всего несколько таблиц, я бы не стал пытаться делать что-то «умное» с динамическим SQL или слишком увлекаться. Самый простой способ сделать это-обратиться LEFT OUTER JOIN
к таблицам и включить значение Table_Name
в условие соединения, а затем использовать COALESCE
функцию для возврата первого ненулевого значения (и в любом случае должно быть только одно).
Запрос просто такой:
SELECT i.Invoice_ID AS [Invoice], i.Invoice_Date AS [Invoice_Dt],
i.Invoice_Line, COALESCE(d.Drink_Name, f.Food_Name) AS [Order_Name]
FROM Invoice_Detail AS i
LEFT OUTER JOIN Drink_Table d ON d.ID = i.ID AND i.Table_Name ='Drink_Table'
LEFT OUTER JOIN Food_Table f ON f.ID = i.ID AND i.Table_Name = 'Food_Table'
Если вы хотите попробовать это, я создал простой скрипт, который объявляет табличные переменные и заполняет их вашими образцами данных. Вы можете запустить сценарий или изменить его, чтобы опробовать другие результаты. Если у вас больше столов, вам просто нужно больше LEFT OUTER JOIN
s… это, вероятно, самый простой способ справиться с этой ситуацией.
DECLARE @Invoice_Header AS TABLE (
[Invoice_ID] INT NOT NULL,
[Invoice_Date] DATE NOT NULL
);
INSERT INTO @Invoice_Header(Invoice_ID,Invoice_Date)
VALUES (1, '1/1/2021'),(2, '1/1/2021'),(3,'2/1/2021'),(4,'3/1/2021');
DECLARE @Invoice_Detail AS TABLE (
[Invoice_ID] INT NOT NULL,
[Invoice_Date] DATE NOT NULL,
[Invoice_Line] INT NOT NULL,
[ID] INT NOT NULL,
[Table_Name] VARCHAR(20) NOT NULL
);
INSERT INTO @Invoice_Detail(Invoice_ID,Invoice_Date,Invoice_Line,ID,Table_Name)
VALUES (1, '1/1/2021',1,2,'Food_Table'),(1,'1/1/2021',2,1,'Drink_Table'),
(2,'1/1/2021',1,1,'Food_Table'),(2,'1/1/2021',2,3,'Drink_Table'),
(3,'2/1/2021',1,3,'Food_Table'),(4,'3/1/2021',1,2,'Drink_Table');
DECLARE @Food_Table AS TABLE(
[ID] INT NOT NULL,
[Food_Name] VARCHAR(20) NOT NULL
);
INSERT INTO @Food_Table(ID,Food_Name)
VALUES (1,'Hamburger'),(2,'Hot Dog'),(3,'Pasta');
DECLARE @Drink_Table AS TABLE(
[ID] INT NOT NULL,
[Drink_Name] VARCHAR(20) NOT NULL
);
INSERT INTO @Drink_Table(ID,Drink_Name)
VALUES (1,'Soda'),(2,'Milk'),(3,'Water');
SELECT i.Invoice_ID AS [Invoice], i.Invoice_Date AS [Invoice_Dt],
i.Invoice_Line, COALESCE(d.Drink_Name, f.Food_Name) AS [Order_Name]
FROM @Invoice_Detail AS i
LEFT OUTER JOIN @Drink_Table d ON d.ID = i.ID AND i.Table_Name ='Drink_Table'
LEFT OUTER JOIN @Food_Table f ON f.ID = i.ID AND i.Table_Name = 'Food_Table'
Вы увидите ожидаемый результат:
Счет-фактура | Счет-фактура | Строка счета-фактуры | Имя ордена |
---|---|---|---|
1 | 2021-01-01 | 1 | хот-дог |
1 | 2021-01-01 | 2 | Сода |
2 | 2021-01-01 | 1 | Гамбургер |
2 | 2021-01-01 | 2 | Вода |
3 | 2021-02-01 | 1 | Макароны |
4 | 2021-03-01 | 1 | Молоко |
Комментарии:
1. Спасибо. Я привел только две таблицы в качестве примера для упрощения. Система, которую я использую (которая принадлежит третьей стороне), на самом деле имеет 37 таблиц и может расширяться. Я знаю и согласен со всеми, что дизайн плохой. С помощью двух таблиц я, безусловно, могу выполнить select sql или хранимую процедуру, как показано на рисунке, или использовать cte. Но все это требует жесткого кодирования/выбора таблиц. И если добавляются новые таблицы, то их придется модифицировать. Я думаю, что нет способа сделать это без жесткого кодирования или не прибегая к написанию кодов.
2. Поскольку в вашем примере имя столбца, который вы хотите отобразить, отличается в других таблицах, нет отличного способа сделать это автоматически. Я согласен, дизайн ужасен, но я также знаю, что, когда у вас есть сторонняя система, вы не можете сказать им, чтобы они исправили свои проблемы… Я просто хотел дать ответ вместо какого-нибудь язвительного комментария по поводу дизайна, когда у вас возник законный вопрос. Честно говоря, даже с 37 таблицами запрос не так уж плох, но, учитывая, что он может расти, вам придется быть готовым поддерживать запрос.