Progress OpenEdge, ODBC, recordsets, joining, oh my - mysql

So frustrated here. I'm not a DB Admin but can get around. I'm writing some ODBC queries against a Progress OpenEdge database that we only have view access to. For the longest time there have been no problems until recently they changed the data structure and for who knows why, they moved customer phone numbers into their own table called "contact" whereas before they were in "cif", where the address etc still remain.
Instead of creating the "contact" table with one row for for each customer and fields for each phone number, they use a code of 0-4, number/email, and customer. So if a customer has 4 phone numbers, they have 4 rows with different code, contact fields and customer name repeated.
I'm trying to join the "contact" table with the "cif" table so it returns each mention of customer in "cif" no matter how many times it is listed in "cif", but include all phone numbers associated from "contact" in each line.
Table structure simplified is like so:
Table "contact"
code | contact(#) | customer
--------------------------------
0 | (123)456-7890 | ABC Corp
1 | (123)456-7891 | ABC Corp
0 | (987)654-3210 | CBA Inc
Table "cif"
customer | b_in_low | b_in_high
----------------------------------
ABC Corp | 50.45 | 134.66
ABC Corp | 64.45 | 188.99
CBA Inc | 12.56 | 890.33
What I'm trying to return is a joined row for each row in "cif" but include all numbers from "contact" so the table above would return:
rsRow1) ABC Corp, 0, (123)456-7890, 1, (123)456-7891, 50.45, 134.66
rsRow2) ABC Corp, 0, (123)456-7890, 1, (123)456-7891, 64.45, 188.99
rsRow3) CBA Inc, 0, (987)654-3210,,, 12.56, 890.00
What I do NOT want:
rsRow1) ABC Corp, 0, #, 50.45, 134.66
rsRow2) ABC Corp, 1, #, 50.45, 134.66
rsRow3) ABC Corp, 0, #, 64.45, 188.99
rsRow4) ABC Corp, 1, #, 64.45, 188.99
rsRow5) CBA Inc, 0, #, 12.56 | 890.00
Make sense? I can get it to work by one rs on the "cif" table and during each repeat region, perform another query on "contact" using the "cif.customer" as a WHERE filter but obviously it is extremely slow and would result in potentially thousands of queries.
I can get it to return only 1 line from "cif" but only 1 number from "contact"
or
I can get it to return up to 5 duplicate "cif" lines with the 5 different phone numbers for each.
So in a nutshell, how can I efficiently get 1 row from "cif" while listing all +-5 phone numbers from "contact"?

How about this:
SELECT c.customer
, ISNULL(c1.code,'')
, ISNULL(c1.contact,'')
, ISNULL(c2.code,'')
, ISNULL(c2.contact,'')
, ISNULL(c3.code,'')
, ISNULL(c3.contact,'')
, ISNULL(c4.code,'')
, ISNULL(c4.contact,'')
, ISNULL(c5.code,'')
, ISNULL(c5.contact,'')
, c.b_in_low
, c.b_in_high
FROM CIF AS c
LEFT OUTER JOIN Contact AS c1
ON c1.customer = c.customer
AND c1.code = 0
LEFT OUTER JOIN Contact AS c2
ON c2.customer = c.customer
AND c2.code = 1
LEFT OUTER JOIN Contact AS c3
ON c3.customer = c.customer
AND c3.code = 1
LEFT OUTER JOIN Contact AS c4
ON c4.customer = c.customer
AND c4.code = 1
LEFT OUTER JOIN Contact AS c5
ON c5.customer = c.customer
AND c5.code = 1
It depends on the type of the field 'code' what is returned, if you want it to be blank you probably have to do another translation.
Not pretty, but I think it works.

