How to solve this SQL query (header and detail)? - sql

I am working with SQL Server 2008 R2.
I have a SQL query question related to header and detail tables. I have a header table where I am storing location & department & week_start_date. I have a detail table where I am storing employee_id & job_code & work_date & hours.
I want to find those employees who are in different headers with same week_start_date but different location and/or department.
Here is the explanation:
I have a header table:
CREATE TABLE header (
header_id bigint not null PRIMARY KEY,
location_code int not null,
department_code int not null,
week_start_date datetime not null )
I have a detail table:
CREATE TABLE detail (
detail_id bigint not null PRIMARY KEY,
header_id bigint not null FOREIGN KEY header(header_id),
employee_id int not null,
job_code int not null,
work_date datetime not null,
hours decimal(8,2) not null )
header table has the unique key as location_code + department_code + week_start_date.
For example this is the data in header table:
header_id=11, location_code=22, department_code=33,
week_start_date='2016-02-08'
header_id=12, location_code=22, department_code=39,
week_start_date='2016-02-08'
header_id=13, location_code=22, department_code=33,
week_start_date='2016-02-15'
header_id=14, location_code=21, department_code=33,
week_start_date='2016-02-08'
Each row in header table can have multiple rows in detail table.
detail table has the unique key as header_id + employee_id + job_code + work_date.
For example this is the data in detail table for 1000598 employee_id:
detail_id=101, header_id=11, employee_id=1000598, job_code=77,
work_date='2016-02-08', hours=5.00
detail_id=102, header_id=11, employee_id=1000598, job_code=77,
work_date='2016-02-09', hours=4.00
detail_id=109, header_id=12, employee_id=1000598, job_code=79,
work_date='2016-02-11', hours=4.50
For example this is the data in detail table for 1000599 employee_id:
detail_id=121, header_id=11, employee_id=1000599, job_code=78,
work_date='2016-02-10', hours=8.00
detail_id=122, header_id=14, employee_id=1000599, job_code=75,
work_date='2016-02-12', hours=3.00
For example this is the data in detail table for 1000600 employee_id:
detail_id=131, header_id=11, employee_id=1000600, job_code=72,
work_date='2016-02-11', hours=7.00
detail_id=132, header_id=13, employee_id=1000600, job_code=75,
work_date='2016-02-17', hours=3.00
The SQL query should return 1000598 employee_id as 1000598 has data for both department_code=33 and department_code=39 for the same week_start_date='2016-02-08'.
The SQL query should return 1000599 employee_id as 1000599 has data for both location_code=22 and location_code=21 for the same week_start_date='2016-02-08'.
The SQL query should not return 1000600 employee_id.
This is the start I have come up with:
select
h.employee_id,
d.week_start_date
from
header h (nolock)
inner join detail d (nolock)
on h.header_id = d.header_id
group by
h.employee_id,
d.week_start_date
order by
1,
2
Not much.

I want to find those employees who are in different headers with same
week_start_date but different location and/or department.
The query below will return all (employee_id, week_start_date) pairs that have more than 1 location_code or department_code
select d.employee_id, h.week_start_date
from detail d
join header h on h.header_id = d.header_id
group by d.employee_id, h.week_start_date -- employees with same week_start_date
having (
count(distinct h.location_code) > 1 -- have more than 1 location
or count(distinct h.department_code) > 1 -- or more than 1 department
)

select dtl.* from
(select * from detail d1 where EXISTS(select count(header_id) from detail d2 where d1.employee_id=d2.employee_id having count(header_id)>1))
inner join
header hd
on hd.header_id=dtl.header_id
group by dtl.employee_id, hd.week_start_date
having count(*)>1

Related

SQL Server 2008 R2: Show only recently added records

