TSQL to insert a set of rows and dependent rows - sql-server

I have 2 tables:
Order (with a identity order id field)
OrderItems (with a foreign key to order id)
In a stored proc, I have a list of orders that I need to duplicate. Is there a good way to do this in a stored proc without a cursor?
Edit:
This is on SQL Server 2008.
A sample spec for the table might be:
CREATE TABLE Order (
OrderID INT IDENTITY(1,1),
CustomerName VARCHAR(100),
CONSTRAINT PK_Order PRIMARY KEY (OrderID)
)
CREATE TABLE OrderItem (
OrderID INT,
LineNumber INT,
Price money,
Notes VARCHAR(100),
CONSTRAINT PK_OrderItem PRIMARY KEY (OrderID, LineNumber),
CONSTRAINT FK_OrderItem_Order FOREIGN KEY (OrderID) REFERENCES Order(OrderID)
)
The stored proc is passed a customerName of 'fred', so its trying to clone all orders where CustomerName = 'fred'.
To give a more concrete example:
Fred happens to have 2 orders:
Order 1 has line numbers 1,2,3
Order 2 has line numbers 1,2,4,6.
If the next identity in the table was 123, then I would want to create:
Order 123 with lines 1,2,3
Order 124 with lines 1,2,4,6

On SQL Server 2008 you can use MERGE and the OUTPUT clause to get the mappings between the original and cloned id values from the insert into Orders then join onto that to clone the OrderItems.
DECLARE #IdMappings TABLE(
New_OrderId INT,
Old_OrderId INT)
;WITH SourceOrders AS
(
SELECT *
FROM Orders
WHERE CustomerName = 'fred'
)
MERGE Orders AS T
USING SourceOrders AS S
ON 0 = 1
WHEN NOT MATCHED THEN
INSERT (CustomerName )
VALUES (CustomerName )
OUTPUT inserted.OrderId,
S.OrderId INTO #IdMappings;
INSERT INTO OrderItems
SELECT New_OrderId,
LineNumber,
Price,
Notes
FROM OrderItems OI
JOIN #IdMappings IDM
ON IDM.Old_OrderId = OI.OrderID

Related

Select all Main table rows with detail table column constraints with GROUP BY

