Selecting TOP #Number From "Group" - sql-server

I think my attempt of wording this is going to be more difficult than sharing the code; However...
Firstly, I'm using SQL Server and extracting information from SAP B1.
I'm trying to generate a CSV file from my SQL Dataset to import into a logistics solution.
OrderNo
NumberOfItems
ItemNumber
12345
2
1
12345
2
2
45678
3
1
45678
3
2
45678
3
3
Using the above table, Order number 12345 would be a TOP 2 function, Order number 45678 would be a TOP 3 function.
The #TOPNUMBER will be pulled from an invoice within SAP so it will be dynamic for each Order number.
When I've writte my query I have performed an inner join using <> to bring through thousands of individual results, then filtering down using the TOP function. (which may have been my first mistake)
DECLARE #NUM AS INT = 3
SELECT TOP (#NUM) --Top Function to be replaced with "Number Of ITems"
T0.[DocNum] AS 'Order_No',
ISNULL(T0.NumAtCard,'') AS 'Cust_Ord_No',
Convert(varchar,T0.DocDate,103) AS 'Order_Date',
'Delivery' AS 'Order_Type',
T0.CardName as 'Del_Name',
ISNULL(T12.buIldingS,'') AS 'Del_Addr_1',
ISNULL(T12.BlockS,'') AS 'Del_Addr_2',
ISNULL(T12.StreetS,'') AS 'Del_Addr_3',
ISNULL(T12.CityS,'') AS 'Del_Town',
ISNULL(T12.CountyS,'') AS 'Del_County',
ISNULL(T12.ZipCodeS,'') AS 'Postal_code',
ISNULL(T12.CountryS,'') AS 'Ctry_Code',
ISNULL(t5.Tel1,'') AS 'Contact_Tel_1',
ISNULL(T5.Tel2,'') AS 'Contact_Tel_2',
ISNULL(T5.Cellolar,'') AS 'Contat_Tel_Mob',
ISNULL(T5.E_mailL,'') AS 'Contact_Email',
ISNULL(T0.Comments,'') AS 'Order_Notes',
'00:05' AS 'Unload_Time',
'' AS 'Delivery_Method',
convert(varchar,DateAdd(Day,-7,GETDATE()),103) AS 'Required_Date',
ISNULL('10:00','') AS 'Required_Time_F', ---- Field to be created in sap
ISNULL('16:00','') AS 'Required_Time_T', ---- Field to be created in sap
Concat('Item ',Format(T1.DocEntry,'000')) as 'Item_Code',
Concat('Item ',Format(T1.DocEntry,'000'),' Of ',FORMAT(#NUM,'000')) as 'Item_Desc',
Concat(T0.DocNum,Format(T1.DocEntry,'000'),FORMAT(#NUM,'000')) AS 'package_id',
'1' AS Item_Qty,
T0.Weight/#NUM AS 'Item_Wgt',
'' AS 'Item_Cube',
convert(varchar,DateAdd(Day,-7,GETDATE()),103) AS 'Item_OnHand_Date'
FROM OINV T0 INNER JOIN INV12 T12 ON T0.DocEntry = T12.DocEntry INNER JOIN OCPR T5 ON T5.[CntctCode] = T0.[CntctCode] INNER JOIN ORDR T1 ON T1.DocNum <> T0.DocEntry
WHERE T0.DocNum > '1647674' AND T0.TrnspCode = 1
ORDER BY T1.DocEntry
This code worked perfectly when I was using WHERE T0.DocNum = '1647674' AND T0.TrnspCode = 1. As soon as I've changed it to a where clause with multiple DocNums it's falling flat on its face.
I'm thinking the easier option may be around me removing the Select Top and the Inner join on ORDR and OINV, and using my #NUM variable (from each document) to only actually give me the required number of lines instead of creating lots and filtering it down?
Any help will be appreciated as it took me a good couple of hours to get this to export in a format in which the CSV file takes with no issues!
---- EDIT ---- Update on the code, i think i've got somewhere close by tweaking a few things, i've actually changed the <> join on the ordr table, to a <= join relating to the variable i'm declaring..
I've ditched the variable number, and replaced it with a dynamic field within SAP.
SELECT
T0.[DocNum] AS 'Order_No',
ISNULL(T0.NumAtCard,'') AS 'Cust_Ord_No',
Convert(varchar,T0.DocDate,103) AS 'Order_Date',
'Delivery' AS 'Order_Type',
T0.CardName as 'Del_Name',
ISNULL(T12.buIldingS,'') AS 'Del_Addr_1',
ISNULL(T12.BlockS,'') AS 'Del_Addr_2',
ISNULL(T12.StreetS,'') AS 'Del_Addr_3',
ISNULL(T12.CityS,'') AS 'Del_Town',
ISNULL(T12.CountyS,'') AS 'Del_County',
ISNULL(T12.ZipCodeS,'') AS 'Postal_code',
ISNULL(T12.CountryS,'') AS 'Ctry_Code',
ISNULL(t5.Tel1,'') AS 'Contact_Tel_1',
ISNULL(T5.Tel2,'') AS 'Contact_Tel_2',
ISNULL(T5.Cellolar,'') AS 'Contat_Tel_Mob',
ISNULL(T5.E_mailL,'') AS 'Contact_Email',
ISNULL(T0.Comments,'') AS 'Order_Notes',
'00:05' AS 'Unload_Time',
'' AS 'Delivery_Method',
convert(varchar,DateAdd(Day,-7,GETDATE()),103) AS 'Required_Date',
ISNULL('10:00','') AS 'Required_Time_F', ---- Field to be created in sap
ISNULL('16:00','') AS 'Required_Time_T', ---- Field to be created in sap
Concat('Item ',Format(T1.DocEntry,'000')) as 'Item_Code',
Concat('Item ',Format(T1.DocEntry,'000'),' Of ',FORMAT(T0.U_PACKQTY,'000')) as 'Item_Desc',
Concat(T0.DocNum,Format(T1.DocEntry,'000'),FORMAT(T0.U_PACKQTY,'000')) AS 'package_id',
'1' AS Item_Qty,
T0.Weight/T0.U_PACKQTY AS 'Item_Wgt',
'' AS 'Item_Cube',
convert(varchar,DateAdd(Day,-7,GETDATE()),103) AS 'Item_OnHand_Date'--,
FROM OINV T0 INNER JOIN INV12 T12 ON T0.DocEntry = T12.DocEntry INNER JOIN OCPR T5 ON T5.[CntctCode] = T0.[CntctCode] INNER JOIN ORDR T1 ON T1.DocEntry <= T0.U_PACKQTY
WHERE T0.DocNum IN ('1647775','1647777','1647778')
The WHERE T0.DocNum IN will be replaced with another where clause to look for "Van Delivery" transport types, and DocNum which will be greater than previously exported DocNum, this will probably be by inserting the "MAX" DocNum into a table at the time i export it, and then using this "MAX" Docnum as a "Min" value in my where clause, so this should always look for new orders only. (I think!)

I think DocNum is of type varchar

Related

How to use multi-column sql query result as input for new query

An alternative title would be "how to join two external tables with two different values, by row, from query result". I'm open to suggestions/edits.
I have this query result (2 of 6000+ lines shown)
objectpriref packpriref locpriref
------------ ---------- ----------
30889 229 16672
30990 267 16697
and 2 tables like this
objname location
id name id name
---------.-------- ---------.----
30889 MACQ_001 16672 A16
30890 BLAH_002 16673 A17
30990 FOOH_009 16697 B300
The desired result should look something like this
objectpriref objname locname
------------ ---------- ----------
30889 MACQ_001 A16
30990 FOOH_009 B300
If this can be done within SQL, what would be the best approach? What I've tried so far:
Put the query result into a temp table using INTO #mtt (for MyTempTable) from here and then trying to address the various columns as #mtt.objectpriref etc. This gets me invalid object name #mtt. This might deserve a separate question.
Put the query inside another select, but that runs into this, using IN works on single columns only.
I may be using the wrong keywords to google for. Any suggestions?
Something like
select
T1.objectpriref,
T2.name as objname,
T3.name as locname
from
table1 T1
inner join table2 T2
on T2.objnameid = T1.objectpriref
inner join table3 T3
on T3.locationid = T1.locpriref
Try this with your table names.
select t1.objectpreiref, t2.name, t3.name
from table1 t1
left join table2 t2 on t1.objectpriref = t2.objnameid
left join table3 t3 on t1.locpriref = t3.locationid

How to Left Inner Join two queries in Sybase?

I have two queries that should be joined together. Here is my query 1:
SELECT
t1.rec_id,
t1.category,
t1.name,
t1.code,
CASE
WHEN t1.name= 'A' THEN SUM(t1.amount)
WHEN t1.name = 'D' THEN SUM(t1.amount)
WHEN t1.name = 'H' THEN SUM(t1.amount)
WHEN t1.name = 'J' THEN SUM(t1.amount)
END AS Amount
FROM Table1 t1
GROUP BY t1.name, t1.rec_id, t1.category, t1.code
Query 1 produce this set of results:
Rec ID Category Name Code Amount
1 1 A MIX 70927.00
1 3 D MIX 19922.00
1 2 H MIX 55104.00
1 4 J MIX 76938.00
Then I have query 2:
SELECT
CASE
WHEN t2.category_id = 1 THEN SUM(t2.sum)
WHEN t2.category_id = 2 THEN SUM(t2.sum)
WHEN t2.category_id = 3 THEN SUM(t2.sum)
WHEN t2.category_id = 4 THEN SUM(t2.sum)
END AS TotalSum
FROM Table2 t2
INNER JOIN Table1 t1
ON t1.amnt_id = t2.amnt_id
AND t2.unique_id = #unique_id
GROUP BY t2.category_id
The result set of query 2 is this:
TotalSum
186013.00
47875.00
12136.00
974602.00
All I need is this result set that combines query 1 and query 2:
Rec ID Category Name Code Amount TotalSum
1 1 A MIX 70927.00 186013.00
1 3 D MIX 19922.00 47875.00
1 2 H MIX 55104.00 12136.00
1 4 J MIX 76938.00 974602.00
As you can see there is connection between table 1 and table 2. That connection is amnt_id. However, I tried doing LEFT INNER JOIN on query 1 and then simply using same logic with case statement to get the total sum for table 2. Unfortunately Sybase version that I use does not support Left Inner Join. I'm wondering if there is other way to join these two queries? Thank you
I wondered if the CASE statement makes sense in the first query because it sums in every row. Are there other values for the name column except A, D, H, J? If not you can change the CASE statement to SUM(t1.amount) AS Amount. Also the GROUP BY in the first query seems dubious to me: you are grouping by the record id column - that means you are not grouping at all but instead return every row. If that is what you really want you can omit the SUM at all and just return the pure amount column.
As far as I understood your problem and your data structure: the values in Table2 are kind of category sums and the values in Table1 are subsets. You would like to see the category sum for every category in Table1 next to the single amounts?
You would typically use a CTE (common table expression, "WITH clause") but ASE doesn't support CTEs, so we have to work with joins. I recreated your tables in my SQL Anywhere database and put together this example. In a nutshell: both queries are subqueries in an outer query and are left joined on the category id:
SELECT *
FROM
(
SELECT
t1.rec_id,
t1.category,
t1.name,
t1.code,
CASE
WHEN t1.name= 'A' THEN SUM(t1.amount)
WHEN t1.name = 'D' THEN SUM(t1.amount)
WHEN t1.name = 'H' THEN SUM(t1.amount)
WHEN t1.name = 'J' THEN SUM(t1.amount)
END AS Amount
FROM Table1 t1
GROUP BY t1.rec_id, t1.name, t1.category, t1.code
) AS t1
LEFT JOIN
(
SELECT category_id, SUM(sum) FROM
table2
GROUP BY category_id
) AS totals(category_id, total_sum)
ON totals.category_id = t1.category;
This query gives me:
Rec ID Category Name Code Amount Category_id total_sum
2 3 D MIX 19922.00 3 47875.00
3 2 H MIX 55104.00 2 12136.00
1 1 A MIX 70927.00 1 186013.00
4 4 J MIX 76938.00 4 974602.00
You surely have to tweak it a bit including your t2.unique_id column (that I don't understand from your queries) but this is a practical way to work around ASE's missing CTE feature.
BTW: it's either an INNER JOIN (only the corresponding records from both tables) or a LEFT (OUTER) JOIN (all from the left, only the corresponding records from the right table) but a LEFT INNER JOIN makes no sense.

Create and execute stored procedure in SQL Server

I have four tables:
dbo.Projects (id, ProjectName, Areas, PaymentSystem, Districts.id, purpose.id, types.id, etc)
dbo.Districts(id, DistrictsName)
dbo.Purpose (id, PurposeName) - has residential & commercial
dbo.Types (id, typName)
I want to select DistrictsName where PurposeName = 'residential'
I tried this procedure :
CREATE PROCEDURE [dbo].[SearchResidentialProjects]
AS
SELECT
dbo.Projects.ID,
dbo.Districts.DistrictName,
dbo.Purpose.PurposeName
FROM
dbo.Projects
INNER JOIN
dbo.Purpose ON dbo.Projects.PurposeID = dbo.Purpose.ID
INNER JOIN
dbo.Districts ON dbo.Projects.DistrictID = dbo.Districts.ID
WHERE
dbo.Purpose.PurposeName = N'Residential'
this is the result from this procedure:
ID DistrictsName PurposeName
1 District1 residential
2 District1 residential
3 District2 residential
4 District2 residential
i want display the DistrictsName without duplicate or with different values , i a have also one more project per district in projects records . this what i want to display :
ID DistrictsName PurposeName
1 District1 residential
2 District2 residential
how i get this result ,
any help is appreciated.
Why do people use stored procedures when views are much more appropriate? I have never understood this. It seems peculiar to SQL Server users.
In any case, you can do what you want with aggregation:
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as id,
d.DistrictName, p.PurposeName
FROM dbo.Projects pr INNER JOIN
dbo.Purpose pu
ON pr.PurposeID = pu.ID INNER JOIN
dbo.Districts d
ON pr.DistrictID = d.ID
WHERE pu.PurposeName = N'Residential'
GROUP BY d.DistrictName, p.PurposeName;
The use of table aliases makes the query much easier to write and to read.
In addition, I don't understand the id column being output. Why would you want to construct a new id? In any case, that is what your data suggests.
Use DISTINCT statement for removing the duplicates:
CREATE PROCEDURE [dbo].[SearchResidentialProjects]
AS
SELECT DISTINCT
dbo.Projects.ID,
dbo.Districts.DistrictName,
dbo.Purpose.PurposeName
FROM
dbo.Projects
INNER JOIN
dbo.Purpose ON dbo.Projects.PurposeID = dbo.Purpose.ID
INNER JOIN
dbo.Districts ON dbo.Projects.DistrictID = dbo.Districts.ID
WHERE
dbo.Purpose.PurposeName = N'Residential'

RIGHT\LEFT Join does not provide null values without condition

I have two tables one is the lookup table and the other is the data table. The lookup table has columns named cycleid, cycle. The data table has SID, cycleid, cycle. Below is the structure of the tables.
If you check the data table, the SID may have all the cycles and may not have all the cycles. I want to output the SID completed as well as missed cycles.
I right joined the lookup table and retrieved the missing as well as completed cycles. Below is the query I used.
SELECT TOP 1000 [SID]
,s4.[CYCLE]
,s4.[CYCLEID]
FROM [dbo].[data] s3 RIGHT JOIN
[dbo].[lookup_data] s4 ON s3.CYCLEID = s4.CYCLEID
The query is not displaying me the missed values when I query for all the SID's. When I specifically query for a SID with the below query i am getting the correct result including the missed ones.
SELECT TOP 1000 [SID]
,s4.[CYCLE]
,s4.[CYCLEID]
FROM [dbo].[data] s3 RIGHT JOIN [dbo].[lookup_data] s4
ON s3.CYCLEID = s4.CYCLEID
AND s3.SID = 101002
ORDER BY [SID], s4.[CYCLEID]
As I am supplying this query into tableau I cannot provide the sid value in the query. I want to return all the sid's and from tableau I will be do the rest of the things.
The expected output that i need is as shown below.
I wrote a cross join query like below to acheive my expected output
SELECT DISTINCT
tab.CYCLEID
,tab.SID
,d.CYCLE
FROM ( SELECT d.SID
,d.[CYCLE]
,e.CYCLEID
FROM ( SELECT e.sid
,e.CYCLE
FROM [db_temp].[dbo].[Sheet3$] e
) d
CROSS JOIN [db_temp].[dbo].[Sheet4$] e
) tab
LEFT OUTER JOIN [db_temp].[dbo].[Sheet3$] d
ON d.CYCLEID = tab.CYCLEID
AND d.SID = tab.SID
ORDER BY tab.SID
,tab.CYCLEID;
However I am not able to use this query for more scenarios as my data set have nearly 20 to 40 columns and i am having issues when i use the above one.
Is there any way to do this in a simpler manner with only left or right join itself? I want the query to return all the missing values and the completed values for the all the SID's instead of supplying a single sid in the query.
You can create a master table first (combine all SID and CYCLE ID), then right join with the data table
;with ctxMaster as (
select distinct d.SID, l.CYCLE, l.CYCLEID
from lookup_data l
cross join data d
)
select d.SID, m.CYCLE, m.CYCLEID
from ctxMaster m
left join data d on m.SID = d.SID and m.CYCLEID = d.CYCLEID
order by m.SID, m.CYCLEID
Fiddle
Or if you don't want to use common table expression, subquery version:
select d.SID, m.CYCLE, m.CYCLEID
from (select distinct d.SID, l.CYCLE, l.CYCLEID
from lookup_data l
cross join data d) m
left join data d on m.SID = d.SID and m.CYCLEID = d.CYCLEID
order by m.SID, m.CYCLEID

Get a collision free hash for a specific query or a view with SQL Server 2008

I am working on a project where I need to synchronize data from our system to an external system. What I want to achieve, is to periodically send only changed items (rows) from a custom query. This query looks like this (but with many more columns) :
SELECT T1.field1,
T1.field2,
T1.field2,
T1.field3,
CASE WHEN T1.field4 = 'some-value' THEN 1 ELSE 0 END,
T2.field1,
T3.field1,
T4.field1
FROM T1
INNER JOIN T2 ON T2.pk = T2.fk
INNER JOIN T3 ON T3.pk = T2.fk
INNER JOIN T4 ON T4.pk = T2.fk
I want to avoid to have to compare every field one to one between synchronizations. I came with the idea that I could generate a hash for every row from my query, and compare this with the hash from the previous synchronization, which will return only the changed rows. I am aware of the CHECKSUM function, but it is very collision-prone and might miss changes sometimes. However I like the way I could just make a temp table and use CHECKSUM(*), which makes maintenance easier (not having to add fields in the query and in the CHECKSUM) :
SELECT T1.field1,
T1.field2,
T1.field2,
T1.field3,
CASE WHEN T1.field4 = 'some-value' THEN 1 ELSE 0 END,
T2.field1,
T3.field1,
T4.field1
INTO #tmp
FROM T1
INNER JOIN T2 ON T2.pk = T2.fk
INNER JOIN T3 ON T3.pk = T2.fk
INNER JOIN T4 ON T4.pk = T2.fk;
-- get all columns from the query, plus a hash of the row
SELECT *, CHECKSUM(*)
FROM #tmp;
I am aware of HASHBYTES function (which supports sha1, md5, which are less prone to collisions), but it only accept varchar or varbinary, not a list of columns or * the way CHECKSUM does. Having to cast/convert every column from the query is a pain in the ... and opens the door to errors (forget to include a new field for instance)
I also noticed Change Data Capture and Change Tracking features of SQL Server, but they all seems complicated and overkill for what I am doing.
So my question : is there an other method to generate a hash from a query or a temp table that meets my criterias ?
If not, is there an other way to achieve this kind of work (to sync differences from a query)
I found a way to do exactly what I wanted, thanks to the FOR XML clause :
SELECT T1.field1,
T1.field2,
T1.field2,
T1.field3,
CASE WHEN T1.field4 = 'some-value' THEN 1 ELSE 0 END,
T2.field1,
T3.field1,
T4.field1
INTO #tmp
FROM T1
INNER JOIN T2 ON T2.pk = T2.fk
INNER JOIN T3 ON T3.pk = T2.fk
INNER JOIN T4 ON T4.pk = T2.fk;
-- get all columns from the query, plus a hash of the row (converted in an hex string)
SELECT T.*, CONVERT(VARCHAR(100), HASHBYTES('sha1', (SELECT T.* FOR XML RAW)), 2) AS sHash
FROM #tmp AS T;

Resources