I have two tables:
Cust : Contains customer details like customer ID and customer Name.
Cust_Address : This table contains customer ID and customer address.
Table: Cust
create table cust
(
cust_id int,
cust_name varchar(10)
);
Records Insertion:
insert into cust values(1,'A');
insert into cust values(2,'B');
insert into cust values(3,'C');
insert into cust values(4,'D');
Table: Cust_Address
create table cust_address
(
cust_id int,
cust_add varchar(50)
);
Records Insertion:
insert into cust_address values(1,'US');
insert into cust_address values(2,'UK');
insert into cust_address values(3,'UAE');
insert into cust_address values(4,'SA');
insert into cust_address values(1,'AUS');
insert into cust_address values(2,'IND');
insert into cust_address values(3,'SL');
insert into cust_address values(1,'CHINA');
Now I want to show the result which contains the latest customer address which have been inserted in the table Cust_Address.
Expected Result:
Cust_ID Cust_Name Cust_Add
-------------------------------
1 A CHINA
2 B IND
3 C SL
4 D SA
Here is the SQLFiddle for tables and its records.
You are not able to retrieve the rows in any particular order. You need some more info to get an order.
The best way is primary index in Cust_address
CustAddrID int identity(1, 1) not null primary key
You can also have a CreatedOn column that will have default value equal to getDate()
After that you can figure out what is the last inserted value for CustAddr for each Cust record.
In case you are not able to add new column there then maybe
change tracking functionality. But your issue seems to be too trivial for that.
There are also Temporal Tables in SQL Server 2016. But again it's probably too much.
Here is an example how you can get the address using primary key CustAddrID
SQL Fiddle
select cust_name, cust_add
from cust C
join
(select
cust_add, cust_id,
row_number() over (partition by cust_id order by cust_add_id desc) rn
from cust_address ) CLA
on CLA.cust_id = C.cust_id and
CLA.rn = 1
Identity column increases every time when we insert new value to the table. The correct value for your case will be the record with the highest cust_add_id and specified cust_id.
In the above query we generates numbers in desc order starting from 1 using row_number() function for each cust_id (partition by cust_id). Finally we take only the records with generated number rn equal to 1 CLA.rn = 1 and we join it to cust table.
You can replace row_number() by max(cust_add_id) and group by cust_id. However in that case you need to join cust_add table twice.
You will not be able to get the rows out of the link table in the order they were inserted.
You need to have a column for this.
Imagine how big the meta-data would be if you needed to keep a record for each record for creation! Would you also want to keep meta-data on your meta-data so you know when the meta-data was updated? The space use can quickly escalate.
SQL Server keeps some stats but something this specific will need to come from a user-defined field.
So you either use a identity column in the CustAddr table [CustAddr int identity(1, 1) not null primary key] or add a column for createdDateAndTime DateTime Default GetDate().

How to Verify Multiple Foreign Key Combination in a table with a Primary Key in another table

I Have a Specification Master Table with three columns
(
ID BIGINT PRIMARY KEY,
Name varchar,
Value varchar
)
a Product Master Table with two Columns
(
ID BIGINT PRIMARY KEY,
Name varchar
)
a Stock table with
(
StockID BIGINT PRIMARY KEY,
ProductID BIGINT FOREIGN KEY (Product.ID),
SpecGroupID BIGINT UNIQUE KEY,
Stock INT
)
a Specification Grouping Table with
(
GroupID BIGINT FOREIGN KEY (Stock.SpecGroupID),
SPecificationID BIGINT FOREIGN KEY (Specification.ID),
PRIMARY KEY (Composite)
)
Now I am looking for a combination of specification if it has any stock or not.
but could not find a logic to match exact combination.
The problem I am facing if a combination of specification has n specification associated with a stock.SpecGroupID in Specification Grouping Table.
While I am searching with a few less than those n specification combination it always returning the same SpecGroupID for n specs group.
Imagine I have a apple (Color: Red; Size:5; Weight:10) in stock
And someone is ordering for a apple (Color: Red; Size:5)
I Need to give a result: Not Available
Will this work for you?
SELECT Specification.ID SpecId
, Specification.Name SpecificationName
, Value SpecificationValue
, MAX(ISNULL(Stock.Stock,0)) HasStock
FROM
Specification
LEFT JOIN SpecificationGrouping sg
ON sg.SpecificationID = Specification.ID
LEFT JOIN Stock
ON GroupID = SpecGroupID
GROUP BY Specification.ID
, Specification.Name
, Value
First cte Groups tell me the specifications of each group.
Second cte GroupDetail join all properties in a single string like this Color|Red,Size|5,Weight|10
Third cte OrderDetail is just the test input. in this sample 6 and 9 are orders.
Last query. Try to find one item on stocks with exact detail as the order
If a specification isn't on stocks I assume is an order, here 6 doesn't have item on stock so produce all null and 9 does have stock and return the StockValue.
SQL Fiddle Demo
WITH Groups as
(
SELECT *
FROM SpecificationGrouping SG
INNER JOIN Specification S
ON Sg.SPecificationID = S.ID
),
GroupDetail as
(
Select distinct ST2.GroupID,
substring(
(
Select ','+ ST1.Name + '|' + ST1.Value AS [text()]
From Groups ST1
Where ST1.GroupID = ST2.GroupID
ORDER BY ST1.Name
For XML PATH ('')
), 2, 1000) [Detail]
From Groups ST2
),
OrderDetail as
(
SELECT Detail
FROM GroupDetail
WHERE GroupID = 9
)
SELECT S.SpecGroupID, S.Stock, G.[Detail]
FROM Stock S
INNER JOIN GroupDetail G
ON S.SpecGroupID = G.GroupID
RIGHT JOIN OrderDetail O
ON O.Detail = G.Detail
Output for 9
| SpecGroupID | Stock | Detail |
|-------------|-------|----------------------------|
| 3 | 5 | Color|Red,Size|5,Weight|10 |