There is an XML option in SQL server that lets you take multiple results and merge them into a concatenated string in a single field. It's the STUFF FOR XML PATH command. Here's an example of how I've I used it.
SELECT call_number, item_number,
REPLACE(REPLACE(STUFF((SELECT DISTINCT ',',''''
+ CONVERT(VARCHAR(20), item_line) + '**‘
+ item_number + '**‘ + work_code + ''''
FROM stage_call_item_detail s
WHERE h.source_system_code = s.source_system_code
AND h.domain_code = s.domain_code
AND h.call_number = s.call_number
AND s.site_code IS NOT NULL
ORDER BY 2
FOR XML PATH(''))
,1, 1, '‘) ,'<item_number>',''''),'</item_number>','''') call_line_item_list, *
FROM stage_ssm_call_history h
WHERE call_number = 'A1014-01'

Can you use buffers when building a query?
If you can, you could do something like:
define buffers contactA ... contactN for contact.
FOR EACH cif WHERE cif.customer = "ABC Corp",
FIRST contact OF cif OUTER-JOIN,
FIRST contactA OF cif WHERE ROWID(contactA) <> ROWID(contact) OUTER-JOIN,
...
FIRST contactN of cif
WHERE ROWID(contactN) <> ROWID(contact)
AND ROWID(contactN) <> ROWID(contactA)
...
This is not a nice solution, and performance can be affected seriously... And this will only work if you have limited number of contacts, say, 0-4.

Related

Can I craft a statement to return records where (attribute A = A1 and attribute B = B1) OR (attrbt A = A2 and attrbt B = B2) OR etc etc?

User has given me a table of vendor #s and Invoice #s, and wants a query to find the documents with those attributes. Tried just giving him:
select * from document.docdata
join document.documents.vendor #s
join document.documents.invoice #s
Where
vendor # in (paste in column A from his table)
AND
invoice # in (paste in column b from his table)
But that didn't do the trick, because he's wanting to see the docs whose vendor# and invoice# match the rows of the table, as in vendor#, invoice# = A1, B1. My first question was "Why not just search by the invoice# and be done with it?"
But turns out the invoice numbers aren't unique.
So I'm needing a better way of writing this:
Select * from table
join vendor #s
join invoice #s
Where
(Vendor#=A1 AND Invoice#=B1)
OR
(Vendor#=A2 AND Invoice#=B2)
....
OR
(Vendor#=A652 AND Invoice#=B652)
For sample data, here's an example with the first 10 items from the user. I have this data in csv format.
A | B
---------------------
354055 | 1637
259769 | 2112
259769 | 2314
153060 | 47185
174829 | 63486
297719 | 4994-033017
203110 | 1360
292193 | 2058-09-1271
202308 | 60513
286641 | 1975
So I need the records that match both Company 354055 and Invoice 1637, as well as both Company 259769 and Invoice 2112, plus both Company 259769 and Invoice 2314, etc.
EDIT: I ended up just using excel to get 659 lines of "(Vendor#=x AND Invoice#=y) OR". Probably could run faster but it works, so off to production it goes.
MySQL supports tuple syntax:
Select *
from table join
vendor #s
on . . . join
invoice #s
on . . .
Where (#Vendor#, Invoice#) in ( (A1, B1), (A2, B2), . . . )
This is a rough answer, since we don't have good sample data or column names, but the general idea is, since you have an external table that matches invoice #s to vendor #s, you should import that data into a temporary table. Then you can join against the table for records where both conditions match in an efficient way.
There are also VALUES() expressions (formally: Table Value Constructors). You could use this to generate your query with a little less code: create the expression for your data as part of the SQL string, and then JOIN against that expression to accomplish the filter.
SELECT d.*
FROM document.docdata d
INNER JOIN (
VALUES
ROW(354055,'1637'),
ROW(259769,'2112'),
ROW(259769,'2314'),
ROW(153060,'47185')
-- etc
) filter on filter.invoice = d.invoice and filter.company = d.company

MySQL query to gather incorrectly stored data

I have recently taken over a email campaign project and need to generate a report for the customer. However the data has been stored very strangely.
Basically the client wants a report of the subscribers first name and last name that have subscribed to a emailing list.
Example table data.
------------------------------------------------------------
id | owner_id | list_id | field_id | email_address | value
------------------------------------------------------------
1 10 1 137 me#example.com John
2 10 1 138 me#example.com Doe
So as you can see, John Doe has subscribed to mailing list 1, and field_id 137 is his first name and field_id 138 is his last name.
The client is looking for a export with the users first name and last name all is one field.
I tred the following sql query
SELECT value
FROM Table_A AS child
INNER JOIN Table_A AS parent
ON parent.email_address = child.email_address
WHERE child.owner_id = '10'
But unfortunately the query gives me the results in many rows but not appending the first name and last name into one field,
If anyone can provide some assistance that would be awesome.
Thanks.
SELECT
concat( parent.value,' ',child.value)name
FROM mytable AS child
left JOIN mytable AS parent
ON parent.email_address = child.email_address
WHERE child.owner_id = '10'
and parent.field_id=137 and child.field_id=138
Check at-http://sqlfiddle.com/#!9/199b4b/45
I think you have to use a variable to put in there everything you have to and then select the variable with the desired name of yours.
For example:
DECLARE #yourvariable VARCHAR(MAX)
SELECT #yourvariable = COALESCE(#yourvariable + " ") + value
FROM table_A
WHERE owner_id = 10
SELECT #yourvariable as FullName
Try that, it might help.
You can try this code(column name equals value in your original DB):
select a.name
from
table_a a inner join table_a b
on a.email_address = b.email_address and a.field_id <> b.field_id
where a.owner_id=10
order by a.field_id
Here is the example link:
http://sqlfiddle.com/#!9/5fbdf6/25/0
As per assumptions, first name has the field id 137 and last name has the field id 138.
You can try the following query to get the desired result.
SELECT CONCAT(SUBSTRING_INDEX(GROUP_CONCAT(`value`),",",1)," ",SUBSTRING_INDEX(GROUP_CONCAT(`value`),",",-1)) AS client_name
FROM Table_A
WHERE owner_id = 10
AND field_id IN (137, 138)
GROUP BY email_address;

SQL Aggregate function that shows if there is an homogeneous value in a group

I am working in Access 2016 and I was wondering if there is an aggregate function which gives me a value, but only if the value is the same for the rows in the group, otherwise null.
For example, I have a list of bills and every bill has items. (Just a simplified example)
Table BILLS
- ID int
- NAME varchar(100)
- ...
Table ITEMS
- ID int
- FK_BILL_ID int
- PRICE currency
- PAID bool
- ...
My query is like:
select b.NAME as Name, sum(i.PRICE) as PriceSum, ????(i.PAID) as PayStatus
from BILLS b
left join ITEMS i on i.FK_BILL_ID = b.ID
group by b.ID, b.NAME
What I want to achieve:
Name | PriceSum | PayStatus
-------+----------+-------------
Bill A | 19,99 | true
Bill B | 12,34 | false
Bill C | 9,99 | null
Bill D | 99,99 | null
The field PayStatus should be true if all items are paid (PAID=true)
The field PayStatus should be false if all items are not paid (PAID=false)
Otherways, PayStatus should be null (if there are no items, or if only a part of the items were paid)
Given that MS Access boolean field stores True as -1 and False as 0, we can write the query as shown below
SELECT b.BillNAME as BillName,
SUM(i.PRICE) AS PriceSum, IIF(SUM(PAID) =0, "FALSE", IIF( count(*)*-1
=sum(PAID), "TRUE", "NULL")) as PayStatus
FROM BILLS b
left join ITEMS i on i.FK_BILL_ID = b.ID
GROUP BY BillName;
Please note that as Name is reserved word in MS Access, I have changed the field name to BillName.
Is Paid field a Yes/No type?
Try an Expression in the aggregate query like:
PayStatus: Switch(Count(*)=Abs(Sum([Paid])),"True", Sum([Paid])=0,"False", True,Null)
Name is a reserved word. Should not use reserved words as names.
And I was composing when Alok answered. Great minds .... Although just multiplying by -1 probably better than calling Abs() function.

Dynamic Header Mapping in SSIS

Can any one help on below implementation...
Source txt/Excel File may come in below format
Case-1
CName Pan Mobile
------------------------
A PANA1 1234567891
B PANB2 1234567892
Case-2
Pan_No Mobile_No CustomerName Gender
----------------------------------------
PANA1 1234567891 A M
PANB2 1234567892 B F
Case-3
Email Mobile_Number Customer_Name PanNumber
----------------------------------------------------
A#gmail.com 1234567891 A PANA1
B#gmail.com 1234567892 B PANB2
Destination Table
Customer Table
C_Name C_PanNo C_MobileNo
---------------------------
A PANA1 1234567891
B PANB2 1234567892
ExternalHeaderMapping Table
Id DestinationColumnName ExternalHeaderName
-----------------------------------------------
1 C_Name CName
2 C_Name CustomerName
3 C_Name Customer_Name
3 C_PanNo Pan
4 C_PanNo Pan_No
5 C_PanNo PanNumber
6 C_MobileNo Mobile
7 C_MobileNo Mobile_No
8 C_MobileNo Mobile_Number
In the above case I need to build the SSIS packge it should work for all the above three case even
the order of column is changed and new column being added.
In case-2 & Case-3 it should ignore the Gender and Email column.
I am new to SSIS, please help me how to achive the same with SSIS..I know it can only achive through Script component but
don't know how to do....
If I will do I will use Script Task and use below logic. (Sorry I can not write code but i can give you overall Idea).. I am not sure about performance but it works 100%...
Use “Script Task”
Output – c1, c2, c3 (assume output columns are fix)
Variable
#Headers = H1,H2,H3,H4 (get value from file like first row/header row)
#Columns_Object = Multiple row from Header mapping table
Row value should like:
1) C_Name |CName,CustomerName,Customer_Name
2) C_PanNo |PAN,Pan_No,Pan_Number
#Array_Header = #Headers splite using “,”
Now value is like
[0] = H1,
[1] = H2, etc
Use foreach loop #Array_Header
Pick one value “H1” and find Row from #Columns_Object
Once you get row find value before “|” (e.g get C_Name) store in local variable #SelectCol
Switch
{
If #SelectCol = ‘C_Name’
Then store into
Ouput C1 =datarow.col[0]
( “0” we get base on position of H1) if loop is looking for H2 then index is “1”
Else if #SelectCol = “C_PanNo”
Ouput C2 =datarow.col[1]
END
}

Dynamic Table Joining Based on Another Table Column Value?

I'm trying to figure out if there is a simple way to dynamically load a 2nd table based on the column value of the first table with mysql
Servers (Table 1):
ID | Game | Title
Servers_1 (Table 2, option 1):
server_id (links to servers.id) | game_version | players | plugins
Servers_2 (Table 2, option 2):
server_id (links to servers.id) | game_version | players | mods | game_map
Servers_etc. (Table 2, option etc.)
Trying to figure out how to do something like
left_join servers_[servers.game] on servers.id = servers_[servers.game].server_id
So it would grab the value of servers.game and use that to finish the table name. If this is not possible, then is a case statement possible such as:
Left_Join
if ( servers.game == 1 ) 'servers_1'
elseif ( servers.game == 2 ) 'servers_2'
elseif ( servers.game == 3 ) 'servers_3'
One option would be to LEFT JOIN each of the tables and use a CASE statement to return the appropriate data.
Something like this should help get you started:
SELECT S.Id, S.Game, S.Title,
CASE S.Game
WHEN 1 THEN S1.game_version
WHEN 2 THEN S2.game_version
END game_version,
...
FROM Servers S
LEFT JOIN Servers_1 S1 ON S.id = S1.Server_Id AND S.Game = 1
LEFT JOIN Servers_2 S2 ON S.id = S2.Server_Id AND S.Game = 2
Instead of using CASE, you could probably just use COALESCE as each Id/Game should be unique and only 1 wouldn't be NULL:
SELECT COALESCE(S1.game_version,S2.game_version,...) game_version
If there is no way the same server id can be in multiple tables, then you can leave the AND S.Game... out of the LEFT JOINs as it wouldn't longer be needed. Depends on your unique keys.
Alternatively, you could use Dynamic SQL.