I've 2 tables tblMain and tblDetail on SQL Server that are linked with tblMain.id=tblDetail.OrderID for orders usage. I've not found exactly the same situation in StackOverflow.
Here below is the sample table design:
/* create and populate tblMain: */
CREATE TABLE tblMain (
ID int IDENTITY(1,1) NOT NULL,
DateOrder datetime NULL,
CONSTRAINT PK_tblMain PRIMARY KEY
(
ID ASC
)
)
GO
INSERT INTO tblMain (DateOrder) VALUES('2021-05-20T12:12:10');
INSERT INTO tblMain (DateOrder) VALUES('2021-05-21T09:13:13');
INSERT INTO tblMain (DateOrder) VALUES('2021-05-22T21:30:28');
GO
/* create and populate tblDetail: */
CREATE TABLE tblDetail (
ID int IDENTITY(1,1) NOT NULL,
OrderID int NULL,
Gencod VARCHAR(255),
Quantity float,
Price float,
CONSTRAINT PK_tblDetail PRIMARY KEY
(
ID ASC
)
)
GO
INSERT INTO tblDetail (OrderID, Gencod, Quantity, Price) VALUES(1, '1234567890123', 8, 12.30);
INSERT INTO tblDetail (OrderID, Gencod, Quantity, Price) VALUES(1, '5825867890321', 2, 2.88);
INSERT INTO tblDetail (OrderID, Gencod, Quantity, Price) VALUES(3, '7788997890333', 1, 1.77);
INSERT INTO tblDetail (OrderID, Gencod, Quantity, Price) VALUES(3, '9882254656215', 3, 5.66);
INSERT INTO tblDetail (OrderID, Gencod, Quantity, Price) VALUES(3, '9665464654654', 4, 10.64);
GO
Here is my SELECT with grouping:
SELECT tblMain.id,SUM(tblDetail.Quantity*tblDetail.Price) AS TotalPrice
FROM tblMain LEFT JOIN tblDetail ON tblMain.id=tblDetail.orderid
WHERE (tblDetail.Quantity<>0) GROUP BY tblMain.id;
GO
This gives:
The wished output:
We see that id=2 is not shown even with LEFT JOIN, as there is no records with OrderID=2 in tblDetail.
How to design a new query to show tblMain.id = 2? Mean while I must keep WHERE (tblDetail.Quantity<>0) constraints. Many thanks.
EDIT:
The above query serves as CTE (Common Table Expression) for a main query that takes into account payments table tblPayments again.
After testing, both solutions work.
In my case, the main table has 15K records, while detail table has some millions. With (tblDetail.Quantity<>0 OR tblDetail.Quantity IS NULL) AND tblDetail.IsActive=1 added on JOIN ON clause it takes 37s to run, while the first solution of #pwilcox, the condition being added on the where clause, it ends up on 29s. So a gain of time of 20%.
tblDetail.IsActive column permits me ignore detail rows that is temporarily ignored by setting it to false.
So the for me it's ( #pwilcox's answer).
where (tblDetail.quantity <> 0 or tblDetail.quantity is null)
Change
WHERE (tblDetail.Quantity<>0)
to
where (tblDetail.quantity <> 0 or tblDetail.quantity is null)
as the former will omit id = 2 because the corresponding quantity would be null in a left join.
And as HABO mentions, you can also make the condition a part of your join logic as opposed to your where statement, avoiding the need for the 'or' condition.
select m.id,
totalPrice = sum(d.quantity * d.price)
from tblMain m
left join tblDetail d
on m.id = d.orderid
and d.quantity <> 0
group by m.id;

Cross apply vs CTE in Sql Server

I have two tables
the first table is named: tblprovince
create table (
provinceid int not null primary key (1,1) ,
provinceNme nvarchar(max),
description nvarchar(max))
the second table is named tblcity:
create table tblcity(
cityid int identity (1,1),
CityName nvarchar(max),
population int,
provinceid int foreign key references tblprovince(provinceid)
);
I need to list all provinces that have at least two large cities. A large city is defined as having a population of at least one million residents. The query must return the following columns:
tblProvince.ProvinceId
tblProvince.ProvinceName
a derived column named LargeCityCount that presents the total count of large cities for the province
 
select p.provinceId, p.provincename, citysummary.LargeCityCount
from tblprovince p
cross apply (
select count(*) as LargeCityCount from tblcity c
where c.population >= 1000000 and c.provinceid=p.provinceid
) citysummary
where citysummary.LargeCityCount
Is this query correct?
Are there other methods that allow me to achieve my goal?
SELECT tp.provinceid, tp.provinceNme, COUNT(tc.cityid) AS largecitycount
FROM tblprovince tp INNER JOIN
tblcity tc ON tc.provinceid=tp.provinceid
WHERE tc.population>=1000000
GROUP BY tp.provinceid, tp.provinceNme
HAVING COUNT(tc.cityid)>1

TSQL Reference Table with redundant keys