Find Unmatch record 2 table with where condition apply and record show from 1 table

class table
class_code varchar(50)
timing varchar(50)
emp_id varchar(50)
employee table
i_id int
emp_id varchar(20)
name varchar(50)
We Have 2 tables 1 is employee and other is class
we have 4 records in employee table where emp_id=as-1,as-2,as-3,as-4
we have 2 records in class table with emp_id = as-1 or as-3 and timing= '3-4'
We select those record from employee table who available/free at timing='3-4' like as-2 or as-3 because as-1 or as-3 is already in class table in timing at 3-4
You have not told us a great deal and the indicated data may not be fully representative. Both of these will return employee 2 and 4 (& both will work in MySQL or MSsql):
SELECT
e.*
FROM employee e
LEFT JOIN class c
ON e.emp_id = c.emp_id
WHERE (c.timing <> '3-4'
OR c.timing IS NULL)
;
SELECT
*
FROM employee
WHERE NOT EXISTS (
SELECT 1
FROM class
WHERE timing = '3-4'
AND class.emp_id = employee.emp_id
)
;
see this sqlfiddle (MySQL)

Join logic from two separate tables in sql

We returned a list of cardID's after a query and those cardID's belong to two tables Student and Personnel. So how can I join those cardID's with Student and Personnel so I can return a table that shows name of Student and Personnel according to cardID's?
Personnel table:
PERSONNELID NUMBER(9,0)
PERSONNELNAME VARCHAR2(20)
PERSONNELSURNAME VARCHAR2(20)
PERSONNELJOB VARCHAR2(40)
PERSONNELCARDID NUMBER(4,0)
Student table:
STUDENTID NUMBER(9,0)
STUDENTNAME VARCHAR2(20)
STUDENTSURNAME VARCHAR2(20)
STUDENTDEPT VARCHAR2(40)
STUDENTFACULTY VARCHAR2(20)
STUDENTCARDID NUMBER(4,0)
CardID table
CARDID NUMBER(4,0)
USERTYPE VARCHAR2(20)
CHARGE NUMBER(3,2)
CREDIT NUMBER(4,2)
PaymentDevice table:
ORDERNO NUMBER
PAYDEVIP NUMBER(8,0)
PAYDEVDATE DATE No
PAYDEVTIME VARCHAR2(8)
CHARGEDCARDID NUMBER(9,0)
MEALTYPE VARCHAR2(10)
I tried to return first 10 person's name and surname that eat at cafeteria on 27/12/2012
SELECT C.CARDID
FROM CARD C, PAYMENTDEVICE P
WHERE P.ORDERNO
BETWEEN (SELECT MIN(ORDERNO)
FROM PAYMENTDEVICE
WHERE PAYDEVDATE='27/12/2012') AND (SELECT MIN(ORDERNO)
FROM PAYMENTDEVICE
WHERE PAYDEVDATE='27/12/2012')+10 AND C.CARDID=P.CHARGEDCARDID;
Our orderNo isn't reset everyday but keeps increasing so we found the min orderNo that day and add 10 to this value to find first 10 person who eat on that day between those order numbers.
So what return from this query:
CARDID
1005
1000
1002
1003
1009
2000
2001
1007
2002
1004
1006
and those some of those cardId (start with 1) are studentCardId and some of them (starts with 2) are PersonnelCardId. So how can I match and write names accordingly?
SELECT *
FROM Personel p INNER JOIN Student s
ON p.PersonnelCardId = s.StudentCardId
INNER JOIN ReturnedQuery rq
ON rq.CardId = p.PersonnelCardId
updated:
SELECT p.PersonnelName, rq.CardId
FROM Personel p INNER JOIN ReturnedQuery rq
ON rq.CardId = p.PersonnelCardId
UNION
SELECT s.StudentName, rq.Cardid
FROM Student s INNER JOIN ReturnedQuery rq
ON s.StudentCardId = rq.Cardid
Your original query is actually pretty fragile. I'd rewrite it like so (and added the needed joins):
WITH First_Daily_Purchase as (SELECT chargedCardId,
MIN(payDevTime) as payDevTime,
MIN(orderNo) as orderNo
FROM PaymentDevice
WHERE payDevDate >=
TO_DATE('2012-12-27', 'YYYY-MM-DD')
AND payDevDate <
TO_DATE('2012-12-28', 'YYYY-MM-DD')
GROUP BY chargedCardId),
First_10_Daily_Purchasers as (SELECT chargedCardId
FROM (SELECT chargedCardId,
RANK() OVER(ORDER BY payDevTime,
orderNo) as rank
FROM First_Daily_Purchase) a
WHERE a.rank < 11)
SELECT a.chargedCardId, b.personnelName, b.personnelSurname
FROM First_10_Daily_Purchasers a
JOIN Personnel b
ON b.personnelCardId = a.chargedCardId
UNION ALL
SELECT a.chargedCardId, b.studentName, b.studentSurname
FROM First_10_Daily_Purchasers a
JOIN Student b
ON b.studentCardId = a.chargedCardId
(Have a working SQL Fiddle - generally bullet-proofing this took me a while.)
This should get you the first 10 people who made a purchase (not the first 11 purchases, which is what you were actually getting). This of course assumes that payDevTime is actually stored in a sortable format (if it isn't you have bigger problems than this query not working quite right).
That said, there's a number of troubling things about your schema design.

SQL DML Query AVG and COUNT

I am beginner at SQL and I am trying to create a query.
I have these tables:
CREATE TABLE Hospital (
hid INT PRIMARY KEY,
name VARCHAR(127) UNIQUE,
country VARCHAR(127),
area INT
);
CREATE TABLE Doctor (
ic INT PRIMARY KEY,
name VARCHAR(127),
date_of_birth INT,
);
CREATE TABLE Work (
hid INT,
ic INT,
since INT,
FOREIGN KEY (hid) REFERENCES Hospital (hid),
FOREIGN KEY (ic) REFERENCES Doctor (ic),
PRIMARY KEY (hid,ic)
);
The query is: What is the average in each country of the number of doctors working in hospitals of that country (1st column: each country, 2nd column: average)? Thanks.
You first need to write a query that counts the doctors per hospital
select w.hid, count(w.ic)
from work w
group by w.hid;
Based on that query, you can retrieve the average number of doctors per country:
with doctor_count as (
select w.hid, count(w.ic) as cnt
from work w
group by w.hid
)
select h.country, avg(dc.cnt)
from hospital h
join doctor_count dc on h.hid = dc.hid
group by h.country;
If you have an old DBMS that does not support common table expressions the above can be rewritten as:
select h.country, avg(dc.cnt)
from hospital h
join (
select w.hid, count(w.ic) as cnt
from work
group by w.hid
) dc on h.hid = dc.hid;
Here is an SQLFiddle demo: http://sqlfiddle.com/#!12/9ff79/1
Btw: storing date_of_birth as an integer is a bad choice. You should use a real DATE column.
And work is a reserved word in SQL. You shouldn't use that for a table name.