I've been trying the following suggestions from this post, but this does not apply for my case or at least I am not capable of adapting the query to my needs.
I have three tables: one stands for documents header, one stands for documents lines and one stands for item's information (Code, Description etc).
I would like to extract all the documents number that have the same value (this is the information from the documents header table), the same items (the code of the item) and the same quantity (from the lines of the documents tables). How can I extract this information? Thanks
The tables are -
DocHeader DocLines Items
ID fDocID ID
Code fItemID Code
Date Quantity Description
---- -------- -----------
TotalValue etc etc
Later edit
Output should like something like:
DocCode ItemCode Quantity TotalValue
01 001 5 1000
01 002 5 1000
01 003 4 1000
02 001 5 1000
02 002 5 1000
02 003 4 1000
DDL
create table DocHeader
(
Id bigint not null identity(1,1) primary key clustered
, Code nvarchar(32) not null
, [Date] datetime not null
)
go
create table Items
(
Id bigint not null identity(1,1) primary key clustered
, Code nvarchar(32) not null
, [Description] nvarchar(256)
, UnitPrice money not null
)
go
create table DocLines
(
Id bigint not null identity(1,1) primary key clustered
,fDocId bigint not null constraint fk_DocLines_fDocId foreign key references DocHeader(Id)
,fItemId bigint not null constraint fk_DocLines_fDocId foreign key references Items(Id)
,Quantity int not null
)
go
create view vDocHeader as
select dh.*
, x.TotalValue
from DocHeader
left outer join
(
select dl.fDocId
, sum(dl.Quantity * i.UnitPrice) TotalValue
from DocLines dl
inner join Items i
on i.Id = dl.fItemId
group by dl.fDocId
) x
on x.fDocId = dh.Id
Why not simple grouping?
SELECT dh.Code AS DocCode, i.Code AS ItemCode, Quantity, SUM(dl.Quantity * i.UnitPrice) AS TotalValue
FROM DocHeader dh
LEFT JOIN DocLines dl ON dl.fDocId=dh.id
JOIN Items i ON i.Id = dl.fItemId
GROUP BY dh.Code,i.Code,Quantity
Related
I have the following problem where I am kindly asking for your help when joining two tables in SQL Server 2016 (v13).
I have 2 tables, Revenues and Cashins.
Revenues:
RevenueID
ProductID
InvoiceNo
Amount
123
456
987
1000
234
456
987
1000
Cashins:
CashinID
ProductID
InoviceNo
Amount
ABC
456
987
1000
CDE
456
987
1000
The goal is to match cashins automatically to revenues (but only once!).
Both tables have their unique-ids but the columns used to join these tables are
ProductID
InvoiceNo
Amount
For entries with only one row in each table with those criteria, everything works fine.
Sometimes though, there are several rows that have the same value within these columns (as above) but with a unique ID (this is no error, but the way it is supposed to be).
The problem with it is, that while joining it results in a cartesian product.
To recreate the tables, here the statements:
DROP TABLE IF EXISTS Revenues
GO
CREATE TABLE Revenues
(
RevenueID [nvarchar](10) NULL,
ProductID [nvarchar](10) NULL,
InvoiceNo [nvarchar](10) NULL,
Amount money NULL
)
GO
DROP TABLE IF EXISTS CashIns
GO
CREATE TABLE CashIns
(
CashinID [nvarchar](10) NULL,
ProductID [nvarchar](10) NULL,
InvoiceNo [nvarchar](10) NULL,
Amount money NULL
)
GO
INSERT INTO [Revenues] VALUES ('123', '456', '987', 1000)
INSERT INTO [Revenues] VALUES ('234', '456', '987', 1000)
INSERT INTO [CashIns] VALUES ('ABC', '456', '987', 1000)
INSERT INTO [CashIns] VALUES ('BCD', '456', '987', 1000)
Desired output:
RevenueID
ProductID
InvoiceNo
Amount
CashinID
123
456
987
1000
ABC
234
456
987
1000
CDE
SELECT
R.RevenueID,
R.ProductID,
R.InvoiceNo,
R.Amount,
C.CashinID,
FROM
[Revenues] R
LEFT JOIN
[CashIns] C ON R.ProductID = C.ProductID
AND R.InvoiceNo = C.InvoiceNo
AND R.Amount = C.Amount
Results:
RevenueID
ProductID
InvoiceNo
Amount
CashinID
123
456
987
1000
ABC
123
456
987
1000
CDE
234
456
987
1000
ABC
234
456
987
1000
CDE
Which in theory makes sense, but I just can't seem to find a solution where each row is just used once.
Two things I found and tried are windowing functions and the OUTER APPLY function with a TOP(1) selection. Both came to the same result:
SELECT
*
FROM
[Revenues] R
OUTER APPLY
(SELECT TOP(1) *
FROM [CashIns] C) C
Which returns the desired columns from the Revenues table, but only matched the first appearance from the Cashins table:
RevenueID
ProductID
InvoiceNo
Amount
CashinID
123
456
987
1000
ABC
234
456
987
1000
ABC
I also thought about something like updating the Revenues table, so that the matched CashinID is next to a line and then check every time that the CashinID is not yet used within that table, but I couldn't make it work...
Many thanks in advance for any help or hint in the right direction!
As I said in my comment, you have a fundamental problem with your data relationships. You need to reference the unique identifier of the other table in one of your tables. If you don't do that, then you can only order your transactions in both tables and join them by the row number. You're using a hope and prayer to join your data instead of unreputable identifier's.
--This example orders the transactions in each transaction table and uses
--the order number to join them.
WITH RevPrelim AS (
SELECT *
, ROW_NUMBER() OVER(PARTITION BY InvoiceNo, ProductID, Amount ORDER BY RevenueID) AS row_num
FROM [Revenues] R
), CashinsPrelim AS (
SELECT *
, ROW_NUMBER() OVER(PARTITION BY InvoiceNo, ProductID, Amount ORDER BY CashinID) AS row_num
FROM [CashIns] AS C
)
SELECT *
FROM RevPrlim AS r
LEFT OUTER JOIN CashinsPrelim AS c
ON c.ProductID = r.ProductID
AND c.InvoiceNo = r.InvoiceNo
AND c.Amount = r.Amount
AND c.row_num = r.row_num
I am new to this, so this can be silly, but..
I have 3 created tables:
create table faculty (id_faculty number(2) Primary Key, //id , 2 digits, primary key
faculty_name varchar2(30) constraint f_name Not Null, // name, up to 30 symbols, not null
dean varchar2(20), // dean, up to 20 symbols
telephone varchar2(8)); //number, up to 8 symbols
create table student (id_student number(3) Primary Key, //id, 3 digits, primary key
student_name varchar(20) constraint s_name Not Null, // name, up to 20 symbols, not null
student_surname varchar(20) constraint s_surname Not Null, // name, up to 20 symbols, not null
course_year number(1) constraint s_course_year Check(course_year>0 and course_year<=6), // number, 1 digit, in range 1-6
faculty_id number(2), // id, 2 digits
constraint s_fkey Foreign key (Faculty_ID) References faculty(ID_faculty)); // foreign key to table faculty to id_faculty
create table money (id_payout number(4) Primary Key,
student_id number(3),
constraint m_fkey Foreign key (student_ID) References student(ID_student),
payout_date date constraint p_date Not Null,
stipend number(5,2) Check(stipend<151),
compensation number(5,2));
Then I created view:
create or replace view stud_stip AS
Select f.faculty_name, student_surname, student_name, s.course_year,
m.stipend, m.payout_date
from
faculty f inner join student s on f.id_faculty=s.faculty_id
inner join money m on m.student_id=s.id_student
where
m.payout_date = ( select distinct max(payout_date)
from money
where money.student_id=money.student_id)
with check option;
This is how the view looks
Then I wanted to add information to a view:
insert into stud_stip values ('Partikas tehnologiju', 'Lapa', 'Jana', 2, 120, '03.12.1998');
This is the error I got:
ORA-01779: cannot modify a column which maps to a non key-preserved table
I have googled all over the internet and did not solve my error, please don't send me other topics about this question, because I probably did read it.
I will be very grateful for the answers. Thank you in advance.
The way to do it is to create an instead of trigger on a view which would - in turn - insert rows into appropriate tables.
As all ID columns are primary keys and you didn't explain how you populate them, I'll do it using a sequence, in the same instead of trigger.
SQL> create sequence seqs;
Sequence created.
SQL> create or replace trigger trg_stud_stip
2 instead of insert on stud_stip
3 for each row
4 begin
5 insert into faculty (id_faculty, faculty_name)
6 values (seqs.nextval, :new.faculty_name);
7
8 insert into student (id_student, student_name, student_surname, course_year)
9 values (seqs.nextval, :new.student_name, :new.student_surname, :new.course_year);
10
11 insert into money (id_payout, stipend, payout_date)
12 values (seqs.nextval, :new.stipend, :new.payout_date);
13 end;
14 /
Trigger created.
Testing (don't insert strings into DATE datatype columns!):
SQL> insert into stud_stip
2 (faculty_name, student_surname, student_name, course_year, stipend, payout_date)
3 values
4 ('Partikas tehnologiju', 'Lapa', 'Jana', 2, 120, date '1998-12-03');
1 row created.
SQL> select * from faculty;
ID_FACULTY FACULTY_NAME DEAN TELEPHON
---------- ------------------------------ -------------------- --------
1 Partikas tehnologiju
SQL> select * from student;
ID_STUDENT STUDENT_NAME STUDENT_SURNAME COURSE_YEAR FACULTY_ID
---------- -------------------- -------------------- ----------- ----------
2 Jana Lapa 2
SQL> select * from money;
ID_PAYOUT STUDENT_ID PAYOUT_DAT STIPEND COMPENSATION
---------- ---------- ---------- ---------- ------------
3 03.12.1998 120
SQL>
It works. Now that you know how, improve it if necessary.
I have this two table.
CREATE TABLE doctor(
code_doctor char(5) primary key not null,
name varchar(30) not null,
gender char(1) check(gender='L' or gender='P'),
address varchar(30),
salary numeric
)
CREATE TABLE schedule_doctor(
code_schedule char(5) primary key not null,
day varchar(10) CHECK (day IN ('monday', 'tuesday', 'wednesday', 'thursday', 'friday','saturday')),
shift varchar(10) CHECK (shift='morning' or shift='evening'),
code_doctor char(5) foreign key references doctor(code_doctor) on update cascade on delete
cascade
)
How to show doctor name, day, shift in one table?
You can simply use join to use to get the desired results. -
Select Name, day, shift
From doctor d inner join schedule_doctor sd on d.code_doctor = sd.code_doctor
you can using join, A JOIN clause is used to combine rows from two or more tables
SELECT d.NAME AS DoctorName,
sd.DAY,
sd.shift
FROM doctor d
INNER JOIN schedule_doctor sd
ON d.code_doctor = sd.code_doctor
Or The LEFT JOIN keyword returns all records from the left table doctor, and the matched records from the right table schedule_doctor. The result is NULL from the right side, if there is no match.
SELECT d.NAME AS DoctorName,
sd.DAY,
sd.shift
FROM doctor d
LEFT JOIN schedule_doctor sd
ON d.code_doctor = sd.code_doctor
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE doctor(
code_doctor char(5) primary key not null,
name varchar(30) not null,
gender char(1) check(gender='L' or gender='P'),
address varchar(30),
salary numeric
)
CREATE TABLE schedule_doctor(
code_schedule char(5) primary key not null,
day varchar(10) CHECK (day IN ('monday', 'tuesday', 'wednesday', 'thursday', 'friday','saturday')),
shift varchar(10) CHECK (shift='morning' or shift='evening'),
code_doctor char(5) foreign key references doctor(code_doctor) on update cascade on delete
cascade
)
INSERT INTO doctor (code_doctor,name,gender,address,salary)VALUES('SL','Sam LeBalnc','L','USA',25000),
('MG','Maria Gilles','P','Spain',35000)
INSERT INTO schedule_doctor (code_schedule,day,shift,code_doctor) VALUES ('SL1','monday','morning','SL'),
('SL2','tuesday','evening','SL'),
('MG1','tuesday','evening','MG'),
('MG2','thursday','evening','MG')
Query 1:
SELECT name, day, shift FROM
doctor doc
LEFT JOIN schedule_doctor sd on doc.code_doctor = sd.code_doctor
Results:
| name | day | shift |
|--------------|----------|---------|
| Maria Gilles | tuesday | evening |
| Maria Gilles | thursday | evening |
| Sam LeBalnc | monday | morning |
| Sam LeBalnc | tuesday | evening |
You can know more about SQL JOINS.
I have two columns and I need them both to be unique between each other (like it is 1 column).
My first attempt was to create sequence and set default constraints.
create sequence seq1
as bigint
start with 1
increment by 1
cache;
go
create table product (
pk uniqueidentifier
, id_1 bigint not null default (next value for seq1)
, id_2 bigint not null default (next value for seq1)
);
go
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
go
And it doesn't work. The result of query above will be:
id_1 id_2
1 1
2 2
3 3
4 4
5 5
Currently I stopped using 2 sequences with even and not even numbers.
create sequence seq2
as bigint
start with 1
increment by 2
cache;
go
create sequence seq3
as bigint
start with 2
increment by 2
cache;
go
But if in future I will need to add another column which also must be unique I will have a problem.
I also thinked about stored procedures. Something like this works for me.
create procedure sp_insertProduct
as
begin
declare #id1 as bigint = next value for seq1;
declare #id2 as bigint = next value for seq1;
insert into product (pk, id_1, id_2) values (newid(), #id1, #id2);
end
go
exec sp_insertProduct;
exec sp_insertProduct;
exec sp_insertProduct;
go
But due to my ORM framework restictions I cannot use stored procedures for inserting.
So is there a better solution for that problem?
PS. for some reasons I can not use uniqueidentifiers.
UPDATE
I think I need to explain question a bit clearly. I have a working solution for now (and both current answers will also work), but I wonder if there is a extensible solution to:
provide uniqueness of values in multiple columns (with ability to
add additional columns in future).
avoid using uniqueidentifiers
for better understanding of question, this is how I check uniqueness:
with src as (
select id_1 as id from product
union all
select id_2 as id from product
)
select id, count(*) as equal_values
from src
group by id
having (count(*) > 1)
create sequence seq1
as bigint
start with 1
increment by 2
cache;
go
create table product (
pk uniqueidentifier
, id_1 bigint not null default (next value for seq1)
, id_2 bigint not null default (next value for seq1 + 1)
);
go
doing this..
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
insert into product (pk) values (newid());
this would generate..
pk id_1 id_2
2A159914-8105-4DC1-9D7E-570CC5444172 1 2
6DAFEF16-2B81-4A10-99EF-B3F1A74389C6 3 4
8C6F6697-D993-4320-92BB-04CD56804C5A 5 6
AC97F37F-CAC3-4E83-BDD4-4B55D009C334 7 8
3DDAADA0-D7DB-4350-8087-ABF02B539552 9 10
SQL Fiddle
May be this seems very naive but it should work. I didn't check but you may need to add some parenthesis:
create table product (
pk uniqueidentifier
, id_1 bigint not null default (next value for seq1)
, id_2 bigint not null default (-next value for seq1)
);
go
I have something like the following table:
CREATE TABLE [dbo].[Test]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[Title] [nvarchar](450) NULL,
[Description] [nvarchar](4000) NULL,
[Created] [datetime] NULL,
[OrgId] [int] NULL CONSTRAINT [FK_Test_OrgId] FOREIGN KEY REFERENCES Test(Id),
CONSTRAINT [PK_Test]
PRIMARY KEY CLUSTERED ([Id] ASC)
)
A new entry has OrgId = null. If an entry has been edited, a new row is created with OrgId set to its original parent. If an entry is edited multiple times, all children will have OrgId set to the Id of the original row. The created datetime provides the "order".
What I need to do is select only the newest versions.
Given the table below, I am looking to select only Id 3, 5 & 6
Id Title Description Created PreId
-----------------------------------------------------
1 Car Orginal car 2014-01-01 NULL
2 House Original house 2014-01-01 NULL
3 Bike Original bike 2014-01-01 NULL
4 Car Car updated 2014-06-01 1
5 Car Car updated again 2014-08-01 1
6 House house updated 2014-09-01 2
Any input appreciated.
Thanks.
Since all records are pointing at the original row (and not the previous one) :
SELECT ID, Title, DEscription, CREATED, PreID
FROM
(SELECT ID, Title, DEscription, CREATED, PreID,
ROW_NUMBER() over(partition by ISNULL(OrgID,id) order by id desc) rn
FROM test) A
where RN = 1
WITH Titles AS
(
SELECT DISTINCT Title
FROM dbo.Test
)
SELECT A.ID, Ti.Title, A.[Description], A.Created, A.OrgId
FROM Titles AS Ti
OUTER APPLY (SELECT TOP (1) ID, [Description], [Created], [OrgId]
FROM dbo.Test AS Te
WHERE Te.Title = Ti.Title
ORDER BY Created DESC
) AS A;