I have 2 related questions and will mark this post as solved for any valid reply to any one of them.
Question 1
I'm quite new at PostgreSQL. Is there a shorter way to write this PostgreSQL code without the repetitive SQL code blocks ? I'm using pgAdmin (if it makes any difference)
Maybe Blocks of SQL code between Union All commands can be defined to be a repetitive template, then they can be repeated by using months as parameters.
Question 2
What I'm trying to do here is turning a table like;
rep_Month | p_Code | L1 .... L6 | EX_1706 | EX_1707 | EX_1708 .... EX_1712
to;
rep_Month | p_Code | L1 .... L6 | DATE | Expense
Can it be done with a more suitable approach ? (I guess there may be one but could not figure it out.)
Thanks in advance...
Have a great day...
-- 2017 --------------------------------------------------
SELECT
"B12_Expense_AB"."rep_Month",
"B12_Expense_AB"."p_Code",
"B12_Expense_AB"."L1",
"B12_Expense_AB"."L2",
"B12_Expense_AB"."L3",
"B12_Expense_AB"."L4",
"B12_Expense_AB"."L5",
"B12_Expense_AB"."L6",
DATE '2017-06-01' AS "Exp_Month",
"B12_Expense_AB"."EX_1706" AS "Expense"
FROM
"B12_Expense_AB"
WHERE
"B12_Expense_AB"."EX_1706" <> 0
UNION ALL
SELECT
"B12_Expense_AB"."rep_Month",
"B12_Expense_AB"."p_Code",
"B12_Expense_AB"."L1",
"B12_Expense_AB"."L2",
"B12_Expense_AB"."L3",
"B12_Expense_AB"."L4",
"B12_Expense_AB"."L5",
"B12_Expense_AB"."L6",
DATE '2017-07-01' AS "Exp_Month",
"B12_Expense_AB"."EX_1707" AS "Expense"
FROM
"B12_Expense_AB"
WHERE
"B12_Expense_AB"."EX_1707" <> 0
UNION ALL
SELECT
"B12_Expense_AB"."rep_Month",
"B12_Expense_AB"."p_Code",
"B12_Expense_AB"."L1",
"B12_Expense_AB"."L2",
"B12_Expense_AB"."L3",
"B12_Expense_AB"."L4",
"B12_Expense_AB"."L5",
"B12_Expense_AB"."L6",
DATE '2017-08-01' AS "Exp_Month",
"B12_Expense_AB"."EX_1708" AS "Expense"
FROM
"B12_Expense_AB"
WHERE
"B12_Expense_AB"."EX_1708" <> 0
UNION ALL
SELECT
"B12_Expense_AB"."rep_Month",
"B12_Expense_AB"."p_Code",
"B12_Expense_AB"."L1",
"B12_Expense_AB"."L2",
"B12_Expense_AB"."L3",
"B12_Expense_AB"."L4",
"B12_Expense_AB"."L5",
"B12_Expense_AB"."L6",
DATE '2017-09-01' AS "Exp_Month",
"B12_Expense_AB"."EX_1709" AS "Expense"
FROM
"B12_Expense_AB"
WHERE
"B12_Expense_AB"."EX_1709" <> 0
UNION ALL
SELECT
"B12_Expense_AB"."rep_Month",
"B12_Expense_AB"."p_Code",
"B12_Expense_AB"."L1",
"B12_Expense_AB"."L2",
"B12_Expense_AB"."L3",
"B12_Expense_AB"."L4",
"B12_Expense_AB"."L5",
"B12_Expense_AB"."L6",
DATE '2017-10-01' AS "Exp_Month",
"B12_Expense_AB"."EX_1710" AS "Expense"
FROM
"B12_Expense_AB"
WHERE
"B12_Expense_AB"."EX_1710" <> 0
UNION ALL
SELECT
"B12_Expense_AB"."rep_Month",
"B12_Expense_AB"."p_Code",
"B12_Expense_AB"."L1",
"B12_Expense_AB"."L2",
"B12_Expense_AB"."L3",
"B12_Expense_AB"."L4",
"B12_Expense_AB"."L5",
"B12_Expense_AB"."L6",
DATE '2017-11-01' AS "Exp_Month",
"B12_Expense_AB"."EX_1711" AS "Expense"
FROM
"B12_Expense_AB"
WHERE
"B12_Expense_AB"."EX_1711" <> 0
UNION ALL
SELECT
"B12_Expense_AB"."rep_Month",
"B12_Expense_AB"."p_Code",
"B12_Expense_AB"."L1",
"B12_Expense_AB"."L2",
"B12_Expense_AB"."L3",
"B12_Expense_AB"."L4",
"B12_Expense_AB"."L5",
"B12_Expense_AB"."L6",
DATE '2017-12-01' AS "Exp_Month",
"B12_Expense_AB"."EX_1712" AS "Expense"
FROM
"B12_Expense_AB"
WHERE
"B12_Expense_AB"."EX_1712" <> 0;
Pivoting columns to rows can be done using the jsonb functions. Try something like this:
select e."rep_Month", e."p_Code",
e."L1", e."L2", e."L3", e."L4", e."L5", e."L6",
regexp_replace(j.k, 'EX_(\d\d)(\d\d)', '20\1-\2-01')::date as "Exp_Month",
j.v as "Expense"
from "B12_Expense_AB" e
join lateral jsonb_each_text(to_jsonb(e)) as j(k, v)
on j.k like 'EX\_%';
db<>fiddle here
Related
I have the following example dataset (actual table is about 30000 rows).
I need to scan through the table and identify any non-contiguous dates for all references within the table that have non-contiguous data, and generate data to fill in the missing dates
So for the above reference I would need example data generated to be in bold below (showing existing rows for clarity):
ID
START_DATE
END_DATE
7172
2020-03-13
2020-10-22
7172
2020-10-23
2020-11-08
7172
2020-11-09
2020-11-09
7172
2020-11-10
2020-11-19
7172
2020-11-20
2020-11-20
7172
2020-11-21
2021-03-14
7172
2021-03-15
2021-03-17
7172
2021-03-18
2021-03-19
7172
2021-03-20
2021-03-28
7172
2021-03-29
2021-04-25
7172
2021-04-26
2021-30-04
7172
2021-05-01
2021-06-07
7172
2021-06-08
2021-06-08
7172
2021-06-09
2022-01-09
Can anyone help please?
You can make life a bit easier on respondents by making "Readily Consumable" Test Data, like the following example... (I added in the extra ID that #PatrickHurst did)...
--===== Create and populate the test table with the given test data.
-- This is NOT a part of the solution.
DROP TABLE IF EXISTS #Test;
GO
CREATE TABLE #Test
(
ID INT
, START_DATE DATE
, END_DATE DATE
)
;
INSERT INTO #Test (ID, START_DATE, END_DATE) VALUES
(7172,'2020-03-13','2020-10-22')
,(7172,'2020-10-23','2020-11-08')
--,(7172,'2020-11-09','2020-11-09') --Is Missing
,(7172,'2020-11-10','2020-11-19')
--,(7172,'2020-11-20','2020-11-20') --Is Missing
,(7172,'2020-11-21','2021-03-14')
,(7172,'2021-03-15','2021-03-17')
--,(7172,'2021-03-18','2021-03-19') --Is Missing
,(7172,'2021-03-20','2021-03-28')
,(7172,'2021-03-29','2021-04-25')
--,(7172,'2021-04-26','2021-04-30') --Is Missing (And you had a bad date here)
,(7172,'2021-05-01','2021-06-07')
--,(7172,'2021-06-08','2021-06-08') --Is Missing
,(7172,'2021-06-09','2022-01-09')
,(1234,'2020-03-13','2022-03-15')
,(1234,'2022-03-20','2022-03-25')
;
If you have SQL Server 2012 or above, LAG makes this easy (and someone can probably simplify it even more)...
WITH cte AS
(
SELECT *
,mSTART_DATE = LAG(DATEADD(dd,1,END_DATE),1,START_DATE) OVER (PARTITION BY ID ORDER BY START_DATE)
,mEND_DATE = DATEADD(dd,-1,START_DATE)
FROM #Test
)
--===== Finds the existing date ranges
SELECT ID, START_DATE, END_DATE, Comment = '' FROM #Test
UNION ALL
--===== Finds the missing date ranges
SELECT ID,mSTART_DATE,mEND_DATE, Comment = 'Was Missing' FROM cte WHERE mSTART_DATE <> START_DATE
ORDER BY ID, START_DATE
;
Here are the results...
If you truly only want the missing date ranges, then the following does it...
WITH cte AS
(
SELECT *
,mSTART_DATE = LAG(DATEADD(dd,1,END_DATE),1,START_DATE) OVER (PARTITION BY ID ORDER BY START_DATE)
,mEND_DATE = DATEADD(dd,-1,START_DATE)
FROM #Test
)
SELECT ID,START_DATE = mSTART_DATE,END_DATE = mEND_DATE FROM cte WHERE mSTART_DATE <> START_DATE
ORDER BY ID,START_DATE
;
Results:
2021-30-04 is a bad date (hehe).
I took your table and used it to generate a table variable, and added a couple of rows:
DECLARE #TABLE TABLE (ID INT, START_DATE DATE, END_DATE DATE)
INSERT INTO #TABLE (ID, START_DATE, END_DATE) VALUES
(7172, '2020-03-13', '2020-10-22'), (7172, '2020-10-23', '2020-11-08'),
(7172, '2020-11-10', '2020-11-19'), (7172, '2020-11-20', '2020-11-20'),
(7172, '2020-11-21', '2021-03-14'), (7172, '2021-03-15', '2021-03-17'),
(7172, '2021-03-20', '2021-03-28'), (7172, '2021-03-29', '2021-04-25'),
(7172, '2021-05-01', '2021-06-07'), (7172, '2021-06-09', '2022-01-09'),
(1234, '2020-03-13', '2022-03-15'), (1234, '2022-03-20', '2022-03-25');
If I understand correctly, we'd be looking to generate rows:
--(7172, '2020-11-09', '2020-11-09'),
--(7172, '2021-03-18', '2021-03-19'),
--(7172, '2021-04-26', '2021-03-04'),
--(7172, '2021-06-08', '2021-06-08'),
--(1234, '2022-03-16', '2022-03-19'),
I poked at this some, and got the result you're looking for using some recursive CTE voodoo. I can't promise this is going to be performant, but it might be a good starting point:
DECLARE #MaxDate DATE = (SELECT MAX(END_DATE) FROM #TABLE)
;WITH DateRange AS (
SELECT ID, MIN(END_DATE) AS Date FROM #TABLE GROUP BY ID--#MinDate AS Date
UNION ALL
SELECT ID, DATEADD(DAY,1,Date)
FROM DateRange
WHERE Date < #MaxDate
), MissingDates AS (
SELECT dr.ID, dr.Date, t.START_DATE, t.END_DATE
FROM DateRange dr
LEFT OUTER JOIN #TABLE t
ON dr.Date BETWEEN t.START_DATE AND t.END_DATE
AND dr.ID = t.ID
WHERE t.ID IS NULL
), ranges AS (
SELECT s.ID, s.date AS START_DATE, s.date AS END_DATE, DATEADD(DAY,1,s.Date) AS NEXT_DATE
FROM MissingDates s
LEFT OUTER JOIN MissingDates n
ON s.Date = DATEADD(DAY,1,n.Date)
AND s.ID = n.ID
WHERE n.Date IS NULL
UNION ALL
SELECT a.ID, a.START_DATE, r.Date AS END_DATE, DATEADD(DAY,1,r.Date) AS NEXT_DATE
FROM ranges a
INNER JOIN MissingDates r
ON a.NEXT_DATE = r.Date
AND a.ID = r.ID
)
SELECT ID AS ID, START_DATE, MAX(END_DATE) AS END_DATE
FROM ranges
GROUP BY ID, START_DATE
OPTION (MAXRECURSION 0)
ID START_DATE END_DATE
----------------------------
7172 2020-11-09 2020-11-09
7172 2021-03-18 2021-03-19
7172 2021-04-26 2021-04-30
7172 2021-06-08 2021-06-08
7172 2022-01-10 2022-03-25
1234 2022-03-16 2022-03-19
SELECT
CAST(c.DT AS DATE) AS 'Date'
, COUNT(p.PatternID) AS 'Count'
FROM CalendarMain c
LEFT OUTER JOIN Pattern p
ON c.DT = p.PatternDate
INNER JOIN Result r
ON p.PatternID = r.PatternID
INNER JOIN Detail d
ON p.PatternID = d.PatternID
WHERE r.Type = 7
AND d.Panel = 501
AND CAST(c.DT AS DATE)
BETWEEN '20190101' AND '20190201'
GROUP BY CAST(c.DT AS DATE)
ORDER BY CAST(c.DT AS DATE)
The query above isn't working for me. It still skips days where the COUNT is NULL for it's c.DT.
c.DT and p.PatternDate are both time DateTime, although c.DT can't be NULL. It is actually the PK for the table. It is populated as DateTimes for every single day from 2015 to 2049, so the records for those days exist.
Another weird thing I noticed is that nothing returns at all when I join C.DT = p.PatternDate without a CAST or CONVERT to a Date style. Not sure why when they are both DateTimes.
There are a few things to talk about here. At this stage it's not clear what you're actually trying to count. If it's the number of "patterns" per day for the month of Jan 2019, then:
Your BETWEEN will also count any activity occurring on 1 Feb,
It looks like one pattern could have multiple results, potentially causing a miscount
It looks like one pattern could have multiple details, potentially causing a miscount
If one pattern has say 3 eligible results, and also 4 details, you'll get the cross product of them. Your count will be 12.
I'm going to assume:
you only want the distinct number of patterns, regardless of the number of details and results.
You only want January's activity
--Set up some dummy data
DROP TABLE IF EXISTS #CalendarMain
SELECT cast('20190101' as datetime) as DT
INTO #CalendarMain
UNION ALL SELECT '20190102' as DT
UNION ALL SELECT '20190103' as DT
UNION ALL SELECT '20190104' as DT
UNION ALL SELECT '20190105' as DT --etc etc
;
DROP TABLE IF EXISTS #Pattern
SELECT cast('1'as int) as PatternID
,cast('20190101 13:00' as datetime) as PatternDate
INTO #Pattern
UNION ALL SELECT 2,'20190101 14:00'
UNION ALL SELECT 3,'20190101 15:00'
UNION ALL SELECT 4,'20190104 11:00'
UNION ALL SELECT 5,'20190104 14:00'
;
DROP TABLE IF EXISTS #Result
SELECT cast(100 as int) as ResultID
,cast(1 as int) as PatternID
,cast(7 as int) as [Type]
INTO #Result
UNION ALL SELECT 101,1,7
UNION ALL SELECT 102,1,8
UNION ALL SELECT 103,1,9
UNION ALL SELECT 104,2,8
UNION ALL SELECT 105,2,7
UNION ALL SELECT 106,3,7
UNION ALL SELECT 107,3,8
UNION ALL SELECT 108,4,7
UNION ALL SELECT 109,5,7
UNION ALL SELECT 110,5,8
;
DROP TABLE IF EXISTS #Detail
SELECT cast(201 as int) as DetailID
,cast(1 as int) as PatternID
,cast(501 as int) as Panel
INTO #Detail
UNION ALL SELECT 202,1,502
UNION ALL SELECT 203,1,503
UNION ALL SELECT 204,1,502
UNION ALL SELECT 205,1,502
UNION ALL SELECT 206,1,502
UNION ALL SELECT 207,2,502
UNION ALL SELECT 208,2,503
UNION ALL SELECT 209,2,502
UNION ALL SELECT 210,4,502
UNION ALL SELECT 211,4,501
;
-- create some variables
DECLARE #start_date as date = '20190101';
DECLARE #end_date as date = '20190201'; --I assume this is an exclusive end date
SELECT cal.DT
,isnull(patterns.[count],0) as [Count]
FROM #CalendarMain cal
LEFT JOIN ( SELECT cast(p.PatternDate as date) as PatternDate
,COUNT(DISTINCT p.PatternID) as [Count]
FROM #Pattern p
JOIN #Result r ON p.PatternID = r.PatternID
JOIN #Detail d ON p.PatternID = d.PatternID
WHERE r.[Type] = 7
and d.Panel = 501
GROUP BY cast(p.PatternDate as date)
) patterns ON cal.DT = patterns.patternDate
WHERE cal.DT >= #start_date
and cal.DT < #end_date --Your code would have included 1 Feb, which I assume was unintentional.
ORDER BY cal.DT
;
I am writing a query using PostgreSQL to count something but I want to sort the date (DDMMYYYY) properly.
With this following codes,
WITH dis_id AS (SELECT
DISTINCT ON (source_user_id) source_user_id,
created_at
FROM public.info_scammers )
SELECT d.date, count(dis_id.source_user_id)
FROM (SELECT to_char(date_trunc('day',(current_date - offs)), 'DD-MM-YYYY') AS date
FROM generate_series(0,365,1) AS offs
) d LEFT OUTER JOIN
dis_id
ON (d.date = to_char(date_trunc('day',dis_id.created_at),'YYYY-MM-DD'))
GROUP BY d.date
The result is
Date | Count
01-01-2017 | 0
01-02-2017 | 0
01-03-2017 | 0
What I want is
Date | Count
01-01-2017 | 0
02-01-2017 | 0
03-01-2017 | 0
I have looked up the existing problems. But most of them do not use PostgreSQL
Thank you
Leave d.date as type date in the inner SELECT (don't convert it to text with to_char), then add ORDER BY d.date and do the conversion to text in the outer SELECT.
Something like:
WITH dis_id AS (...)
SELECT to_char(d.date, 'DD-MM-YYYY'), count(...)
FROM (SELECT date_trunc(...) AS date
FROM ...
) d
LEFT OUTER JOIN ...
GROUP BY to_char(d.date, 'DD-MM-YYYY')
ORDER BY d.date;
I've found a small annoyance that I was wondering how to get around...
In a simplified example, say I need to return "TEST B-19" and "TEST B-20"
I have a where clause that looks like:
where [Name] LIKE 'TEST B-[12][90]'
and it works... unless there's a "TEST B-10" or "TEST-B29" value that I don't want.
I'd rather not resort to doing both cases, because in more complex situations that would become prohibitive.
I tried:
where [Name] LIKE 'TEST B-[19-20]'
but of course that doesn't work because it is looking for single characters...
Thoughts? Again, this is a very simple example, I'd be looking for ways to grab ranges from 16 to 32 or 234 to 459 without grabbing all the extra values that could be created.
EDITED to include test examples...
You might see "TEXAS 22" or "THX 99-20-110-B6" or "E-19" or "SOUTHERN B" or "122 FLOWERS" in that field. The presense of digits is common, but not a steadfast rule, and there are absolutely no general patterns for hypens, digits, characters, order, etc.
I would divide the Name column into the text parts and the number parts, and convert the number parts into an integer, and then check if that one was between the values. Something like:
where cast(substring([Name], 7, 2) as integer) between 19 and 20
And, of course, if the possible structure of [Name] is much more complex, you'd have to calculate the values for 7 and 2, not hardcode them....
EDIT: If you want to filter out the ones not conforming to the pattern first, do the following:
where [Name] LIKE '%TEST B-__%'
and cast(substring([Name], CHARINDEX('TEST B-', [Name]) + LEN('TEST B-'), 2) as integer) between 19 and 20
Maybe it's faster using CHARINDEX in place of the LIKE in the topmost line two, especially if you put an index on the computed value, but... That is only optimization... :)
EDIT: Tested the procedure. Given the following data:
jajajajajajajTEST B-100
jajajajajajajTEST B-85
jajajajjTEST B-100
jajjajajTEST B-100
jajajajajajajTEST B-00
jajajajaTEST B-100
jajajajajajajEST B-99
jajajajajajajTEST B-100
jajajajajajajTEST B-19
jajajajjTEST B-100
jajjajajTEST B-120
jajajajajajajTEST B-00
jajajajaTEST B-150
jajajajajajajEST B-20
TEST B-20asdfh asdfkh
The query returns the following rows:
jajajajajajajTEST B-19
TEST B-20asdfh asdfkh
Wildcards or no, you still have to edit the query every time you want to change the range definition. If you're always dealing with a range (and it's not always the same range), you might use parameters. For example:
note: for some reason (this has happened in many other posts as well), when I try to post code beginning with 'declare', SO hangs and times-out. I reported it on meta already, but nobody could reproduce it (including me). Here it's happening again, so I took the 'D' off, and now it works. I'll come back tomorrow, and it will let me put the 'D' back on.
DECLARE #min varchar(5)
DECLARE #max varchar(5)
SET #min = 'B-19'
SET #max = 'B-20'
SELECT
...
WHERE NAME BETWEEN #min AND #max
You should avoid formatting [NAME] as others have suggested (using function on it) -- this way, your search can benefit from an index on it.
In any case -- you might re-consider your table structure. It sounds like 'TEST B-19' is a composite (non-normalized) value of category ('TEST') + sub-category ('B') + instance ('19'). Put it in a lookup table with 4 columns (id being the first), and then join it by id in whatever query needs to output the composite value. This will make searching and indexing much easier and faster.
In the absence of test data, I generated my own. I just removed the Test B- prefix, converted to int and did a Between
With Numerals As
(
Select top 100 row_number() over (order by name) TestNumeral
from sys.columns
),
TestNumbers AS
(
Select 'TEST B-' + Convert (VarChar, TestNumeral) TestNumber
From Numerals
)
Select *
From TestNumbers
Where Cast (Replace (TestNumber, 'TEST B-', '') as Integer) between 1 and 16
This gave me
TestNumber
-------------------------------------
TEST B-1
TEST B-2
TEST B-3
TEST B-4
TEST B-5
TEST B-6
TEST B-7
TEST B-8
TEST B-9
TEST B-10
TEST B-11
TEST B-12
TEST B-13
TEST B-14
TEST B-15
TEST B-16
This means, however, that if you have different strategies for naming tests, you would have to remove all different kinds of prefixes.
Now, on the other hand, if your Test numbers are in the TEST-Space-TestType-Hyphen-TestNumber format, you could use PatIndex and SubString
With Numerals As
(
Select top 100 row_number() over (order by name) TestNumeral
from sys.columns
),
TestNumbers AS
(
Select 'TEST B-' + Convert (VarChar, TestNumeral) TestNumber
From Numerals
Where TestNumeral Between 10 and 19
UNION
Select 'TEST A-' + Convert (VarChar, TestNumeral) TestNumber
From Numerals
Where TestNumeral Between 20 and 29
)
Select *
From TestNumbers
Where Cast (SubString (TestNumber, PATINDEX ('%-%', TestNumber)+1, Len (TestNumber) - PATINDEX ('%-%', TestNumber)) as Integer) between 16 and 26
That should yield the following
TestNumber
-------------------------------------
TEST A-20
TEST A-21
TEST A-22
TEST A-23
TEST A-24
TEST A-25
TEST A-26
TEST B-16
TEST B-17
TEST B-18
TEST B-19
All of your examples seem to have the test numbers at the end. So if you can create a table of patterns and then JOIN using a LIKE statement, you may be able make it work. Here is an example:
;
With TestNumbers As
(
select 'E-1' TestNumber
union select 'E-2'
union select 'E-3'
union select 'E-4'
union select 'E-5'
union select 'E-6'
union select 'E-7'
union select 'SOUTHERN B1'
union select 'SOUTHERN B2'
union select 'SOUTHERN B3'
union select 'SOUTHERN B4'
union select 'SOUTHERN B5'
union select 'SOUTHERN B6'
union select 'SOUTHERN B7'
union select 'Southern CC'
union select 'Southern DD'
union select 'Southern EE'
union select 'TEST B-1'
union select 'TEST B-2'
union select 'TEST B-3'
union select 'TEST B-4'
union select 'TEST B-5'
union select 'TEST B-6'
union select 'TEST B-7'
union select 'TEXAS 1'
union select 'TEXAS 2'
union select 'TEXAS 3'
union select 'TEXAS 4'
union select 'TEXAS 5'
union select 'TEXAS 6'
union select 'TEXAS 7'
union select 'THX 99-20-110-B1'
union select 'THX 99-20-110-B2'
union select 'THX 99-20-110-B3'
union select 'THX 99-20-110-B4'
union select 'THX 99-20-110-B5'
union select 'THX 99-20-110-B6'
union select 'THX 99-20-110-B7'
union select 'Southern AA'
union select 'Southern CC'
union select 'Southern DD'
union select 'Southern EE'
),
Prefixes as
(
Select 'TEXAS ' TestPrefix
Union Select 'THX 99-20-110-B'
Union Select 'E-'
Union Select 'SOUTHERN B'
Union Select 'TEST B-'
)
Select TN.TestNumber
From TestNumbers TN, Prefixes P
Where 1=1
And TN.TestNumber Like '%' + P.TestPrefix + '%'
And Cast (REPLACE (Tn.TestNumber, p.TestPrefix, '') AS INTEGER) between 4 and 6
This will give you
TestNumber
----------------
E-4
E-5
E-6
SOUTHERN B4
SOUTHERN B5
SOUTHERN B6
TEST B-4
TEST B-5
TEST B-6
TEXAS 4
TEXAS 5
TEXAS 6
THX 99-20-110-B4
THX 99-20-110-B5
THX 99-20-110-B6
(15 row(s) affected)
Is this acceptable:
WHERE [Name] IN ( 'TEST B-19', 'TEST B-20' )
The list of values can come from a subquery, e.g.:
WHERE [Name] IN ( SELECT [Name] FROM Elsewhere WHERE ... )
Just a brief of business scenario is table has been created for a good receipt. So here we have good expected line with PurchaseOrder(PO) in first few line. And then we receive each expected line physically and that time these quantity may be different, due to business case like quantity may damage and short quantity like that. So we maintain a status for that eg: OK, Damage, also we have to calculate short quantity based on total of expected quantity of each item and total of received line.
if object_id('DEV..Temp','U') is not null
drop table Temp
CREATE TABLE Temp
(
ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
Item VARCHAR(32),
PO VARCHAR(32) NULL,
ExpectedQty INT NULL,
ReceivedQty INT NULL,
[STATUS] VARCHAR(32) NULL,
BoxName VARCHAR(32) NULL
)
Please see first few line with PO data will be the expected lines,
and then rest line will be received line
INSERT INTO TEMP (Item,PO,ExpectedQty,ReceivedQty,[STATUS],BoxName)
SELECT 'ITEM01','PO-01','30',NULL,NULL,NULL UNION ALL
SELECT 'ITEM01','PO-02','20',NULL,NULL,NULL UNION ALL
SELECT 'ITEM02','PO-01','40',NULL,NULL,NULL UNION ALL
SELECT 'ITEM03','PO-01','50',NULL,NULL,NULL UNION ALL
SELECT 'ITEM03','PO-02','30',NULL,NULL,NULL UNION ALL
SELECT 'ITEM03','PO-03','20',NULL,NULL,NULL UNION ALL
SELECT 'ITEM04','PO-01','30',NULL,NULL,NULL UNION ALL
SELECT 'ITEM01',NULL,NULL,'20','OK','box01' UNION ALL
SELECT 'ITEM01',NULL,NULL,'25','OK','box02' UNION ALL
SELECT 'ITEM01',NULL,NULL,'5','DAMAGE','box03' UNION ALL
SELECT 'ITEM02',NULL,NULL,'38','OK','box04' UNION ALL
SELECT 'ITEM02',NULL,NULL,'2','DAMAGE','box05' UNION ALL
SELECT 'ITEM03',NULL,NULL,'30','OK','box06' UNION ALL
SELECT 'ITEM03',NULL,NULL,'30','OK','box07' UNION ALL
SELECT 'ITEM03',NULL,NULL,'30','OK','box08' UNION ALL
SELECT 'ITEM03',NULL,NULL,'10','DAMAGE','box09' UNION ALL
SELECT 'ITEM04',NULL,NULL,'25','OK','box10'
Below Table is my expected result based on above data.
I need to show those data following way.
So I appreciate if you can give me an appropriate query for it.
Note: first row is blank and it is actually my table header. :)
SELECT '' as 'ITEM', '' as 'PO#', '' as 'ExpectedQty',
'' as 'ReceivedQty','' as 'DamageQty' ,'' as 'ShortQty' UNION ALL
SELECT 'ITEM01','PO-01','30','30','0' ,'0' UNION ALL
SELECT 'ITEM01','PO-02','20','15','5' ,'0' UNION ALL
SELECT 'ITEM02','PO-01','40','38','2' ,'0' UNION ALL
SELECT 'ITEM03','PO-01','50','50','0' ,'0' UNION ALL
SELECT 'ITEM03','PO-02','30','30','0' ,'0' UNION ALL
SELECT 'ITEM03','PO-03','20','10','10','0' UNION ALL
SELECT 'ITEM04','PO-01','30','25','0' ,'5'
Note : we don't received more than expected.
solution should be based on SQL 2000
You should reconsider how you store this data. Separate Expected and Received+Damaged in different tables (you have many unused (null) cells). This way any query should become more readable.
I think what you try to do can be achieved more easily with a stored procedure.
Anyway, try this query:
SELECT Item, PO, ExpectedQty,
CASE WHEN [rec-consumed] > 0 THEN ExpectedQty
ELSE CASE WHEN [rec-consumed] + ExpectedQty > 0
THEN [rec-consumed] + ExpectedQty
ELSE 0
END
END ReceivedQty,
CASE WHEN [rec-consumed] < 0
THEN CASE WHEN DamageQty >= -1*[rec-consumed]
THEN -1*[rec-consumed]
ELSE DamageQty
END
ELSE 0
END DamageQty,
CASE WHEN [rec_damage-consumed] < 0
THEN DamageQty - [rec-consumed]
ELSE 0
END ShortQty
FROM (
select t1.Item,
t1.PO,
t1.ExpectedQty,
st.sum_ReceivedQty_OK
- (sum(COALESCE(t2.ExpectedQty,0))
+t1.ExpectedQty)
[rec-consumed],
st.sum_ReceivedQty_OK + st.sum_ReceivedQty_DAMAGE
- (sum(COALESCE(t2.ExpectedQty,0))
+t1.ExpectedQty)
[rec_damage-consumed],
st.sum_ReceivedQty_DAMAGE DamageQty
from #tt t1
left join #tt t2 on t1.Item = t2.Item
and t1.PO > t2.PO
and t2.PO is not null
join (select Item
, sum(CASE WHEN status = 'OK' THEN ReceivedQty ELSE 0 END)
sum_ReceivedQty_OK
, sum(CASE WHEN status != 'OK' THEN ReceivedQty ELSE 0 END)
sum_ReceivedQty_DAMAGE
from #tt where PO is null
group by Item) st on t1.Item = st.Item
where t1.PO is not null
group by t1.Item, t1.PO, t1.ExpectedQty,
st.sum_ReceivedQty_OK,
st.sum_ReceivedQty_DAMAGE
) a
order by Item, PO