I'm currently working on a stored procedure on SQL Server 2016. In my Database I have a table structure and need to add another table, which references to the same table as an existing one.
Thus, I have 2 times a 1:1 relation to the same table.
The occuring problem is, I reference the same keys from 2 different origin tables twice in the same target table.
Target table:
FK_Tables | Text
----------------
1 | Table One Text Id: 1
1 | Table Two Text Id: 1 // The error: Same FK_Tables 2 times
Table One:
ID | OtherField
---------
1 | 42
Table Two:
ID | CoolField
---------
1 | 22
Table One and Table Two are currently referencing to the table Reference Table.
Do you know how I can solve this problem, of the same ID twice?
Thanks!!
You need to add a column for each table you're referencing, otherwise you wouldn't know where the ID is coming from if they were all inserted into the same field. Something like this:
/*
CREATE TEST TABLES
*/
DROP TABLE IF EXISTS tbOne;
CREATE TABLE tbOne ( ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
, TXT VARCHAR(10)
);
DROP TABLE IF EXISTS tbTwo;
CREATE TABLE tbTwo ( ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
, TXT VARCHAR(10)
);
DROP TABLE IF EXISTS Target;
CREATE TABLE Target ( ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
, FKTB1 INT
, FKTB2 INT
, TXT VARCHAR(100)
);
-- 1st FK tbOne
ALTER TABLE Target ADD CONSTRAINT FK_One FOREIGN KEY (FKTB1) REFERENCES tbOne (ID);
--2nd FK tbTwo
ALTER TABLE Target ADD CONSTRAINT FK_Two FOREIGN KEY (FKTB2) REFERENCES tbTwo (ID);
-- Populate test tables
INSERT INTO tbOne (TXT)
SELECT TOP 100 LEFT(text, 10)
FROM SYS.messages
INSERT INTO tbTwo (TXT)
SELECT TOP 100 LEFT(text, 10)
FROM SYS.messages
INSERT INTO [Target] (FKTB1, FKTB2, TXT)
SELECT 1, 1, 'Test - constraint'
-- Check result set
SELECT *
FROM tbTwo
SELECT *
FROM tbOne
SELECT *
FROM [Target] T
INNER JOIN tbOne TB1
ON T.FKTB1 = TB1.ID
INNER JOIN tbTwo TB2
ON T.FKTB2 = TB2.ID

Insert column to make the primary key in T-SQL

I have a huge table (more than a million records) right now that currently contains the following 2 columns: CustomerName and AmountBilled
I want to add another column, which we can call it PurchaseID, so that CustomerName + PurchaseID becomes a unique combination and hence I can create a primary key.
Say for example, my original data looks like this:
CustomerName AmountBilled
-------------------------
Bill $2
Bill $3.5
Joe $5
I want my new table to look like this:
Bill 1 $2
Bill 2 $3.5
Joe 1 $5
With the second column calculated in SQL.
What is the correct SQL statement for this?
alter table TableName
add PurchaseID int NULL
GO
;with cte as (
select *, rn = row_number() over (partition by CustomerName order by ##spid)
from TableName
)
update cte set PurchaseID = rn
GO
alter table TableName
alter column PurchaseID int not NULL
GO

get max from table where sum required

Suppose I have a table with following data:
gameId difficultyLevel numberOfQuestions
--------------------------------------------
1 1 2
1 2 2
1 3 1
In this example the game is configured for 5 questions, but I'm looking for a SQL statement that will work for n number of questions.
What I need is a SQL statement that given a question, displayOrder will return the current difficulty level of question. For example - given a displayOrder of 3, with the table data above, will return 2.
Can anyone advise how the query should look like?
I'd recommend a game table with a 1:m relationship with a question table.
You shouldn't repeat columns in a table - it violates first normal form.
Something like this:
create table if not exists game
(
game_id bigint not null auto_increment,
name varchar(64),
description varchar(64),
primary key (game_id)
);
create table if not exists question
(
question_id bigint not null auto_increment,
text varchar(64),
difficulty int default 1,
game_id bigint,
primary key (question_id) ,
foreign key game_id references game(game_id)
);
select
game.game_id, name, description, question_id, text, difficulty
game left join question
on game.game_id = question.game_id
order by question_id;
things might be easier for you if you change your design as duffymo suggests, but if you must do it that way, here's a query that should do the trick.
SELECT MIN(difficultyLevel) as difficltyLevel
FROM
(
SELECT difficltyLevel, (SELECT sum(numberOfQuestions) FROM yourtable sub WHERE sub.difficultyLevel <= yt.difficultyLevel ) AS questionTotal
FROM yourTable yt
) AS innerSQL
WHERE innerSQL.questionTotal >= #displayOrder

Resources